From ea32eb78f425019010668b09962104f8a58d3275 Mon Sep 17 00:00:00 2001 From: Javinator9889 Date: Sun, 24 May 2020 19:23:43 +0200 Subject: [PATCH 01/95] Initialized Firebase functions project --- functions/.gitignore | 8 + functions/package-lock.json | 2521 +++++++++++++++++++++++++++++++++++ functions/package.json | 29 + functions/src/index.ts | 54 + functions/tsconfig.json | 15 + functions/tslint.json | 115 ++ 6 files changed, 2742 insertions(+) create mode 100644 functions/.gitignore create mode 100644 functions/package-lock.json create mode 100644 functions/package.json create mode 100644 functions/src/index.ts create mode 100644 functions/tsconfig.json create mode 100644 functions/tslint.json diff --git a/functions/.gitignore b/functions/.gitignore new file mode 100644 index 0000000..7fbb8b4 --- /dev/null +++ b/functions/.gitignore @@ -0,0 +1,8 @@ +## Compiled JavaScript files +**/*.js +**/*.js.map + +# Typescript v1 declaration files +typings/ + +node_modules/ \ No newline at end of file diff --git a/functions/package-lock.json b/functions/package-lock.json new file mode 100644 index 0000000..83a83d5 --- /dev/null +++ b/functions/package-lock.json @@ -0,0 +1,2521 @@ +{ + "name": "functions", + "requires": true, + "lockfileVersion": 1, + "dependencies": { + "@babel/code-frame": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.8.3.tgz", + "integrity": "sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g==", + "dev": true, + "requires": { + "@babel/highlight": "^7.8.3" + } + }, + "@babel/helper-validator-identifier": { + "version": "7.9.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.9.5.tgz", + "integrity": "sha512-/8arLKUFq882w4tWGj9JYzRpAlZgiWUJ+dtteNTDqrRBz9Iguck9Rn3ykuBDoUwh2TO4tSAJlrxDUOXWklJe4g==", + "dev": true + }, + "@babel/highlight": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.9.0.tgz", + "integrity": "sha512-lJZPilxX7Op3Nv/2cvFdnlepPXDxi29wxteT57Q965oc5R9v86ztx0jfxVrTcBk8C2kcPkkDa2Z4T3ZsPPVWsQ==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.9.0", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, + "@firebase/app-types": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.6.1.tgz", + "integrity": "sha512-L/ZnJRAq7F++utfuoTKX4CLBG5YR7tFO3PLzG1/oXXKEezJ0kRL3CMRoueBEmTCzVb/6SIs2Qlaw++uDgi5Xyg==" + }, + "@firebase/auth-interop-types": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/@firebase/auth-interop-types/-/auth-interop-types-0.1.5.tgz", + "integrity": "sha512-88h74TMQ6wXChPA6h9Q3E1Jg6TkTHep2+k63OWg3s0ozyGVMeY+TTOti7PFPzq5RhszQPQOoCi59es4MaRvgCw==" + }, + "@firebase/component": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.12.tgz", + "integrity": "sha512-03w800MxR/EW1m7N0Q46WNcngwdDIHDWpFPHTdbZEI6U/HuLks5RJQlBxWqb1P73nYPkN8YP3U8gTdqrDpqY3Q==", + "requires": { + "@firebase/util": "0.2.47", + "tslib": "1.11.1" + } + }, + "@firebase/database": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/@firebase/database/-/database-0.6.3.tgz", + "integrity": "sha512-gHoCISHQVLoq+rGu+PorYxMkhsjhXov3ocBxz/0uVdznNhrbKkAZaEKF+dIAsUPDlwSYeZuwWuik7xcV3DtRaw==", + "requires": { + "@firebase/auth-interop-types": "0.1.5", + "@firebase/component": "0.1.12", + "@firebase/database-types": "0.5.1", + "@firebase/logger": "0.2.4", + "@firebase/util": "0.2.47", + "faye-websocket": "0.11.3", + "tslib": "1.11.1" + } + }, + "@firebase/database-types": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-0.5.1.tgz", + "integrity": "sha512-onQxom1ZBYBJ648w/VNRzUewovEDAH7lvnrrpCd69ukkyrMk6rGEO/PQ9BcNEbhlNtukpsqRS0oNOFlHs0FaSA==", + "requires": { + "@firebase/app-types": "0.6.1" + } + }, + "@firebase/logger": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.2.4.tgz", + "integrity": "sha512-akHkOU7izYB1okp/B5sxClGjjw6KvZdSHyjNM5pKd67Zg5W6PsbkI/GFNv21+y6LkUkJwDRbdeDgJoYXWT3mMA==" + }, + "@firebase/util": { + "version": "0.2.47", + "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.2.47.tgz", + "integrity": "sha512-RjcIvcfswyxYhf0OMXod+qeI/933wl9FGLIszf0/O1yMZ/s8moXcse7xnOpMjmQPRLB9vHzCMoxW5X90kKg/bQ==", + "requires": { + "tslib": "1.11.1" + } + }, + "@google-cloud/common": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@google-cloud/common/-/common-2.4.0.tgz", + "integrity": "sha512-zWFjBS35eI9leAHhjfeOYlK5Plcuj/77EzstnrJIZbKgF/nkqjcQuGiMCpzCwOfPyUbz8ZaEOYgbHa759AKbjg==", + "optional": true, + "requires": { + "@google-cloud/projectify": "^1.0.0", + "@google-cloud/promisify": "^1.0.0", + "arrify": "^2.0.0", + "duplexify": "^3.6.0", + "ent": "^2.2.0", + "extend": "^3.0.2", + "google-auth-library": "^5.5.0", + "retry-request": "^4.0.0", + "teeny-request": "^6.0.0" + } + }, + "@google-cloud/firestore": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/@google-cloud/firestore/-/firestore-3.8.0.tgz", + "integrity": "sha512-FEv52jhsRAV/0nHaMy0PAmExYkYRLDoOXRB+85euaC72HZU8aV/bgOi1OvAeiD+ogoO39ilxiHQh5PaRHIolug==", + "optional": true, + "requires": { + "deep-equal": "^2.0.0", + "functional-red-black-tree": "^1.0.1", + "google-gax": "^1.13.0", + "readable-stream": "^3.4.0", + "through2": "^3.0.0" + } + }, + "@google-cloud/paginator": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@google-cloud/paginator/-/paginator-2.0.3.tgz", + "integrity": "sha512-kp/pkb2p/p0d8/SKUu4mOq8+HGwF8NPzHWkj+VKrIPQPyMRw8deZtrO/OcSiy9C/7bpfU5Txah5ltUNfPkgEXg==", + "optional": true, + "requires": { + "arrify": "^2.0.0", + "extend": "^3.0.2" + } + }, + "@google-cloud/projectify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@google-cloud/projectify/-/projectify-1.0.4.tgz", + "integrity": "sha512-ZdzQUN02eRsmTKfBj9FDL0KNDIFNjBn/d6tHQmA/+FImH5DO6ZV8E7FzxMgAUiVAUq41RFAkb25p1oHOZ8psfg==", + "optional": true + }, + "@google-cloud/promisify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@google-cloud/promisify/-/promisify-1.0.4.tgz", + "integrity": "sha512-VccZDcOql77obTnFh0TbNED/6ZbbmHDf8UMNnzO1d5g9V0Htfm4k5cllY8P1tJsRKC3zWYGRLaViiupcgVjBoQ==", + "optional": true + }, + "@google-cloud/storage": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@google-cloud/storage/-/storage-4.7.0.tgz", + "integrity": "sha512-f0guAlbeg7Z0m3gKjCfBCu7FG9qS3M3oL5OQQxlvGoPtK7/qg3+W+KQV73O2/sbuS54n0Kh2mvT5K2FWzF5vVQ==", + "optional": true, + "requires": { + "@google-cloud/common": "^2.1.1", + "@google-cloud/paginator": "^2.0.0", + "@google-cloud/promisify": "^1.0.0", + "arrify": "^2.0.0", + "compressible": "^2.0.12", + "concat-stream": "^2.0.0", + "date-and-time": "^0.13.0", + "duplexify": "^3.5.0", + "extend": "^3.0.2", + "gaxios": "^3.0.0", + "gcs-resumable-upload": "^2.2.4", + "hash-stream-validation": "^0.2.2", + "mime": "^2.2.0", + "mime-types": "^2.0.8", + "onetime": "^5.1.0", + "p-limit": "^2.2.0", + "pumpify": "^2.0.0", + "readable-stream": "^3.4.0", + "snakeize": "^0.1.0", + "stream-events": "^1.0.1", + "through2": "^3.0.0", + "xdg-basedir": "^4.0.0" + } + }, + "@grpc/grpc-js": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.0.4.tgz", + "integrity": "sha512-Qawt6HUrEmljQMPWnLnIXpcjelmtIAydi3M9awiG02WWJ1CmIvFEx4IOC1EsWUWUlabOGksRbpfvoIeZKFTNXw==", + "optional": true, + "requires": { + "google-auth-library": "^6.0.0", + "semver": "^6.2.0" + }, + "dependencies": { + "google-auth-library": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-6.0.0.tgz", + "integrity": "sha512-uLydy1t6SHN/EvYUJrtN3GCHFrnJ0c8HJjOxXiGjoTuYHIoCUT3jVxnzmjHwVnSdkfE9Akasm2rM6qG1COTXfQ==", + "optional": true, + "requires": { + "arrify": "^2.0.0", + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "fast-text-encoding": "^1.0.0", + "gaxios": "^3.0.0", + "gcp-metadata": "^4.0.0", + "gtoken": "^5.0.0", + "jws": "^4.0.0", + "lru-cache": "^5.0.0" + } + } + } + }, + "@grpc/proto-loader": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.5.4.tgz", + "integrity": "sha512-HTM4QpI9B2XFkPz7pjwMyMgZchJ93TVkL3kWPW8GDMDKYxsMnmf4w2TNMJK7+KNiYHS5cJrCEAFlF+AwtXWVPA==", + "optional": true, + "requires": { + "lodash.camelcase": "^4.3.0", + "protobufjs": "^6.8.6" + } + }, + "@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha1-m4sMxmPWaafY9vXQiToU00jzD78=", + "optional": true + }, + "@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", + "optional": true + }, + "@protobufjs/codegen": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", + "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==", + "optional": true + }, + "@protobufjs/eventemitter": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", + "integrity": "sha1-NVy8mLr61ZePntCV85diHx0Ga3A=", + "optional": true + }, + "@protobufjs/fetch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", + "integrity": "sha1-upn7WYYUr2VwDBYZ/wbUVLDYTEU=", + "optional": true, + "requires": { + "@protobufjs/aspromise": "^1.1.1", + "@protobufjs/inquire": "^1.1.0" + } + }, + "@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha1-Xp4avctz/Ap8uLKR33jIy9l7h9E=", + "optional": true + }, + "@protobufjs/inquire": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", + "integrity": "sha1-/yAOPnzyQp4tyvwRQIKOjMY48Ik=", + "optional": true + }, + "@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha1-bMKyDFya1q0NzP0hynZz2Nf79o0=", + "optional": true + }, + "@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha1-Cf0V8tbTq/qbZbw2ZQbWrXhG/1Q=", + "optional": true + }, + "@protobufjs/utf8": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", + "integrity": "sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA=", + "optional": true + }, + "@tootallnate/once": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", + "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==", + "optional": true + }, + "@types/body-parser": { + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.0.tgz", + "integrity": "sha512-W98JrE0j2K78swW4ukqMleo8R7h/pFETjM2DQ90MF6XK2i4LO4W3gQ71Lt4w3bfm2EvVSyWHplECvB5sK22yFQ==", + "requires": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "@types/connect": { + "version": "3.4.33", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.33.tgz", + "integrity": "sha512-2+FrkXY4zllzTNfJth7jOqEHC+enpLeGslEhpnTAkg21GkRrWV4SsAtqchtT4YS9/nODBU2/ZfsBY2X4J/dX7A==", + "requires": { + "@types/node": "*" + } + }, + "@types/express": { + "version": "4.17.6", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.6.tgz", + "integrity": "sha512-n/mr9tZI83kd4azlPG5y997C/M4DNABK9yErhFM6hKdym4kkmd9j0vtsJyjFIwfRBxtrxZtAfGZCNRIBMFLK5w==", + "requires": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "*", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "@types/express-serve-static-core": { + "version": "4.17.7", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.7.tgz", + "integrity": "sha512-EMgTj/DF9qpgLXyc+Btimg+XoH7A2liE8uKul8qSmMTHCeNYzydDKFdsJskDvw42UsesCnhO63dO0Grbj8J4Dw==", + "requires": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*" + } + }, + "@types/fs-extra": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-8.1.1.tgz", + "integrity": "sha512-TcUlBem321DFQzBNuz8p0CLLKp0VvF/XH9E4KHNmgwyp4E3AfgI5cjiIVZWlbfThBop2qxFIh4+LeY6hVWWZ2w==", + "optional": true, + "requires": { + "@types/node": "*" + } + }, + "@types/lodash": { + "version": "4.14.152", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.152.tgz", + "integrity": "sha512-Vwf9YF2x1GE3WNeUMjT5bTHa2DqgUo87ocdgTScupY2JclZ5Nn7W2RLM/N0+oreexUk8uaVugR81NnTY/jNNXg==", + "dev": true + }, + "@types/long": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.1.tgz", + "integrity": "sha512-5tXH6Bx/kNGd3MgffdmP4dy2Z+G4eaXw0SE81Tq3BNadtnMR5/ySMzX4SLEzHJzSmPNn4HIdpQsBvXMUykr58w==", + "optional": true + }, + "@types/mime": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-2.0.2.tgz", + "integrity": "sha512-4kPlzbljFcsttWEq6aBW0OZe6BDajAmyvr2xknBG92tejQnvdGtT9+kXSZ580DqpxY9qG2xeQVF9Dq0ymUTo5Q==" + }, + "@types/node": { + "version": "8.10.61", + "resolved": "https://registry.npmjs.org/@types/node/-/node-8.10.61.tgz", + "integrity": "sha512-l+zSbvT8TPRaCxL1l9cwHCb0tSqGAGcjPJFItGGYat5oCTiq1uQQKYg5m7AF1mgnEBzFXGLJ2LRmNjtreRX76Q==" + }, + "@types/qs": { + "version": "6.9.3", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.3.tgz", + "integrity": "sha512-7s9EQWupR1fTc2pSMtXRQ9w9gLOcrJn+h7HOXw4evxyvVqMi4f+q7d2tnFe3ng3SNHjtK+0EzGMGFUQX4/AQRA==" + }, + "@types/range-parser": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.3.tgz", + "integrity": "sha512-ewFXqrQHlFsgc09MK5jP5iR7vumV/BYayNC6PgJO2LPe8vrnNFyjQjSppfEngITi0qvfKtzFvgKymGheFM9UOA==" + }, + "@types/serve-static": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.4.tgz", + "integrity": "sha512-jTDt0o/YbpNwZbQmE/+2e+lfjJEJJR0I3OFaKQKPWkASkCoW3i6fsUnqudSMcNAfbtmADGu8f4MV4q+GqULmug==", + "requires": { + "@types/express-serve-static-core": "*", + "@types/mime": "*" + } + }, + "abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "optional": true, + "requires": { + "event-target-shim": "^5.0.0" + } + }, + "accepts": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", + "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", + "requires": { + "mime-types": "~2.1.24", + "negotiator": "0.6.2" + } + }, + "agent-base": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.0.tgz", + "integrity": "sha512-j1Q7cSCqN+AwrmDd+pzgqc0/NpC655x2bUf5ZjRIO77DcNBFmh+OgRNzF6OKdCC9RSCb19fGd99+bhXFdkRNqw==", + "optional": true, + "requires": { + "debug": "4" + } + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "array-filter": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-filter/-/array-filter-1.0.0.tgz", + "integrity": "sha1-uveeYubvTCpMC4MSMtr/7CUfnYM=", + "optional": true + }, + "array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" + }, + "arrify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz", + "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==", + "optional": true + }, + "assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==" + }, + "available-typed-arrays": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.2.tgz", + "integrity": "sha512-XWX3OX8Onv97LMk/ftVyBibpGwY5a8SmuxZPzeOxqmuEqUCOM9ZE+uIaD1VNJ5QnvU2UQusvmKbuM1FR8QWGfQ==", + "optional": true, + "requires": { + "array-filter": "^1.0.0" + } + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "base64-js": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz", + "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==", + "optional": true + }, + "bignumber.js": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-7.2.1.tgz", + "integrity": "sha512-S4XzBk5sMB+Rcb/LNcpzXr57VRTxgAvaAEDAl1AwRx27j00hT84O6OkteE7u8UB3NuaaygCRrEpqox4uDOrbdQ==", + "optional": true + }, + "body-parser": { + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", + "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", + "requires": { + "bytes": "3.1.0", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "~1.1.2", + "http-errors": "1.7.2", + "iconv-lite": "0.4.24", + "on-finished": "~2.3.0", + "qs": "6.7.0", + "raw-body": "2.4.0", + "type-is": "~1.6.17" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + } + } + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=" + }, + "buffer-from": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", + "optional": true + }, + "builtin-modules": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", + "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=", + "dev": true + }, + "bytes": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", + "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==" + }, + "chai": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.2.0.tgz", + "integrity": "sha512-XQU3bhBukrOsQCuwZndwGcCVQHyZi53fQ6Ys1Fym7E4olpIqqZZhhoFJoaKVvV17lWQoXYwgWN2nF5crA8J2jw==", + "requires": { + "assertion-error": "^1.1.0", + "check-error": "^1.0.2", + "deep-eql": "^3.0.1", + "get-func-name": "^2.0.0", + "pathval": "^1.1.0", + "type-detect": "^4.0.5" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "check-error": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", + "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=" + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true + }, + "compressible": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", + "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", + "optional": true, + "requires": { + "mime-db": ">= 1.43.0 < 2" + } + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "concat-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz", + "integrity": "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==", + "optional": true, + "requires": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.0.2", + "typedarray": "^0.0.6" + } + }, + "configstore": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/configstore/-/configstore-5.0.1.tgz", + "integrity": "sha512-aMKprgk5YhBNyH25hj8wGt2+D52Sw1DRRIzqBwLp2Ya9mFmY8KPvvtvmna8SxVR9JMZ4kzMD68N22vlaRpkeFA==", + "optional": true, + "requires": { + "dot-prop": "^5.2.0", + "graceful-fs": "^4.1.2", + "make-dir": "^3.0.0", + "unique-string": "^2.0.0", + "write-file-atomic": "^3.0.0", + "xdg-basedir": "^4.0.0" + } + }, + "content-disposition": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", + "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", + "requires": { + "safe-buffer": "5.1.2" + }, + "dependencies": { + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + } + } + }, + "content-type": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" + }, + "cookie": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", + "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==" + }, + "cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", + "optional": true + }, + "cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "requires": { + "object-assign": "^4", + "vary": "^1" + } + }, + "crypto-random-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz", + "integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==", + "optional": true + }, + "date-and-time": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/date-and-time/-/date-and-time-0.13.1.tgz", + "integrity": "sha512-/Uge9DJAT+s+oAcDxtBhyR8+sKjUnZbYmyhbmWjTHNtX7B7oWD8YyYdeXcBRbwSj6hVvj+IQegJam7m7czhbFw==", + "optional": true + }, + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "optional": true, + "requires": { + "ms": "^2.1.1" + } + }, + "deep-eql": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", + "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==", + "requires": { + "type-detect": "^4.0.0" + } + }, + "deep-equal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.0.3.tgz", + "integrity": "sha512-Spqdl4H+ky45I9ByyJtXteOm9CaIrPmnIPmOhrkKGNYWeDgCvJ8jNYVCTjChxW4FqGuZnLHADc8EKRMX6+CgvA==", + "optional": true, + "requires": { + "es-abstract": "^1.17.5", + "es-get-iterator": "^1.1.0", + "is-arguments": "^1.0.4", + "is-date-object": "^1.0.2", + "is-regex": "^1.0.5", + "isarray": "^2.0.5", + "object-is": "^1.1.2", + "object-keys": "^1.1.1", + "object.assign": "^4.1.0", + "regexp.prototype.flags": "^1.3.0", + "side-channel": "^1.0.2", + "which-boxed-primitive": "^1.0.1", + "which-collection": "^1.0.1", + "which-typed-array": "^1.1.2" + } + }, + "define-properties": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", + "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "requires": { + "object-keys": "^1.0.12" + } + }, + "depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" + }, + "destroy": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", + "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" + }, + "dicer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/dicer/-/dicer-0.3.0.tgz", + "integrity": "sha512-MdceRRWqltEG2dZqO769g27N/3PXfcKl04VhYnBlo2YhH7zPi88VebsjTKclaOyiuMaGU72hTfw3VkUitGcVCA==", + "requires": { + "streamsearch": "0.1.2" + } + }, + "diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true + }, + "dot-prop": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.2.0.tgz", + "integrity": "sha512-uEUyaDKoSQ1M4Oq8l45hSE26SnTxL6snNnqvK/VWx5wJhmff5z0FUVJDKDanor/6w3kzE3i7XZOk+7wC0EXr1A==", + "optional": true, + "requires": { + "is-obj": "^2.0.0" + } + }, + "duplexify": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz", + "integrity": "sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==", + "optional": true, + "requires": { + "end-of-stream": "^1.0.0", + "inherits": "^2.0.1", + "readable-stream": "^2.0.0", + "stream-shift": "^1.0.0" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "optional": true + }, + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "optional": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "optional": true + } + } + }, + "ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" + }, + "encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" + }, + "end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "optional": true, + "requires": { + "once": "^1.4.0" + } + }, + "ent": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/ent/-/ent-2.2.0.tgz", + "integrity": "sha1-6WQhkyWiHQX0RGai9obtbOX13R0=", + "optional": true + }, + "es-abstract": { + "version": "1.17.5", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.5.tgz", + "integrity": "sha512-BR9auzDbySxOcfog0tLECW8l28eRGpDpU3Dm3Hp4q/N+VtLTmyj4EUN088XZWQDW/hzj6sYRDXeOFsaAODKvpg==", + "requires": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.1.5", + "is-regex": "^1.0.5", + "object-inspect": "^1.7.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.0", + "string.prototype.trimleft": "^2.1.1", + "string.prototype.trimright": "^2.1.1" + } + }, + "es-get-iterator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.0.tgz", + "integrity": "sha512-UfrmHuWQlNMTs35e1ypnvikg6jCz3SK8v8ImvmDsh36fCVUR1MqoFDiyn0/k52C8NqO3YsO8Oe0azeesNuqSsQ==", + "optional": true, + "requires": { + "es-abstract": "^1.17.4", + "has-symbols": "^1.0.1", + "is-arguments": "^1.0.4", + "is-map": "^2.0.1", + "is-set": "^2.0.1", + "is-string": "^1.0.5", + "isarray": "^2.0.5" + } + }, + "es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, + "escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true + }, + "etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" + }, + "event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "optional": true + }, + "express": { + "version": "4.17.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", + "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==", + "requires": { + "accepts": "~1.3.7", + "array-flatten": "1.1.1", + "body-parser": "1.19.0", + "content-disposition": "0.5.3", + "content-type": "~1.0.4", + "cookie": "0.4.0", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "~1.1.2", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "~1.1.2", + "fresh": "0.5.2", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.5", + "qs": "6.7.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.1.2", + "send": "0.17.1", + "serve-static": "1.14.1", + "setprototypeof": "1.1.1", + "statuses": "~1.5.0", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + } + } + }, + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "optional": true + }, + "fast-text-encoding": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/fast-text-encoding/-/fast-text-encoding-1.0.2.tgz", + "integrity": "sha512-5rQdinSsycpzvAoHga2EDn+LRX1d5xLFsuNG0Kg61JrAT/tASXcLL0nf/33v+sAxlQcfYmWbTURa1mmAf55jGw==", + "optional": true + }, + "faye-websocket": { + "version": "0.11.3", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.3.tgz", + "integrity": "sha512-D2y4bovYpzziGgbHYtGCMjlJM36vAl/y+xUyn1C+FVx8szd1E+86KwVw6XvYSzOP8iMpm1X0I4xJD+QtUb36OA==", + "requires": { + "websocket-driver": ">=0.5.1" + } + }, + "finalhandler": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", + "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", + "requires": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "statuses": "~1.5.0", + "unpipe": "~1.0.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + } + } + }, + "firebase-admin": { + "version": "8.12.1", + "resolved": "https://registry.npmjs.org/firebase-admin/-/firebase-admin-8.12.1.tgz", + "integrity": "sha512-DZ4Q7QQJYaO2BhnhZLrhL+mGRTCLS5WrxjbJtuKGmbKRBepwMhx++EQA5yhnGnIXgDHnp5SrZnVKygNdXtH8BQ==", + "requires": { + "@firebase/database": "^0.6.0", + "@google-cloud/firestore": "^3.0.0", + "@google-cloud/storage": "^4.1.2", + "@types/node": "^8.10.59", + "dicer": "^0.3.0", + "jsonwebtoken": "8.1.0", + "node-forge": "0.7.4" + } + }, + "firebase-functions": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/firebase-functions/-/firebase-functions-3.6.1.tgz", + "integrity": "sha512-CBvlDEoFgsdm10PTHs7gRd5xBmhp+eqCqgsyqKbzmdbU3J8RYqtBWoHm2O31gjtZv6MyOWvS3oFITShzBulylQ==", + "requires": { + "@types/express": "^4.17.3", + "cors": "^2.8.5", + "express": "^4.17.1", + "jsonwebtoken": "^8.5.1", + "lodash": "^4.17.14" + }, + "dependencies": { + "jsonwebtoken": { + "version": "8.5.1", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz", + "integrity": "sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==", + "requires": { + "jws": "^3.2.2", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^5.6.0" + } + }, + "jwa": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", + "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "requires": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "requires": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" + } + } + }, + "firebase-functions-helper": { + "version": "0.7.5", + "resolved": "https://registry.npmjs.org/firebase-functions-helper/-/firebase-functions-helper-0.7.5.tgz", + "integrity": "sha512-iZripWqrsbBeFq/UP8ycuYl7J7fuQVsjMAnvV3tahBTEPleSk44nUJDo6XRxzQos3gNvPupw2+slAXbrHVtBkg==", + "requires": { + "chai": "^4.2.0", + "firebase-admin": "^8.9.0" + } + }, + "firebase-functions-test": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/firebase-functions-test/-/firebase-functions-test-0.2.1.tgz", + "integrity": "sha512-+ZaNrDoRVy0ar4NGtrYbqVTsnitL3/Ud5yC7ElZUkX3956j+AzPCcrsCfa+5GJnpnVODXkMKpw9AySFJ/12nvA==", + "dev": true, + "requires": { + "@types/lodash": "^4.14.104", + "lodash": "^4.17.5" + } + }, + "foreach": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.5.tgz", + "integrity": "sha1-C+4AUBiusmDQo6865ljdATbsG5k=", + "optional": true + }, + "forwarded": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", + "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=" + }, + "fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + }, + "functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", + "optional": true + }, + "gaxios": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-3.0.3.tgz", + "integrity": "sha512-PkzQludeIFhd535/yucALT/Wxyj/y2zLyrMwPcJmnLHDugmV49NvAi/vb+VUq/eWztATZCNcb8ue+ywPG+oLuw==", + "optional": true, + "requires": { + "abort-controller": "^3.0.0", + "extend": "^3.0.2", + "https-proxy-agent": "^5.0.0", + "is-stream": "^2.0.0", + "node-fetch": "^2.3.0" + } + }, + "gcp-metadata": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-4.1.0.tgz", + "integrity": "sha512-r57SV28+olVsflPlKyVig3Muo/VDlcsObMtvDGOEtEJXj+DDE8bEl0coIkXh//hbkSDTvo+f5lbihZOndYXQQQ==", + "optional": true, + "requires": { + "gaxios": "^3.0.0", + "json-bigint": "^0.3.0" + } + }, + "gcs-resumable-upload": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/gcs-resumable-upload/-/gcs-resumable-upload-2.3.3.tgz", + "integrity": "sha512-sf896I5CC/1AxeaGfSFg3vKMjUq/r+A3bscmVzZm10CElyRanN0XwPu/MxeIO4LSP+9uF6yKzXvNsaTsMXUG6Q==", + "optional": true, + "requires": { + "abort-controller": "^3.0.0", + "configstore": "^5.0.0", + "gaxios": "^2.0.0", + "google-auth-library": "^5.0.0", + "pumpify": "^2.0.0", + "stream-events": "^1.0.4" + }, + "dependencies": { + "gaxios": { + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-2.3.4.tgz", + "integrity": "sha512-US8UMj8C5pRnao3Zykc4AAVr+cffoNKRTg9Rsf2GiuZCW69vgJj38VK2PzlPuQU73FZ/nTk9/Av6/JGcE1N9vA==", + "optional": true, + "requires": { + "abort-controller": "^3.0.0", + "extend": "^3.0.2", + "https-proxy-agent": "^5.0.0", + "is-stream": "^2.0.0", + "node-fetch": "^2.3.0" + } + } + } + }, + "get-func-name": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", + "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=" + }, + "glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "google-auth-library": { + "version": "5.10.1", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-5.10.1.tgz", + "integrity": "sha512-rOlaok5vlpV9rSiUu5EpR0vVpc+PhN62oF4RyX/6++DG1VsaulAFEMlDYBLjJDDPI6OcNOCGAKy9UVB/3NIDXg==", + "optional": true, + "requires": { + "arrify": "^2.0.0", + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "fast-text-encoding": "^1.0.0", + "gaxios": "^2.1.0", + "gcp-metadata": "^3.4.0", + "gtoken": "^4.1.0", + "jws": "^4.0.0", + "lru-cache": "^5.0.0" + }, + "dependencies": { + "gaxios": { + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-2.3.4.tgz", + "integrity": "sha512-US8UMj8C5pRnao3Zykc4AAVr+cffoNKRTg9Rsf2GiuZCW69vgJj38VK2PzlPuQU73FZ/nTk9/Av6/JGcE1N9vA==", + "optional": true, + "requires": { + "abort-controller": "^3.0.0", + "extend": "^3.0.2", + "https-proxy-agent": "^5.0.0", + "is-stream": "^2.0.0", + "node-fetch": "^2.3.0" + } + }, + "gcp-metadata": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-3.5.0.tgz", + "integrity": "sha512-ZQf+DLZ5aKcRpLzYUyBS3yo3N0JSa82lNDO8rj3nMSlovLcz2riKFBsYgDzeXcv75oo5eqB2lx+B14UvPoCRnA==", + "optional": true, + "requires": { + "gaxios": "^2.1.0", + "json-bigint": "^0.3.0" + } + }, + "google-p12-pem": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-2.0.4.tgz", + "integrity": "sha512-S4blHBQWZRnEW44OcR7TL9WR+QCqByRvhNDZ/uuQfpxywfupikf/miba8js1jZi6ZOGv5slgSuoshCWh6EMDzg==", + "optional": true, + "requires": { + "node-forge": "^0.9.0" + } + }, + "gtoken": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-4.1.4.tgz", + "integrity": "sha512-VxirzD0SWoFUo5p8RDP8Jt2AGyOmyYcT/pOUgDKJCK+iSw0TMqwrVfY37RXTNmoKwrzmDHSk0GMT9FsgVmnVSA==", + "optional": true, + "requires": { + "gaxios": "^2.1.0", + "google-p12-pem": "^2.0.0", + "jws": "^4.0.0", + "mime": "^2.2.0" + } + }, + "node-forge": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.9.1.tgz", + "integrity": "sha512-G6RlQt5Sb4GMBzXvhfkeFmbqR6MzhtnT7VTHuLadjkii3rdYHNdw0m8zA4BTxVIh68FicCQ2NSUANpsqkr9jvQ==", + "optional": true + } + } + }, + "google-gax": { + "version": "1.15.3", + "resolved": "https://registry.npmjs.org/google-gax/-/google-gax-1.15.3.tgz", + "integrity": "sha512-3JKJCRumNm3x2EksUTw4P1Rad43FTpqrtW9jzpf3xSMYXx+ogaqTM1vGo7VixHB4xkAyATXVIa3OcNSh8H9zsQ==", + "optional": true, + "requires": { + "@grpc/grpc-js": "~1.0.3", + "@grpc/proto-loader": "^0.5.1", + "@types/fs-extra": "^8.0.1", + "@types/long": "^4.0.0", + "abort-controller": "^3.0.0", + "duplexify": "^3.6.0", + "google-auth-library": "^5.0.0", + "is-stream-ended": "^0.1.4", + "lodash.at": "^4.6.0", + "lodash.has": "^4.5.2", + "node-fetch": "^2.6.0", + "protobufjs": "^6.8.9", + "retry-request": "^4.0.0", + "semver": "^6.0.0", + "walkdir": "^0.4.0" + } + }, + "google-p12-pem": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-3.0.1.tgz", + "integrity": "sha512-VlQgtozgNVVVcYTXS36eQz4PXPt9gIPqLOhHN0QiV6W6h4qSCNVKPtKC5INtJsaHHF2r7+nOIa26MJeJMTaZEQ==", + "optional": true, + "requires": { + "node-forge": "^0.9.0" + }, + "dependencies": { + "node-forge": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.9.1.tgz", + "integrity": "sha512-G6RlQt5Sb4GMBzXvhfkeFmbqR6MzhtnT7VTHuLadjkii3rdYHNdw0m8zA4BTxVIh68FicCQ2NSUANpsqkr9jvQ==", + "optional": true + } + } + }, + "graceful-fs": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", + "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", + "optional": true + }, + "gtoken": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-5.0.1.tgz", + "integrity": "sha512-33w4FNDkUcyIOq/TqyC+drnKdI4PdXmWp9lZzssyEQKuvu9ZFN3KttaSnDKo52U3E51oujVGop93mKxmqO8HHg==", + "optional": true, + "requires": { + "gaxios": "^3.0.0", + "google-p12-pem": "^3.0.0", + "jws": "^4.0.0", + "mime": "^2.2.0" + } + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "has-symbols": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", + "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==" + }, + "hash-stream-validation": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/hash-stream-validation/-/hash-stream-validation-0.2.3.tgz", + "integrity": "sha512-OEohGLoUOh+bwsIpHpdvhIXFyRGjeLqJbT8Yc5QTZPbRM7LKywagTQxnX/6mghLDOrD9YGz88hy5mLN2eKflYQ==", + "optional": true, + "requires": { + "through2": "^2.0.0" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "optional": true + }, + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "optional": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "optional": true + }, + "through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "optional": true, + "requires": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + } + } + }, + "http-errors": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", + "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.1", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.0" + }, + "dependencies": { + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + } + } + }, + "http-parser-js": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.2.tgz", + "integrity": "sha512-opCO9ASqg5Wy2FNo7A0sxy71yGbbkJJXLdgMK04Tcypw9jr2MgWbyubb0+WdmDmGnFflO7fRbqbaihh/ENDlRQ==" + }, + "http-proxy-agent": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", + "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==", + "optional": true, + "requires": { + "@tootallnate/once": "1", + "agent-base": "6", + "debug": "4" + } + }, + "https-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", + "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==", + "optional": true, + "requires": { + "agent-base": "6", + "debug": "4" + } + }, + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "optional": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" + }, + "is-arguments": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.0.4.tgz", + "integrity": "sha512-xPh0Rmt8NE65sNzvyUmWgI1tz3mKq74lGA0mL8LYZcoIzKOzDh6HmrYm3d18k60nHerC8A9Km8kYu87zfSFnLA==", + "optional": true + }, + "is-bigint": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.0.tgz", + "integrity": "sha512-t5mGUXC/xRheCK431ylNiSkGGpBp8bHENBcENTkDT6ppwPzEVxNGZRvgvmOEfbWkFhA7D2GEuE2mmQTr78sl2g==", + "optional": true + }, + "is-boolean-object": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.0.1.tgz", + "integrity": "sha512-TqZuVwa/sppcrhUCAYkGBk7w0yxfQQnxq28fjkO53tnK9FQXmdwz2JS5+GjsWQ6RByES1K40nI+yDic5c9/aAQ==", + "optional": true + }, + "is-callable": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.5.tgz", + "integrity": "sha512-ESKv5sMCJB2jnHTWZ3O5itG+O128Hsus4K4Qh1h2/cgn2vbgnLSVqfV46AeJA9D5EeeLa9w81KUXMtn34zhX+Q==" + }, + "is-date-object": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz", + "integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==" + }, + "is-map": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.1.tgz", + "integrity": "sha512-T/S49scO8plUiAOA2DBTBG3JHpn1yiw0kRp6dgiZ0v2/6twi5eiB0rHtHFH9ZIrvlWc6+4O+m4zg5+Z833aXgw==", + "optional": true + }, + "is-number-object": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.4.tgz", + "integrity": "sha512-zohwelOAur+5uXtk8O3GPQ1eAcu4ZX3UwxQhUlfFFMNpUd83gXgjbhJh6HmB6LUNV/ieOLQuDwJO3dWJosUeMw==", + "optional": true + }, + "is-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", + "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", + "optional": true + }, + "is-regex": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.5.tgz", + "integrity": "sha512-vlKW17SNq44owv5AQR3Cq0bQPEb8+kF3UKZ2fiZNOWtztYE5i0CzCZxFDwO58qAOWtxdBRVO/V5Qin1wjCqFYQ==", + "requires": { + "has": "^1.0.3" + } + }, + "is-set": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.1.tgz", + "integrity": "sha512-eJEzOtVyenDs1TMzSQ3kU3K+E0GUS9sno+F0OBT97xsgcJsF9nXMBtkT9/kut5JEpM7oL7X/0qxR17K3mcwIAA==", + "optional": true + }, + "is-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", + "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==", + "optional": true + }, + "is-stream-ended": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-stream-ended/-/is-stream-ended-0.1.4.tgz", + "integrity": "sha512-xj0XPvmr7bQFTvirqnFr50o0hQIh6ZItDqloxt5aJrR4NQsYeSsyFQERYGCAzfindAcnKjINnwEEgLx4IqVzQw==", + "optional": true + }, + "is-string": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.5.tgz", + "integrity": "sha512-buY6VNRjhQMiF1qWDouloZlQbRhDPCebwxSjxMjxgemYT46YMd2NR0/H+fBhEfWX4A/w9TBJ+ol+okqJKFE6vQ==", + "optional": true + }, + "is-symbol": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz", + "integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==", + "requires": { + "has-symbols": "^1.0.1" + } + }, + "is-typed-array": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.3.tgz", + "integrity": "sha512-BSYUBOK/HJibQ30wWkWold5txYwMUXQct9YHAQJr8fSwvZoiglcqB0pd7vEN23+Tsi9IUEjztdOSzl4qLVYGTQ==", + "optional": true, + "requires": { + "available-typed-arrays": "^1.0.0", + "es-abstract": "^1.17.4", + "foreach": "^2.0.5", + "has-symbols": "^1.0.1" + } + }, + "is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", + "optional": true + }, + "is-weakmap": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.1.tgz", + "integrity": "sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA==", + "optional": true + }, + "is-weakset": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.1.tgz", + "integrity": "sha512-pi4vhbhVHGLxohUw7PhGsueT4vRGFoXhP7+RGN0jKIv9+8PWYCQTqtADngrxOm2g46hoH0+g8uZZBzMrvVGDmw==", + "optional": true + }, + "isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "optional": true + }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "js-yaml": { + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.0.tgz", + "integrity": "sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "json-bigint": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-0.3.0.tgz", + "integrity": "sha1-DM2RLEuCcNBfBW+9E4FLU9OCWx4=", + "optional": true, + "requires": { + "bignumber.js": "^7.0.0" + } + }, + "jsonwebtoken": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.1.0.tgz", + "integrity": "sha1-xjl80uX9WD1lwAeoPce7eOaYK4M=", + "requires": { + "jws": "^3.1.4", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.0.0", + "xtend": "^4.0.1" + }, + "dependencies": { + "jwa": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", + "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "requires": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "requires": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + } + } + }, + "jwa": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", + "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", + "optional": true, + "requires": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "jws": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz", + "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==", + "optional": true, + "requires": { + "jwa": "^2.0.0", + "safe-buffer": "^5.0.1" + } + }, + "lodash": { + "version": "4.17.15", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", + "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==" + }, + "lodash.at": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.at/-/lodash.at-4.6.0.tgz", + "integrity": "sha1-k83OZk8KGZTqM9181A4jr9EbD/g=", + "optional": true + }, + "lodash.camelcase": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha1-soqmKIorn8ZRA1x3EfZathkDMaY=", + "optional": true + }, + "lodash.has": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/lodash.has/-/lodash.has-4.5.2.tgz", + "integrity": "sha1-0Z9NwQlQWMzL4rDN9O4P5Ko3yGI=", + "optional": true + }, + "lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8=" + }, + "lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY=" + }, + "lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha1-YZwK89A/iwTDH1iChAt3sRzWg0M=" + }, + "lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w=" + }, + "lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=" + }, + "lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=" + }, + "lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=" + }, + "long": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", + "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==", + "optional": true + }, + "lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "optional": true, + "requires": { + "yallist": "^3.0.2" + } + }, + "make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "optional": true, + "requires": { + "semver": "^6.0.0" + } + }, + "media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" + }, + "merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" + }, + "methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" + }, + "mime": { + "version": "2.4.5", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.5.tgz", + "integrity": "sha512-3hQhEUF027BuxZjQA3s7rIv/7VCQPa27hN9u9g87sEkWaKwQPuXOkVKtOeiyUrnWqTDiOs8Ed2rwg733mB0R5w==", + "optional": true + }, + "mime-db": { + "version": "1.44.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz", + "integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==" + }, + "mime-types": { + "version": "2.1.27", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz", + "integrity": "sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==", + "requires": { + "mime-db": "1.44.0" + } + }, + "mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "optional": true + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", + "dev": true + }, + "mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "dev": true, + "requires": { + "minimist": "^1.2.5" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "negotiator": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", + "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==" + }, + "node-fetch": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.0.tgz", + "integrity": "sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA==", + "optional": true + }, + "node-forge": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.7.4.tgz", + "integrity": "sha512-8Df0906+tq/omxuCZD6PqhPaQDYuyJ1d+VITgxoIA8zvQd1ru+nMJcDChHH324MWitIgbVkAkQoGEEVJNpn/PA==" + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" + }, + "object-inspect": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.7.0.tgz", + "integrity": "sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw==" + }, + "object-is": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.2.tgz", + "integrity": "sha512-5lHCz+0uufF6wZ7CRFWJN3hp8Jqblpgve06U5CMQ3f//6iDjPr2PEo9MWCjEssDsa+UZEL4PkFpr+BMop6aKzQ==", + "optional": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + } + }, + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==" + }, + "object.assign": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", + "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", + "requires": { + "define-properties": "^1.1.2", + "function-bind": "^1.1.1", + "has-symbols": "^1.0.0", + "object-keys": "^1.0.11" + } + }, + "on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "requires": { + "ee-first": "1.1.1" + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "requires": { + "wrappy": "1" + } + }, + "onetime": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.0.tgz", + "integrity": "sha512-5NcSkPHhwTVFIQN+TUqXoS5+dlElHXdpAWu9I0HP20YOtIi+aZ0Ct82jdlILDxjLEAWwvm+qj1m6aEtsDVmm6Q==", + "optional": true, + "requires": { + "mimic-fn": "^2.1.0" + } + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "optional": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "optional": true + }, + "parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "path-parse": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", + "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", + "dev": true + }, + "path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" + }, + "pathval": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.0.tgz", + "integrity": "sha1-uULm1L3mUwBe9rcTYd74cn0GReA=" + }, + "process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "optional": true + }, + "protobufjs": { + "version": "6.9.0", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.9.0.tgz", + "integrity": "sha512-LlGVfEWDXoI/STstRDdZZKb/qusoAWUnmLg9R8OLSO473mBLWHowx8clbX5/+mKDEI+v7GzjoK9tRPZMMcoTrg==", + "optional": true, + "requires": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/long": "^4.0.1", + "@types/node": "^13.7.0", + "long": "^4.0.0" + }, + "dependencies": { + "@types/node": { + "version": "13.13.9", + "resolved": "https://registry.npmjs.org/@types/node/-/node-13.13.9.tgz", + "integrity": "sha512-EPZBIGed5gNnfWCiwEIwTE2Jdg4813odnG8iNPMQGrqVxrI+wL68SPtPeCX+ZxGBaA6pKAVc6jaKgP/Q0QzfdQ==", + "optional": true + } + } + }, + "proxy-addr": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.6.tgz", + "integrity": "sha512-dh/frvCBVmSsDYzw6n926jv974gddhkFPfiN8hPOi30Wax25QZyZEGveluCgliBnqmuM+UJmBErbAUFIoDbjOw==", + "requires": { + "forwarded": "~0.1.2", + "ipaddr.js": "1.9.1" + } + }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "optional": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "pumpify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-2.0.1.tgz", + "integrity": "sha512-m7KOje7jZxrmutanlkS1daj1dS6z6BgslzOXmcSEpIlCxM3VJH7lG5QLeck/6hgF6F4crFf01UtQmNsJfweTAw==", + "optional": true, + "requires": { + "duplexify": "^4.1.1", + "inherits": "^2.0.3", + "pump": "^3.0.0" + }, + "dependencies": { + "duplexify": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.1.tgz", + "integrity": "sha512-DY3xVEmVHTv1wSzKNbwoU6nVjzI369Y6sPoqfYr0/xlx3IdX2n94xIszTcjPO8W8ZIv0Wb0PXNcjuZyT4wiICA==", + "optional": true, + "requires": { + "end-of-stream": "^1.4.1", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1", + "stream-shift": "^1.0.0" + } + } + } + }, + "qs": { + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", + "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==" + }, + "range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" + }, + "raw-body": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", + "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", + "requires": { + "bytes": "3.1.0", + "http-errors": "1.7.2", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + } + }, + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "optional": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "regexp.prototype.flags": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.3.0.tgz", + "integrity": "sha512-2+Q0C5g951OlYlJz6yu5/M33IcsESLlLfsyIaLJaG4FA2r4yP8MvVMJUUP/fVBkSpbbbZlS5gynbEWLipiiXiQ==", + "optional": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0-next.1" + } + }, + "resolve": { + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz", + "integrity": "sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==", + "dev": true, + "requires": { + "path-parse": "^1.0.6" + } + }, + "retry-request": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/retry-request/-/retry-request-4.1.1.tgz", + "integrity": "sha512-BINDzVtLI2BDukjWmjAIRZ0oglnCAkpP2vQjM3jdLhmT62h0xnQgciPwBRDAvHqpkPT2Wo1XuUyLyn6nbGrZQQ==", + "optional": true, + "requires": { + "debug": "^4.1.1", + "through2": "^3.0.1" + } + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "optional": true + }, + "send": { + "version": "0.17.1", + "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", + "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==", + "requires": { + "debug": "2.6.9", + "depd": "~1.1.2", + "destroy": "~1.0.4", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "~1.7.2", + "mime": "1.6.0", + "ms": "2.1.1", + "on-finished": "~2.3.0", + "range-parser": "~1.2.1", + "statuses": "~1.5.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + }, + "dependencies": { + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + } + } + }, + "mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" + } + } + }, + "serve-static": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz", + "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==", + "requires": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.17.1" + } + }, + "setprototypeof": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", + "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" + }, + "side-channel": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.2.tgz", + "integrity": "sha512-7rL9YlPHg7Ancea1S96Pa8/QWb4BtXL/TZvS6B8XFetGBeuhAsfmUspK6DokBeZ64+Kj9TCNRD/30pVz1BvQNA==", + "optional": true, + "requires": { + "es-abstract": "^1.17.0-next.1", + "object-inspect": "^1.7.0" + } + }, + "signal-exit": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", + "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==", + "optional": true + }, + "snakeize": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/snakeize/-/snakeize-0.1.0.tgz", + "integrity": "sha1-EMCI2LWOsHazIpu1oE4jLOEmQi0=", + "optional": true + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, + "statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" + }, + "stream-events": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/stream-events/-/stream-events-1.0.5.tgz", + "integrity": "sha512-E1GUzBSgvct8Jsb3v2X15pjzN1tYebtbLaMg+eBOUOAxgbLoSbT2NS91ckc5lJD1KfLjId+jXJRgo0qnV5Nerg==", + "optional": true, + "requires": { + "stubs": "^3.0.0" + } + }, + "stream-shift": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz", + "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==", + "optional": true + }, + "streamsearch": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-0.1.2.tgz", + "integrity": "sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo=" + }, + "string.prototype.trimend": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.1.tgz", + "integrity": "sha512-LRPxFUaTtpqYsTeNKaFOw3R4bxIzWOnbQ837QfBylo8jIxtcbK/A/sMV7Q+OAV/vWo+7s25pOE10KYSjaSO06g==", + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + } + }, + "string.prototype.trimleft": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/string.prototype.trimleft/-/string.prototype.trimleft-2.1.2.tgz", + "integrity": "sha512-gCA0tza1JBvqr3bfAIFJGqfdRTyPae82+KTnm3coDXkZN9wnuW3HjGgN386D7hfv5CHQYCI022/rJPVlqXyHSw==", + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5", + "string.prototype.trimstart": "^1.0.0" + } + }, + "string.prototype.trimright": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/string.prototype.trimright/-/string.prototype.trimright-2.1.2.tgz", + "integrity": "sha512-ZNRQ7sY3KroTaYjRS6EbNiiHrOkjihL9aQE/8gfQ4DtAC/aEBRHFJa44OmoWxGGqXuJlfKkZW4WcXErGr+9ZFg==", + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5", + "string.prototype.trimend": "^1.0.0" + } + }, + "string.prototype.trimstart": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.1.tgz", + "integrity": "sha512-XxZn+QpvrBI1FOcg6dIpxUPgWCPuNXvMD72aaRaUQv1eD4e/Qy8i/hFTe0BUmD60p/QA6bh1avmuPTfNjqVWRw==", + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "optional": true, + "requires": { + "safe-buffer": "~5.1.0" + }, + "dependencies": { + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "optional": true + } + } + }, + "stubs": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/stubs/-/stubs-3.0.0.tgz", + "integrity": "sha1-6NK6H6nJBXAwPAMLaQD31fiavls=", + "optional": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + }, + "teeny-request": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-6.0.3.tgz", + "integrity": "sha512-TZG/dfd2r6yeji19es1cUIwAlVD8y+/svB1kAC2Y0bjEyysrfbO8EZvJBRwIE6WkwmUoB7uvWLwTIhJbMXZ1Dw==", + "optional": true, + "requires": { + "http-proxy-agent": "^4.0.0", + "https-proxy-agent": "^5.0.0", + "node-fetch": "^2.2.0", + "stream-events": "^1.0.5", + "uuid": "^7.0.0" + } + }, + "through2": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/through2/-/through2-3.0.1.tgz", + "integrity": "sha512-M96dvTalPT3YbYLaKaCuwu+j06D/8Jfib0o/PxbVt6Amhv3dUAtW6rTV1jPgJSBG83I/e04Y6xkVdVhSRhi0ww==", + "optional": true, + "requires": { + "readable-stream": "2 || 3" + } + }, + "toidentifier": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", + "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==" + }, + "tslib": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.11.1.tgz", + "integrity": "sha512-aZW88SY8kQbU7gpV19lN24LtXh/yD4ZZg6qieAJDDg+YBsJcSmLGK9QpnUjAKVG/xefmvJGd1WUmfpT/g6AJGA==" + }, + "tslint": { + "version": "5.20.1", + "resolved": "https://registry.npmjs.org/tslint/-/tslint-5.20.1.tgz", + "integrity": "sha512-EcMxhzCFt8k+/UP5r8waCf/lzmeSyVlqxqMEDQE7rWYiQky8KpIBz1JAoYXfROHrPZ1XXd43q8yQnULOLiBRQg==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "builtin-modules": "^1.1.1", + "chalk": "^2.3.0", + "commander": "^2.12.1", + "diff": "^4.0.1", + "glob": "^7.1.1", + "js-yaml": "^3.13.1", + "minimatch": "^3.0.4", + "mkdirp": "^0.5.1", + "resolve": "^1.3.2", + "semver": "^5.3.0", + "tslib": "^1.8.0", + "tsutils": "^2.29.0" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + } + } + }, + "tsutils": { + "version": "2.29.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.29.0.tgz", + "integrity": "sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA==", + "dev": true, + "requires": { + "tslib": "^1.8.1" + } + }, + "type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==" + }, + "type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "requires": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + } + }, + "typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", + "optional": true + }, + "typedarray-to-buffer": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", + "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", + "optional": true, + "requires": { + "is-typedarray": "^1.0.0" + } + }, + "typescript": { + "version": "3.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.3.tgz", + "integrity": "sha512-D/wqnB2xzNFIcoBG9FG8cXRDjiqSTbG2wd8DMZeQyJlP1vfTkIxH4GKveWaEBYySKIg+USu+E+EDIR47SqnaMQ==", + "dev": true + }, + "unique-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz", + "integrity": "sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==", + "optional": true, + "requires": { + "crypto-random-string": "^2.0.0" + } + }, + "unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "optional": true + }, + "utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" + }, + "uuid": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-7.0.3.tgz", + "integrity": "sha512-DPSke0pXhTZgoF/d+WSt2QaKMCFSfx7QegxEWT+JOuHF5aWrKEn0G+ztjuJg/gG8/ItK+rbPCD/yNv8yyih6Cg==", + "optional": true + }, + "vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" + }, + "walkdir": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/walkdir/-/walkdir-0.4.1.tgz", + "integrity": "sha512-3eBwRyEln6E1MSzcxcVpQIhRG8Q1jLvEqRmCZqS3dsfXEDR/AhOF4d+jHg1qvDCpYaVRZjENPQyrVxAkQqxPgQ==", + "optional": true + }, + "websocket-driver": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", + "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", + "requires": { + "http-parser-js": ">=0.5.1", + "safe-buffer": ">=5.1.0", + "websocket-extensions": ">=0.1.1" + } + }, + "websocket-extensions": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.3.tgz", + "integrity": "sha512-nqHUnMXmBzT0w570r2JpJxfiSD1IzoI+HGVdd3aZ0yNi3ngvQ4jv1dtHt5VGxfI2yj5yqImPhOK4vmIh2xMbGg==" + }, + "which-boxed-primitive": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.1.tgz", + "integrity": "sha512-7BT4TwISdDGBgaemWU0N0OU7FeAEJ9Oo2P1PHRm/FCWoEi2VLWC9b6xvxAA3C/NMpxg3HXVgi0sMmGbNUbNepQ==", + "optional": true, + "requires": { + "is-bigint": "^1.0.0", + "is-boolean-object": "^1.0.0", + "is-number-object": "^1.0.3", + "is-string": "^1.0.4", + "is-symbol": "^1.0.2" + } + }, + "which-collection": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.1.tgz", + "integrity": "sha512-W8xeTUwaln8i3K/cY1nGXzdnVZlidBcagyNFtBdD5kxnb4TvGKR7FfSIS3mYpwWS1QUCutfKz8IY8RjftB0+1A==", + "optional": true, + "requires": { + "is-map": "^2.0.1", + "is-set": "^2.0.1", + "is-weakmap": "^2.0.1", + "is-weakset": "^2.0.1" + } + }, + "which-typed-array": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.2.tgz", + "integrity": "sha512-KT6okrd1tE6JdZAy3o2VhMoYPh3+J6EMZLyrxBQsZflI1QCZIxMrIYLkosd8Twf+YfknVIHmYQPgJt238p8dnQ==", + "optional": true, + "requires": { + "available-typed-arrays": "^1.0.2", + "es-abstract": "^1.17.5", + "foreach": "^2.0.5", + "function-bind": "^1.1.1", + "has-symbols": "^1.0.1", + "is-typed-array": "^1.1.3" + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + }, + "write-file-atomic": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", + "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", + "optional": true, + "requires": { + "imurmurhash": "^0.1.4", + "is-typedarray": "^1.0.0", + "signal-exit": "^3.0.2", + "typedarray-to-buffer": "^3.1.5" + } + }, + "xdg-basedir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-4.0.0.tgz", + "integrity": "sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==", + "optional": true + }, + "xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==" + }, + "yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "optional": true + } + } +} diff --git a/functions/package.json b/functions/package.json new file mode 100644 index 0000000..53ca40c --- /dev/null +++ b/functions/package.json @@ -0,0 +1,29 @@ +{ + "name": "functions", + "scripts": { + "lint": "tslint --project tsconfig.json", + "build": "tsc", + "serve": "npm run build && firebase emulators:start --only functions", + "shell": "npm run build && firebase functions:shell", + "start": "npm run shell", + "deploy": "firebase deploy --only functions", + "logs": "firebase functions:log" + }, + "engines": { + "node": "8" + }, + "main": "lib/index.js", + "dependencies": { + "body-parser": "^1.19.0", + "express": "^4.17.1", + "firebase-admin": "^8.10.0", + "firebase-functions": "^3.6.1", + "firebase-functions-helper": "^0.7.5" + }, + "devDependencies": { + "tslint": "^5.12.0", + "typescript": "^3.8.0", + "firebase-functions-test": "^0.2.0" + }, + "private": true +} diff --git a/functions/src/index.ts b/functions/src/index.ts new file mode 100644 index 0000000..699d89a --- /dev/null +++ b/functions/src/index.ts @@ -0,0 +1,54 @@ +import * as functions from 'firebase-functions'; +import * as admin from 'firebase-admin'; +import * as firebaseHelper from 'firebase-functions-helper'; +import * as express from 'express'; +import * as bodyParser from 'body-parser'; + +admin.initializeApp(functions.config().firebase); + +const db = admin.firestore(); +const TIME_INTERVAL = 15 * 60 * 1000; +const NEWS_URL = 'https://api.newsriver.io/v2/'; + +const app = express(); +const main = express(); + +const jsonCollectionName = 'news'; + +main.use('/api/v1', app); +main.use(bodyParser.json()); +main.use(bodyParser.urlencoded({ extended: false })); + +export const webApi = functions.https.onRequest(main); + +app.get('/', async (req, res) => { + try { + const language = req.body['lang']; + const doc = await firebaseHelper.firestore.createNewDocument(db, jsonCollectionName, {'test': 'test'}); + res.status(201).send(`Created new contact: ${doc.id}`); + } catch (error) { + res.status(400).send('Data must contain langauge'); + } +}); + +const timerId = setInterval(() => { + const httpRequest = new XMLHttpRequest(); + + httpRequest.open('GET', NEWS_URL); + httpRequest.send(); + + httpRequest.onreadystatechange = (e) => { + try { + const response = JSON.parse(httpRequest.responseText); + response + } + } +}, TIME_INTERVAL); + +// // Start writing Firebase Functions +// // https://firebase.google.com/docs/functions/typescript +// +// export const helloWorld = functions.https.onRequest((request, response) => { +// response.send("Hello from Firebase!"); +// }); + diff --git a/functions/tsconfig.json b/functions/tsconfig.json new file mode 100644 index 0000000..7ce05d0 --- /dev/null +++ b/functions/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "module": "commonjs", + "noImplicitReturns": true, + "noUnusedLocals": true, + "outDir": "lib", + "sourceMap": true, + "strict": true, + "target": "es2017" + }, + "compileOnSave": true, + "include": [ + "src" + ] +} diff --git a/functions/tslint.json b/functions/tslint.json new file mode 100644 index 0000000..98b2bfd --- /dev/null +++ b/functions/tslint.json @@ -0,0 +1,115 @@ +{ + "rules": { + // -- Strict errors -- + // These lint rules are likely always a good idea. + + // Force function overloads to be declared together. This ensures readers understand APIs. + "adjacent-overload-signatures": true, + + // Do not allow the subtle/obscure comma operator. + "ban-comma-operator": true, + + // Do not allow internal modules or namespaces . These are deprecated in favor of ES6 modules. + "no-namespace": true, + + // Do not allow parameters to be reassigned. To avoid bugs, developers should instead assign new values to new vars. + "no-parameter-reassignment": true, + + // Force the use of ES6-style imports instead of /// imports. + "no-reference": true, + + // Do not allow type assertions that do nothing. This is a big warning that the developer may not understand the + // code currently being edited (they may be incorrectly handling a different type case that does not exist). + "no-unnecessary-type-assertion": true, + + // Disallow nonsensical label usage. + "label-position": true, + + // Disallows the (often typo) syntax if (var1 = var2). Replace with if (var2) { var1 = var2 }. + "no-conditional-assignment": true, + + // Disallows constructors for primitive types (e.g. new Number('123'), though Number('123') is still allowed). + "no-construct": true, + + // Do not allow super() to be called twice in a constructor. + "no-duplicate-super": true, + + // Do not allow the same case to appear more than once in a switch block. + "no-duplicate-switch-case": true, + + // Do not allow a variable to be declared more than once in the same block. Consider function parameters in this + // rule. + "no-duplicate-variable": [true, "check-parameters"], + + // Disallows a variable definition in an inner scope from shadowing a variable in an outer scope. Developers should + // instead use a separate variable name. + "no-shadowed-variable": true, + + // Empty blocks are almost never needed. Allow the one general exception: empty catch blocks. + "no-empty": [true, "allow-empty-catch"], + + // Functions must either be handled directly (e.g. with a catch() handler) or returned to another function. + // This is a major source of errors in Cloud Functions and the team strongly recommends leaving this rule on. + "no-floating-promises": true, + + // Do not allow any imports for modules that are not in package.json. These will almost certainly fail when + // deployed. + "no-implicit-dependencies": true, + + // The 'this' keyword can only be used inside of classes. + "no-invalid-this": true, + + // Do not allow strings to be thrown because they will not include stack traces. Throw Errors instead. + "no-string-throw": true, + + // Disallow control flow statements, such as return, continue, break, and throw in finally blocks. + "no-unsafe-finally": true, + + // Expressions must always return a value. Avoids common errors like const myValue = functionReturningVoid(); + "no-void-expression": [true, "ignore-arrow-function-shorthand"], + + // Disallow duplicate imports in the same file. + "no-duplicate-imports": true, + + + // -- Strong Warnings -- + // These rules should almost never be needed, but may be included due to legacy code. + // They are left as a warning to avoid frustration with blocked deploys when the developer + // understand the warning and wants to deploy anyway. + + // Warn when an empty interface is defined. These are generally not useful. + "no-empty-interface": {"severity": "warning"}, + + // Warn when an import will have side effects. + "no-import-side-effect": {"severity": "warning"}, + + // Warn when variables are defined with var. Var has subtle meaning that can lead to bugs. Strongly prefer const for + // most values and let for values that will change. + "no-var-keyword": {"severity": "warning"}, + + // Prefer === and !== over == and !=. The latter operators support overloads that are often accidental. + "triple-equals": {"severity": "warning"}, + + // Warn when using deprecated APIs. + "deprecation": {"severity": "warning"}, + + // -- Light Warnings -- + // These rules are intended to help developers use better style. Simpler code has fewer bugs. These would be "info" + // if TSLint supported such a level. + + // prefer for( ... of ... ) to an index loop when the index is only used to fetch an object from an array. + // (Even better: check out utils like .map if transforming an array!) + "prefer-for-of": {"severity": "warning"}, + + // Warns if function overloads could be unified into a single function with optional or rest parameters. + "unified-signatures": {"severity": "warning"}, + + // Prefer const for values that will not change. This better documents code. + "prefer-const": {"severity": "warning"}, + + // Multi-line object literals and function calls should have a trailing comma. This helps avoid merge conflicts. + "trailing-comma": {"severity": "warning"} + }, + + "defaultSeverity": "error" +} From f0a3659bc27e78b8fff9f7b0b85144c13f42cfda Mon Sep 17 00:00:00 2001 From: Javinator9889 Date: Mon, 25 May 2020 11:10:13 +0200 Subject: [PATCH 02/95] Included firebase project files --- .firebaserc | 5 +++++ firebase.json | 16 ++++++++++++++++ 2 files changed, 21 insertions(+) create mode 100644 .firebaserc create mode 100644 firebase.json diff --git a/.firebaserc b/.firebaserc new file mode 100644 index 0000000..f92f8a3 --- /dev/null +++ b/.firebaserc @@ -0,0 +1,5 @@ +{ + "projects": { + "default": "handwashing" + } +} diff --git a/firebase.json b/firebase.json new file mode 100644 index 0000000..9c5b99f --- /dev/null +++ b/firebase.json @@ -0,0 +1,16 @@ +{ + "functions": { + "predeploy": [ + "npm --prefix \"$RESOURCE_DIR\" run lint", + "npm --prefix \"$RESOURCE_DIR\" run build" + ] + }, + "hosting": { + "public": "public", + "ignore": [ + "firebase.json", + "**/.*", + "**/node_modules/**" + ] + } +} From 0f7b492b51c82d72ca95fc7e7143a411eaa78465 Mon Sep 17 00:00:00 2001 From: Javinator9889 Date: Mon, 25 May 2020 13:40:00 +0200 Subject: [PATCH 03/95] Created rcdata (RemoteConfigData) for obtaining search terms by using remote config --- functions/src/rcdata.ts | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 functions/src/rcdata.ts diff --git a/functions/src/rcdata.ts b/functions/src/rcdata.ts new file mode 100644 index 0000000..5442063 --- /dev/null +++ b/functions/src/rcdata.ts @@ -0,0 +1,24 @@ +import * as admin from 'firebase-admin'; + +export class RemoteConfigData { + remoteConfig: admin.remoteConfig.RemoteConfig; + + constructor(app: admin.app.App) { + this.remoteConfig = admin.remoteConfig(app); + } + + getSearchTermsForLanguage(language: string): Promise> { + return new Promise>(resolve => { + this.remoteConfig.getTemplate() + .then(template => { + let condition: string; + switch (language) { + case 'es': condition = 'Spanish users'; break; + default: condition = 'Default language users'; break; + } + const values = JSON.parse(template.parameters['search_terms'].conditionalValues[condition]['value']); + resolve(values); + }); + }); + } +} From 0bcec2b5bd08ff90951b3642ffab5315b70c6cf1 Mon Sep 17 00:00:00 2001 From: Javinator9889 Date: Mon, 25 May 2020 14:07:51 +0200 Subject: [PATCH 04/95] Created updater function for publishing data to Firestore --- functions/src/updater.ts | 69 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 functions/src/updater.ts diff --git a/functions/src/updater.ts b/functions/src/updater.ts new file mode 100644 index 0000000..7e87b4d --- /dev/null +++ b/functions/src/updater.ts @@ -0,0 +1,69 @@ +import { NewsriverData } from "./newsriver"; +import firebaseHelper = require("firebase-functions-helper"); + +class Updater { + db: FirebaseFirestore.Firestore; + collectionName: string; + interval: number; + searchTerms: Array; + language: string; + url: string | undefined; + auth: string; + + constructor(db: FirebaseFirestore.Firestore, + collectionName: string, + searchTerms: Array, + auth: string, + language: string = 'en', + intervalMins: number = 15) { + this.db = db; + this.collectionName = collectionName; + this.searchTerms = searchTerms; + this.language = language; + this.auth = auth; + this.interval = intervalMins * 60 * 1000; + this.buildURL() + .then(url => this.url = url); + } + + schedule(): NodeJS.Timer { + return setInterval(() => { + const httpRequest = new XMLHttpRequest(); + httpRequest.setRequestHeader('Authorization', this.auth); + httpRequest.open('GET', this.url); + httpRequest.onreadystatechange = _ => { + this.updateData(httpRequest.responseText); + } + }, this.interval); + } + + async updateData(content: string) { + const response: Array = JSON.parse(content); + response.forEach(element => { + if (!firebaseHelper.firestore.checkDocumentExists(this.db, this.collectionName, element.id)) { + firebaseHelper.firestore.createDocumentWithID(this.db, this.collectionName, element.id, element); + } + }); + } + + buildURL(): Promise { + return new Promise(resolve => { + const parts = ['https://api.newsriver.io/v2/search?query=']; + this.searchTerms.forEach((term, i, _) => { + if (i !== 0) + parts.push[' ']; + parts.push[`title:${term} OR text:${term}`]; + }); + let language: string; + switch(this.language) { + case 'es': language = 'ES'; break; + default: language = 'EN'; break; + } + parts.push[` AND language:${language}`]; + parts.push['&sortBy=discoverDate']; + parts.push['&sortOrder=DESC']; + parts.push['&limit=200']; + resolve(parts.join('')); + }); + } +} \ No newline at end of file From 8552ef711e85133d241011595170463fe15f3f3a Mon Sep 17 00:00:00 2001 From: Javinator9889 Date: Mon, 25 May 2020 17:12:07 +0200 Subject: [PATCH 05/95] Updated files including JSON request handler --- functions/.gitignore | 5 +- functions/package-lock.json | 116 ++++++++++++++++++++++++++++++++++++ functions/package.json | 5 +- functions/src/index.ts | 48 ++++++++------- functions/src/newsriver.ts | 48 +++++++++++++++ functions/src/updater.ts | 70 ++++++++++++++-------- functions/tsconfig.json | 2 +- 7 files changed, 245 insertions(+), 49 deletions(-) create mode 100644 functions/src/newsriver.ts diff --git a/functions/.gitignore b/functions/.gitignore index 7fbb8b4..220462e 100644 --- a/functions/.gitignore +++ b/functions/.gitignore @@ -5,4 +5,7 @@ # Typescript v1 declaration files typings/ -node_modules/ \ No newline at end of file +node_modules/ + +handwashing-firebase-adminsdk.json +.runtimeconfig.json \ No newline at end of file diff --git a/functions/package-lock.json b/functions/package-lock.json index 83a83d5..6bf7501 100644 --- a/functions/package-lock.json +++ b/functions/package-lock.json @@ -274,6 +274,11 @@ "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==", "optional": true }, + "@types/bluebird": { + "version": "3.5.32", + "resolved": "https://registry.npmjs.org/@types/bluebird/-/bluebird-3.5.32.tgz", + "integrity": "sha512-dIOxFfI0C+jz89g6lQ+TqhGgPQ0MxSnh/E4xuC0blhFtyW269+mPG5QeLgbdwst/LvdP8o1y0o/Gz5EHXLec/g==" + }, "@types/body-parser": { "version": "1.19.0", "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.0.tgz", @@ -283,6 +288,11 @@ "@types/node": "*" } }, + "@types/caseless": { + "version": "0.12.2", + "resolved": "https://registry.npmjs.org/@types/caseless/-/caseless-0.12.2.tgz", + "integrity": "sha512-6ckxMjBBD8URvjB6J3NcnuAn5Pkl7t3TizAg+xdlzzQGSPSmBcXf8KoIH0ua/i+tio+ZRUHEXp0HEmvaR4kt0w==" + }, "@types/connect": { "version": "3.4.33", "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.33.tgz", @@ -353,6 +363,26 @@ "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.3.tgz", "integrity": "sha512-ewFXqrQHlFsgc09MK5jP5iR7vumV/BYayNC6PgJO2LPe8vrnNFyjQjSppfEngITi0qvfKtzFvgKymGheFM9UOA==" }, + "@types/request": { + "version": "2.48.5", + "resolved": "https://registry.npmjs.org/@types/request/-/request-2.48.5.tgz", + "integrity": "sha512-/LO7xRVnL3DxJ1WkPGDQrp4VTV1reX9RkC85mJ+Qzykj2Bdw+mG15aAfDahc76HtknjzE16SX/Yddn6MxVbmGQ==", + "requires": { + "@types/caseless": "*", + "@types/node": "*", + "@types/tough-cookie": "*", + "form-data": "^2.5.0" + } + }, + "@types/request-promise": { + "version": "4.1.46", + "resolved": "https://registry.npmjs.org/@types/request-promise/-/request-promise-4.1.46.tgz", + "integrity": "sha512-3Thpj2Va5m0ji3spaCk8YKrjkZyZc6RqUVOphA0n/Xet66AW/AiOAs5vfXhQIL5NmkaO7Jnun7Nl9NEjJ2zBaw==", + "requires": { + "@types/bluebird": "*", + "@types/request": "*" + } + }, "@types/serve-static": { "version": "1.13.4", "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.4.tgz", @@ -362,6 +392,11 @@ "@types/mime": "*" } }, + "@types/tough-cookie": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.0.tgz", + "integrity": "sha512-I99sngh224D0M7XgW1s120zxCt3VYQ3IQsuw3P3jbq5GG4yc79+ZjyKznyOGIQrflfylLgcfekeZW/vk0yng6A==" + }, "abort-controller": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", @@ -429,6 +464,11 @@ "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==" }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" + }, "available-typed-arrays": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.2.tgz", @@ -456,6 +496,11 @@ "integrity": "sha512-S4XzBk5sMB+Rcb/LNcpzXr57VRTxgAvaAEDAl1AwRx27j00hT84O6OkteE7u8UB3NuaaygCRrEpqox4uDOrbdQ==", "optional": true }, + "bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==" + }, "body-parser": { "version": "1.19.0", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", @@ -564,6 +609,14 @@ "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", "dev": true }, + "combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "requires": { + "delayed-stream": "~1.0.0" + } + }, "commander": { "version": "2.20.3", "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", @@ -715,6 +768,11 @@ "object-keys": "^1.0.12" } }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" + }, "depd": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", @@ -1093,6 +1151,16 @@ "integrity": "sha1-C+4AUBiusmDQo6865ljdATbsG5k=", "optional": true }, + "form-data": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.1.tgz", + "integrity": "sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA==", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + } + }, "forwarded": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", @@ -1984,6 +2052,11 @@ "ipaddr.js": "1.9.1" } }, + "psl": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", + "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==" + }, "pump": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", @@ -2019,6 +2092,11 @@ } } }, + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" + }, "qs": { "version": "6.7.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", @@ -2061,6 +2139,25 @@ "es-abstract": "^1.17.0-next.1" } }, + "request-promise": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/request-promise/-/request-promise-4.2.5.tgz", + "integrity": "sha512-ZgnepCykFdmpq86fKGwqntyTiUrHycALuGggpyCZwMvGaZWgxW6yagT0FHkgo5LzYvOaCNvxYwWYIjevSH1EDg==", + "requires": { + "bluebird": "^3.5.0", + "request-promise-core": "1.1.3", + "stealthy-require": "^1.1.1", + "tough-cookie": "^2.3.3" + } + }, + "request-promise-core": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.3.tgz", + "integrity": "sha512-QIs2+ArIGQVp5ZYbWD5ZLCY29D5CfWizP8eWnm8FoGD1TX61veauETVQbrV60662V0oFBkrDOuaBI8XgtuyYAQ==", + "requires": { + "lodash": "^4.17.15" + } + }, "resolve": { "version": "1.17.0", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz", @@ -2192,6 +2289,11 @@ "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" }, + "stealthy-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz", + "integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=" + }, "stream-events": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/stream-events/-/stream-events-1.0.5.tgz", @@ -2309,6 +2411,15 @@ "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==" }, + "tough-cookie": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", + "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", + "requires": { + "psl": "^1.1.28", + "punycode": "^2.1.1" + } + }, "tslib": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.11.1.tgz", @@ -2506,6 +2617,11 @@ "integrity": "sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==", "optional": true }, + "xhr2": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/xhr2/-/xhr2-0.2.0.tgz", + "integrity": "sha512-BDtiD0i2iKPK/S8OAZfpk6tyzEDnKKSjxWHcMBVmh+LuqJ8A32qXTyOx+TVOg2dKvq6zGBq2sgKPkEeRs1qTRA==" + }, "xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", diff --git a/functions/package.json b/functions/package.json index 53ca40c..e9f83d9 100644 --- a/functions/package.json +++ b/functions/package.json @@ -14,11 +14,14 @@ }, "main": "lib/index.js", "dependencies": { + "@types/request-promise": "^4.1.46", "body-parser": "^1.19.0", "express": "^4.17.1", "firebase-admin": "^8.10.0", "firebase-functions": "^3.6.1", - "firebase-functions-helper": "^0.7.5" + "firebase-functions-helper": "^0.7.5", + "request-promise": "^4.2.5", + "xhr2": "^0.2.0" }, "devDependencies": { "tslint": "^5.12.0", diff --git a/functions/src/index.ts b/functions/src/index.ts index 699d89a..6bda024 100644 --- a/functions/src/index.ts +++ b/functions/src/index.ts @@ -3,18 +3,37 @@ import * as admin from 'firebase-admin'; import * as firebaseHelper from 'firebase-functions-helper'; import * as express from 'express'; import * as bodyParser from 'body-parser'; +import { RemoteConfigData } from "./rcdata"; +import { Updater } from "./updater"; -admin.initializeApp(functions.config().firebase); +const serviceAccount = require("../handwashing-firebase-adminsdk.json"); +const firebaseApp = admin.initializeApp( + { + credential: admin.credential.cert(serviceAccount), + databaseURL: "https://handwashing.firebaseio.com" + } +); +const newsCollection = 'news'; const db = admin.firestore(); -const TIME_INTERVAL = 15 * 60 * 1000; -const NEWS_URL = 'https://api.newsriver.io/v2/'; +const authToken = functions.config().newsriver.key; +const updaters = new Set(); +const languages = new Set(['es', 'en']); +const rc = new RemoteConfigData(firebaseApp); + +languages.forEach(language => { + rc.getSearchTermsForLanguage(language) + .then(res => { + console.log(`Search terms for language ${language} obtained: ${res}`); + const updater = new Updater(db, newsCollection, res, authToken, language, 1); + updaters.add(updater); + updater.schedule(); + }); +}); const app = express(); const main = express(); -const jsonCollectionName = 'news'; - main.use('/api/v1', app); main.use(bodyParser.json()); main.use(bodyParser.urlencoded({ extended: false })); @@ -23,28 +42,13 @@ export const webApi = functions.https.onRequest(main); app.get('/', async (req, res) => { try { - const language = req.body['lang']; - const doc = await firebaseHelper.firestore.createNewDocument(db, jsonCollectionName, {'test': 'test'}); + // const language = req.body['lang']; + const doc = await firebaseHelper.firestore.createNewDocument(db, newsCollection, { 'test': 'test' }); res.status(201).send(`Created new contact: ${doc.id}`); } catch (error) { res.status(400).send('Data must contain langauge'); } }); - -const timerId = setInterval(() => { - const httpRequest = new XMLHttpRequest(); - - httpRequest.open('GET', NEWS_URL); - httpRequest.send(); - - httpRequest.onreadystatechange = (e) => { - try { - const response = JSON.parse(httpRequest.responseText); - response - } - } -}, TIME_INTERVAL); - // // Start writing Firebase Functions // // https://firebase.google.com/docs/functions/typescript // diff --git a/functions/src/newsriver.ts b/functions/src/newsriver.ts new file mode 100644 index 0000000..1fd8258 --- /dev/null +++ b/functions/src/newsriver.ts @@ -0,0 +1,48 @@ +interface NewsriverElements { + type: string, + primary: boolean, + url: string, + width: number | null, + height: number | null, + title: string | null, + alternative: string | null + } + + interface NewsriverWebsite { + name: string, + hostName: string, + domainName: string, + iconURL: string, + countryName: string | null, + countryCode: string | null, + region: null + } + + interface Sentiment { + type: string, + sentiment: number + } + + interface ReadTime { + type: string, + seconds: number + } + + interface NewsriverMetadata { + finSentiment: Sentiment, + readTime: ReadTime + } + + export interface NewsriverData { + id: string, + discoverDate: Date, + title: string, + language: string, + text: string, + structuredText: string, + elements: Array, + website: NewsriverWebsite, + metadata: NewsriverMetadata, + highlight: string, + score: number + } \ No newline at end of file diff --git a/functions/src/updater.ts b/functions/src/updater.ts index 7e87b4d..5b09ec9 100644 --- a/functions/src/updater.ts +++ b/functions/src/updater.ts @@ -1,21 +1,22 @@ -import { NewsriverData } from "./newsriver"; +// import { NewsriverData } from "./newsriver"; import firebaseHelper = require("firebase-functions-helper"); +import XMLHttpRequest = require('xhr2'); -class Updater { +export class Updater { db: FirebaseFirestore.Firestore; collectionName: string; interval: number; searchTerms: Array; language: string; - url: string | undefined; auth: string; + url: string | undefined; constructor(db: FirebaseFirestore.Firestore, - collectionName: string, - searchTerms: Array, - auth: string, - language: string = 'en', - intervalMins: number = 15) { + collectionName: string, + searchTerms: Array, + auth: string, + language: string = 'en', + intervalMins: number = 15) { this.db = db; this.collectionName = collectionName; this.searchTerms = searchTerms; @@ -29,21 +30,42 @@ class Updater { schedule(): NodeJS.Timer { return setInterval(() => { const httpRequest = new XMLHttpRequest(); - httpRequest.setRequestHeader('Authorization', this.auth); + const that = this; + console.log('Requesting news'); + while (this.url === undefined); + console.log(`URL: ${this.url}`); httpRequest.open('GET', this.url); - httpRequest.onreadystatechange = _ => { - this.updateData(httpRequest.responseText); + httpRequest.setRequestHeader('Authorization', this.auth); + httpRequest.send(); + httpRequest.onreadystatechange = () => { + console.log('Callback called'); + if (httpRequest.status === 200) { + console.log('Response is OK'); + that.updateData(httpRequest.responseText); + } + }; + httpRequest.onerror = (error) => { + console.log(`error ocurred during process ${error}`) } }, this.interval); } async updateData(content: string) { - const response: Array = JSON.parse(content); - response.forEach(element => { - if (!firebaseHelper.firestore.checkDocumentExists(this.db, this.collectionName, element.id)) { - firebaseHelper.firestore.createDocumentWithID(this.db, this.collectionName, element.id, element); - } - }); + try { + console.log(content); + const response = JSON.parse(content); + console.log(`Got response: ${response}`); + + return; + + response.forEach(element => { + if (!firebaseHelper.firestore.checkDocumentExists(this.db, this.collectionName, element.id)) { + firebaseHelper.firestore.createDocumentWithID(this.db, this.collectionName, element.id, element); + } + }); + } catch (error) { + console.error(error); + } } buildURL(): Promise { @@ -51,18 +73,18 @@ class Updater { const parts = ['https://api.newsriver.io/v2/search?query=']; this.searchTerms.forEach((term, i, _) => { if (i !== 0) - parts.push[' ']; - parts.push[`title:${term} OR text:${term}`]; + parts.push(encodeURI(' OR ')); + parts.push(encodeURI(`title:${term} OR text:${term}`)); }); let language: string; - switch(this.language) { + switch (this.language) { case 'es': language = 'ES'; break; default: language = 'EN'; break; } - parts.push[` AND language:${language}`]; - parts.push['&sortBy=discoverDate']; - parts.push['&sortOrder=DESC']; - parts.push['&limit=200']; + parts.push(encodeURI(` AND language:${language}`)); + parts.push(encodeURI('&sortBy=discoverDate')); + parts.push(encodeURI('&sortOrder=DESC')); + parts.push(encodeURI('&limit=10')); resolve(parts.join('')); }); } diff --git a/functions/tsconfig.json b/functions/tsconfig.json index 7ce05d0..872b7f9 100644 --- a/functions/tsconfig.json +++ b/functions/tsconfig.json @@ -5,7 +5,7 @@ "noUnusedLocals": true, "outDir": "lib", "sourceMap": true, - "strict": true, + "strict": false, "target": "es2017" }, "compileOnSave": true, From 6a72f28e079a6c49f1fc24652441bbfd867a19f0 Mon Sep 17 00:00:00 2001 From: Javinator9889 Date: Thu, 28 May 2020 10:22:52 +0200 Subject: [PATCH 06/95] Changed entry point to 'http2' application --- functions/package-lock.json | 348 +++++++++++++++++++++++++++++++-- functions/package.json | 8 +- functions/src/app.ts | 31 +++ functions/src/bin/www.ts | 80 ++++++++ functions/src/index.ts | 16 +- functions/src/updater.ts | 4 +- functions/src/views/error.pug | 6 + functions/src/views/layout.pug | 7 + 8 files changed, 469 insertions(+), 31 deletions(-) create mode 100644 functions/src/app.ts create mode 100644 functions/src/bin/www.ts create mode 100644 functions/src/views/error.pug create mode 100644 functions/src/views/layout.pug diff --git a/functions/package-lock.json b/functions/package-lock.json index 6bf7501..07b4eaa 100644 --- a/functions/package-lock.json +++ b/functions/package-lock.json @@ -29,6 +29,28 @@ "js-tokens": "^4.0.0" } }, + "@babel/parser": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.10.1.tgz", + "integrity": "sha512-AUTksaz3FqugBkbTZ1i+lDLG5qy8hIzCaAxEtttU6C0BtZZU9pkNZtWSVAht4EW9kl46YBiyTGMp9xTTGqViNg==" + }, + "@babel/types": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.10.1.tgz", + "integrity": "sha512-L2yqUOpf3tzlW9GVuipgLEcZxnO+96SzR6fjXMuxxNkIgFJ5+07mHCZ+HkHqaeZu8+3LKnNJJ1bKbjBETQAsrA==", + "requires": { + "@babel/helper-validator-identifier": "^7.10.1", + "lodash": "^4.17.13", + "to-fast-properties": "^2.0.0" + }, + "dependencies": { + "@babel/helper-validator-identifier": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.1.tgz", + "integrity": "sha512-5vW/JXLALhczRCWP0PnFDMCJAchlBvM7f4uk/jXritBnIa6E1KmqmtrS3yn1LAnxFBypQ3eneLuXjsnfQsgILw==" + } + } + }, "@firebase/app-types": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.6.1.tgz", @@ -415,6 +437,11 @@ "negotiator": "0.6.2" } }, + "acorn": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.2.0.tgz", + "integrity": "sha512-apwXVmYVpQ34m/i71vrApRrRKCWQnZZF1+npOD0WV5xZFfwWOmKGQ2RWlfdy9vWITsenisM8M0Qeq8agcFHNiQ==" + }, "agent-base": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.0.tgz", @@ -459,6 +486,16 @@ "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==", "optional": true }, + "asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=" + }, + "assert-never": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/assert-never/-/assert-never-1.2.1.tgz", + "integrity": "sha512-TaTivMB6pYI1kXwrFlEhLeGfOqoDNdTxjCdwRfFFkEA30Eu+k48W34nlok2EYWJfFFzqaEmichdNM7th6M5HNw==" + }, "assertion-error": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", @@ -478,6 +515,14 @@ "array-filter": "^1.0.0" } }, + "babel-walk": { + "version": "3.0.0-canary-5", + "resolved": "https://registry.npmjs.org/babel-walk/-/babel-walk-3.0.0-canary-5.tgz", + "integrity": "sha512-GAwkz0AihzY5bkwIY5QDR+LvsRQgB/B+1foMPvi0FZPMl5fjD7ICiznUiBdLYMH1QYe6vqu4gWYytZOccLouFw==", + "requires": { + "@babel/types": "^7.9.6" + } + }, "balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", @@ -490,6 +535,21 @@ "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==", "optional": true }, + "basic-auth": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", + "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", + "requires": { + "safe-buffer": "5.1.2" + }, + "dependencies": { + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + } + } + }, "bignumber.js": { "version": "7.2.1", "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-7.2.1.tgz", @@ -526,6 +586,23 @@ "ms": "2.0.0" } }, + "http-errors": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", + "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.1", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.0" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + }, "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", @@ -589,6 +666,14 @@ "supports-color": "^5.3.0" } }, + "character-parser": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/character-parser/-/character-parser-2.2.0.tgz", + "integrity": "sha1-x84o821LzZdE5f/CxfzeHHMmH8A=", + "requires": { + "is-regex": "^1.0.3" + } + }, "check-error": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", @@ -664,6 +749,15 @@ "xdg-basedir": "^4.0.0" } }, + "constantinople": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/constantinople/-/constantinople-4.0.1.tgz", + "integrity": "sha512-vCrqcSIq4//Gx74TXXCGnHpulY1dskqLTFGDmhrGxzeXL8lF8kvXv6mpNWlJj1uD4DW23D4ljAqbY4RRaaUZIw==", + "requires": { + "@babel/parser": "^7.6.0", + "@babel/types": "^7.6.1" + } + }, "content-disposition": { "version": "0.5.3", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", @@ -797,6 +891,11 @@ "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", "dev": true }, + "doctypes": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/doctypes/-/doctypes-1.1.0.tgz", + "integrity": "sha1-6oCxBqh1OHdOijpKWv4pPeSJ4Kk=" + }, "dot-prop": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.2.0.tgz", @@ -1454,22 +1553,15 @@ } }, "http-errors": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", - "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.3.tgz", + "integrity": "sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw==", "requires": { "depd": "~1.1.2", - "inherits": "2.0.3", + "inherits": "2.0.4", "setprototypeof": "1.1.1", "statuses": ">= 1.5.0 < 2", "toidentifier": "1.0.0" - }, - "dependencies": { - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" - } } }, "http-parser-js": { @@ -1560,6 +1652,15 @@ "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz", "integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==" }, + "is-expression": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-expression/-/is-expression-4.0.0.tgz", + "integrity": "sha512-zMIXX63sxzG3XrkHkrAPvm/OVZVSCPNkwMHU8oTX7/U3AL78I0QXCEICXUM13BIa8TYGZ68PiTKfQz3yaTNr4A==", + "requires": { + "acorn": "^7.1.1", + "object-assign": "^4.1.1" + } + }, "is-map": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.1.tgz", @@ -1578,6 +1679,11 @@ "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", "optional": true }, + "is-promise": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz", + "integrity": "sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==" + }, "is-regex": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.5.tgz", @@ -1654,6 +1760,11 @@ "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", "optional": true }, + "js-stringify": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/js-stringify/-/js-stringify-1.0.2.tgz", + "integrity": "sha1-Fzb939lyTyijaCrcYjCufk6Weds=" + }, "js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -1717,6 +1828,15 @@ } } }, + "jstransformer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/jstransformer/-/jstransformer-1.0.0.tgz", + "integrity": "sha1-7Yvwkh4vPx7U1cGkT2hwntJHIsM=", + "requires": { + "is-promise": "^2.0.0", + "promise": "^7.0.1" + } + }, "jwa": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", @@ -1884,6 +2004,38 @@ "minimist": "^1.2.5" } }, + "morgan": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.0.tgz", + "integrity": "sha512-AbegBVI4sh6El+1gNwvD5YIck7nSA36weD7xvIxG4in80j/UoK8AEGaWnnz8v1GxonMCltmlNs5ZKbGvl9b1XQ==", + "requires": { + "basic-auth": "~2.0.1", + "debug": "2.6.9", + "depd": "~2.0.0", + "on-finished": "~2.3.0", + "on-headers": "~1.0.2" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + } + } + }, "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -1949,6 +2101,11 @@ "ee-first": "1.1.1" } }, + "on-headers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==" + }, "once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -1995,8 +2152,7 @@ "path-parse": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", - "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", - "dev": true + "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==" }, "path-to-regexp": { "version": "0.1.7", @@ -2014,6 +2170,14 @@ "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", "optional": true }, + "promise": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", + "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==", + "requires": { + "asap": "~2.0.3" + } + }, "protobufjs": { "version": "6.9.0", "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.9.0.tgz", @@ -2057,6 +2221,118 @@ "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==" }, + "pug": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pug/-/pug-3.0.0.tgz", + "integrity": "sha512-inmsJyFBSHZaiGLaguoFgJGViX0If6AcfcElimvwj9perqjDpUpw79UIEDZbWFmoGVidh08aoE+e8tVkjVJPCw==", + "requires": { + "pug-code-gen": "^3.0.0", + "pug-filters": "^4.0.0", + "pug-lexer": "^5.0.0", + "pug-linker": "^4.0.0", + "pug-load": "^3.0.0", + "pug-parser": "^6.0.0", + "pug-runtime": "^3.0.0", + "pug-strip-comments": "^2.0.0" + } + }, + "pug-attrs": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pug-attrs/-/pug-attrs-3.0.0.tgz", + "integrity": "sha512-azINV9dUtzPMFQktvTXciNAfAuVh/L/JCl0vtPCwvOA21uZrC08K/UnmrL+SXGEVc1FwzjW62+xw5S/uaLj6cA==", + "requires": { + "constantinople": "^4.0.1", + "js-stringify": "^1.0.2", + "pug-runtime": "^3.0.0" + } + }, + "pug-code-gen": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/pug-code-gen/-/pug-code-gen-3.0.1.tgz", + "integrity": "sha512-xJIGvmXTQlkJllq6hqxxjRWcay2F9CU69TuAuiVZgHK0afOhG5txrQOcZyaPHBvSWCU/QQOqEp5XCH94rRZpBQ==", + "requires": { + "constantinople": "^4.0.1", + "doctypes": "^1.1.0", + "js-stringify": "^1.0.2", + "pug-attrs": "^3.0.0", + "pug-error": "^2.0.0", + "pug-runtime": "^3.0.0", + "void-elements": "^3.1.0", + "with": "^7.0.0" + } + }, + "pug-error": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pug-error/-/pug-error-2.0.0.tgz", + "integrity": "sha512-sjiUsi9M4RAGHktC1drQfCr5C5eriu24Lfbt4s+7SykztEOwVZtbFk1RRq0tzLxcMxMYTBR+zMQaG07J/btayQ==" + }, + "pug-filters": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/pug-filters/-/pug-filters-4.0.0.tgz", + "integrity": "sha512-yeNFtq5Yxmfz0f9z2rMXGw/8/4i1cCFecw/Q7+D0V2DdtII5UvqE12VaZ2AY7ri6o5RNXiweGH79OCq+2RQU4A==", + "requires": { + "constantinople": "^4.0.1", + "jstransformer": "1.0.0", + "pug-error": "^2.0.0", + "pug-walk": "^2.0.0", + "resolve": "^1.15.1" + } + }, + "pug-lexer": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/pug-lexer/-/pug-lexer-5.0.0.tgz", + "integrity": "sha512-52xMk8nNpuyQ/M2wjZBN5gXQLIylaGkAoTk5Y1pBhVqaopaoj8Z0iVzpbFZAqitL4RHNVDZRnJDsqEYe99Ti0A==", + "requires": { + "character-parser": "^2.2.0", + "is-expression": "^4.0.0", + "pug-error": "^2.0.0" + } + }, + "pug-linker": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/pug-linker/-/pug-linker-4.0.0.tgz", + "integrity": "sha512-gjD1yzp0yxbQqnzBAdlhbgoJL5qIFJw78juN1NpTLt/mfPJ5VgC4BvkoD3G23qKzJtIIXBbcCt6FioLSFLOHdw==", + "requires": { + "pug-error": "^2.0.0", + "pug-walk": "^2.0.0" + } + }, + "pug-load": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pug-load/-/pug-load-3.0.0.tgz", + "integrity": "sha512-OCjTEnhLWZBvS4zni/WUMjH2YSUosnsmjGBB1An7CsKQarYSWQ0GCVyd4eQPMFJqZ8w9xgs01QdiZXKVjk92EQ==", + "requires": { + "object-assign": "^4.1.1", + "pug-walk": "^2.0.0" + } + }, + "pug-parser": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/pug-parser/-/pug-parser-6.0.0.tgz", + "integrity": "sha512-ukiYM/9cH6Cml+AOl5kETtM9NR3WulyVP2y4HOU45DyMim1IeP/OOiyEWRr6qk5I5klpsBnbuHpwKmTx6WURnw==", + "requires": { + "pug-error": "^2.0.0", + "token-stream": "1.0.0" + } + }, + "pug-runtime": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pug-runtime/-/pug-runtime-3.0.0.tgz", + "integrity": "sha512-GoEPcmQNnaTsePEdVA05bDpY+Op5VLHKayg08AQiqJBWU/yIaywEYv7TetC5dEQS3fzBBoyb2InDcZEg3mPTIA==" + }, + "pug-strip-comments": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pug-strip-comments/-/pug-strip-comments-2.0.0.tgz", + "integrity": "sha512-zo8DsDpH7eTkPHCXFeAk1xZXJbyoTfdPlNR0bK7rpOMuhBYb0f5qUVCO1xlsitYd3w5FQTK7zpNVKb3rZoUrrQ==", + "requires": { + "pug-error": "^2.0.0" + } + }, + "pug-walk": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pug-walk/-/pug-walk-2.0.0.tgz", + "integrity": "sha512-yYELe9Q5q9IQhuvqsZNwA5hfPkMJ8u92bQLIMcsMxf/VADjNtEYptU+inlufAFYcWdHlwNfZOEnOOQrZrcyJCQ==" + }, "pump": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", @@ -2116,6 +2392,25 @@ "http-errors": "1.7.2", "iconv-lite": "0.4.24", "unpipe": "1.0.0" + }, + "dependencies": { + "http-errors": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", + "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.1", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.0" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + } } }, "readable-stream": { @@ -2162,7 +2457,6 @@ "version": "1.17.0", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz", "integrity": "sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==", - "dev": true, "requires": { "path-parse": "^1.0.6" } @@ -2406,11 +2700,21 @@ "readable-stream": "2 || 3" } }, + "to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=" + }, "toidentifier": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==" }, + "token-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/token-stream/-/token-stream-1.0.0.tgz", + "integrity": "sha1-zCAOqyYT9BZtJ/+a/HylbUnfbrQ=" + }, "tough-cookie": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", @@ -2534,6 +2838,11 @@ "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" }, + "void-elements": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz", + "integrity": "sha1-YU9/v42AHwu18GYfWy9XhXUOTwk=" + }, "walkdir": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/walkdir/-/walkdir-0.4.1.tgz", @@ -2594,6 +2903,17 @@ "is-typed-array": "^1.1.3" } }, + "with": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/with/-/with-7.0.1.tgz", + "integrity": "sha512-TpHxhlaRS5mNJbCDXqbDJB4qhyV8zQUPytY3o3cCb6t2m13Qw+vsWFvJCBBIkWILRjNlmlnvd/0AW0dPaO7n/w==", + "requires": { + "@babel/parser": "^7.9.6", + "@babel/types": "^7.9.6", + "assert-never": "^1.2.1", + "babel-walk": "3.0.0-canary-5" + } + }, "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", diff --git a/functions/package.json b/functions/package.json index e9f83d9..6a2913b 100644 --- a/functions/package.json +++ b/functions/package.json @@ -9,10 +9,7 @@ "deploy": "firebase deploy --only functions", "logs": "firebase functions:log" }, - "engines": { - "node": "8" - }, - "main": "lib/index.js", + "main": "lib/bin/www.js", "dependencies": { "@types/request-promise": "^4.1.46", "body-parser": "^1.19.0", @@ -20,6 +17,9 @@ "firebase-admin": "^8.10.0", "firebase-functions": "^3.6.1", "firebase-functions-helper": "^0.7.5", + "http-errors": "^1.7.3", + "morgan": "^1.10.0", + "pug": "^3.0.0", "request-promise": "^4.2.5", "xhr2": "^0.2.0" }, diff --git a/functions/src/app.ts b/functions/src/app.ts new file mode 100644 index 0000000..0a59874 --- /dev/null +++ b/functions/src/app.ts @@ -0,0 +1,31 @@ +const createError = require('http-errors'); +const apiRouter = require('./routes/api'); +const express = require('express'); +const logger = require('morgan'); +const path = require('path'); + +const app = express(); + +// view engine setup +app.set('views', path.join(__dirname, 'views')); +app.set('view engine', 'pug'); + +app.use(logger('dev')); +app.use(express.json()); +app.use(express.urlencoded({ extended: false })); + +app.use(apiRouter); + +// catch 404 and forward to error handler +app.use((req, res, next) => next(createError(404))); + +// error handler +app.use((err, req, res, next) => { + res.locals.message = err.message; + res.locals.error = res.app.get('env') === 'development' ? err : {}; + + res.status(err.status || 500); + res.render('error'); +}); + +export const expressApp = app; diff --git a/functions/src/bin/www.ts b/functions/src/bin/www.ts new file mode 100644 index 0000000..1da42d1 --- /dev/null +++ b/functions/src/bin/www.ts @@ -0,0 +1,80 @@ +import { expressApp } from "../app"; +import * as http from 'http2'; + +/** + * Get port from environment and store in Express + */ +const port = normalizePort(process.env.PORT || '3000'); +expressApp.set('port', port); + +/** + * Create the http server + */ +const server = http.createServer(expressApp); + +/** + * Listen on a provided port, on all network interfaces + */ +server.listen(port); +server.on('error', onError); +server.on('listening', onListening); + +/** + * Normalize a port into a number, string or false + * @param val the port + */ +function normalizePort(val) { + const port = parseInt(val, 10); + + if (isNaN(port)) { + // named pipe + return val; + } + + if (port >= 0) { + // port number + return port; + } + + return false; +} + +/** + * Event listener for HTTP server "error" event. + */ + +function onError(error) { + if (error.syscall !== 'listen') { + throw error; + } + + const bind = typeof port === 'string' + ? 'Pipe ' + port + : 'Port ' + port; + + // handle specific listen errors with friendly messages + switch (error.code) { + case 'EACCES': + console.error(bind + ' requires elevated privileges'); + process.exit(1); + break; + case 'EADDRINUSE': + console.error(bind + ' is already in use'); + process.exit(1); + break; + default: + throw error; + } +} + +/** + * Event listener for HTTP server "listening" event. + */ + +function onListening() { + const addr = server.address(); + const bind = typeof addr === 'string' + ? 'pipe ' + addr + : 'port ' + addr.port; + console.log('Listening on ' + bind); +} diff --git a/functions/src/index.ts b/functions/src/index.ts index 6bda024..c103b93 100644 --- a/functions/src/index.ts +++ b/functions/src/index.ts @@ -3,15 +3,14 @@ import * as admin from 'firebase-admin'; import * as firebaseHelper from 'firebase-functions-helper'; import * as express from 'express'; import * as bodyParser from 'body-parser'; -import { RemoteConfigData } from "./rcdata"; -import { Updater } from "./updater"; +import { RemoteConfigData } from './rcdata'; +import { Updater } from './updater'; - -const serviceAccount = require("../handwashing-firebase-adminsdk.json"); +const serviceAccount = require('../handwashing-firebase-adminsdk.json'); const firebaseApp = admin.initializeApp( { credential: admin.credential.cert(serviceAccount), - databaseURL: "https://handwashing.firebaseio.com" + databaseURL: 'https://handwashing.firebaseio.com' } ); const newsCollection = 'news'; @@ -49,10 +48,5 @@ app.get('/', async (req, res) => { res.status(400).send('Data must contain langauge'); } }); -// // Start writing Firebase Functions -// // https://firebase.google.com/docs/functions/typescript -// -// export const helloWorld = functions.https.onRequest((request, response) => { -// response.send("Hello from Firebase!"); -// }); +module.exports = app; diff --git a/functions/src/updater.ts b/functions/src/updater.ts index 5b09ec9..ff6b59c 100644 --- a/functions/src/updater.ts +++ b/functions/src/updater.ts @@ -1,6 +1,6 @@ // import { NewsriverData } from "./newsriver"; -import firebaseHelper = require("firebase-functions-helper"); -import XMLHttpRequest = require('xhr2'); +import * as firebaseHelper from 'firebase-functions-helper'; +import * as XMLHttpRequest from 'xhr2'; export class Updater { db: FirebaseFirestore.Firestore; diff --git a/functions/src/views/error.pug b/functions/src/views/error.pug new file mode 100644 index 0000000..3b25cfa --- /dev/null +++ b/functions/src/views/error.pug @@ -0,0 +1,6 @@ +extends layout + +block content + h1= message + h2= error.status + pre #{error.stack} diff --git a/functions/src/views/layout.pug b/functions/src/views/layout.pug new file mode 100644 index 0000000..6dc17d8 --- /dev/null +++ b/functions/src/views/layout.pug @@ -0,0 +1,7 @@ +doctype html +html + head + title= title + link(rel='stylesheet', href='/stylesheets/style.css') + body + block content From 5f16082b39dcd834ac30dddeeba61061b21f82cb Mon Sep 17 00:00:00 2001 From: Javinator9889 Date: Sat, 30 May 2020 16:04:20 +0200 Subject: [PATCH 07/95] Created main application model - requests to URL are working as expected In the file "app.ts" there is a commented section with code for testing the response from Newsriver website --- build.gradle | 2 +- functions/.gitignore | 5 +- functions/.idea/.gitignore | 5 + functions/package-lock.json | 119 +----------------- functions/package.json | 12 +- functions/src/app.ts | 24 +++- functions/src/bin/www.ts | 8 +- functions/src/interfaces/dictionary.ts | 7 ++ functions/src/interfaces/projectProperties.ts | 5 + functions/src/models/api.ts | 72 +++++++++++ functions/src/public/stylesheets/style.css | 8 ++ functions/src/routes/api.ts | 5 + functions/src/updater.ts | 97 +++++++------- 13 files changed, 193 insertions(+), 176 deletions(-) create mode 100644 functions/.idea/.gitignore create mode 100644 functions/src/interfaces/dictionary.ts create mode 100644 functions/src/interfaces/projectProperties.ts create mode 100644 functions/src/models/api.ts create mode 100644 functions/src/public/stylesheets/style.css create mode 100644 functions/src/routes/api.ts diff --git a/build.gradle b/build.gradle index 853987b..0d4325a 100644 --- a/build.gradle +++ b/build.gradle @@ -12,7 +12,7 @@ buildscript { } } dependencies { - classpath 'com.android.tools.build:gradle:3.6.3' + classpath 'com.android.tools.build:gradle:4.0.0' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "com.mikepenz.aboutlibraries.plugin:aboutlibraries-plugin:${latestAboutLibsRelease}" classpath 'com.google.gms:google-services:4.3.3' diff --git a/functions/.gitignore b/functions/.gitignore index 220462e..1f66c5c 100644 --- a/functions/.gitignore +++ b/functions/.gitignore @@ -8,4 +8,7 @@ typings/ node_modules/ handwashing-firebase-adminsdk.json -.runtimeconfig.json \ No newline at end of file +.runtimeconfig.json + +# Log files +*.log \ No newline at end of file diff --git a/functions/.idea/.gitignore b/functions/.idea/.gitignore new file mode 100644 index 0000000..b58b603 --- /dev/null +++ b/functions/.idea/.gitignore @@ -0,0 +1,5 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ diff --git a/functions/package-lock.json b/functions/package-lock.json index 07b4eaa..9116fb9 100644 --- a/functions/package-lock.json +++ b/functions/package-lock.json @@ -296,11 +296,6 @@ "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==", "optional": true }, - "@types/bluebird": { - "version": "3.5.32", - "resolved": "https://registry.npmjs.org/@types/bluebird/-/bluebird-3.5.32.tgz", - "integrity": "sha512-dIOxFfI0C+jz89g6lQ+TqhGgPQ0MxSnh/E4xuC0blhFtyW269+mPG5QeLgbdwst/LvdP8o1y0o/Gz5EHXLec/g==" - }, "@types/body-parser": { "version": "1.19.0", "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.0.tgz", @@ -310,11 +305,6 @@ "@types/node": "*" } }, - "@types/caseless": { - "version": "0.12.2", - "resolved": "https://registry.npmjs.org/@types/caseless/-/caseless-0.12.2.tgz", - "integrity": "sha512-6ckxMjBBD8URvjB6J3NcnuAn5Pkl7t3TizAg+xdlzzQGSPSmBcXf8KoIH0ua/i+tio+ZRUHEXp0HEmvaR4kt0w==" - }, "@types/connect": { "version": "3.4.33", "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.33.tgz", @@ -385,26 +375,6 @@ "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.3.tgz", "integrity": "sha512-ewFXqrQHlFsgc09MK5jP5iR7vumV/BYayNC6PgJO2LPe8vrnNFyjQjSppfEngITi0qvfKtzFvgKymGheFM9UOA==" }, - "@types/request": { - "version": "2.48.5", - "resolved": "https://registry.npmjs.org/@types/request/-/request-2.48.5.tgz", - "integrity": "sha512-/LO7xRVnL3DxJ1WkPGDQrp4VTV1reX9RkC85mJ+Qzykj2Bdw+mG15aAfDahc76HtknjzE16SX/Yddn6MxVbmGQ==", - "requires": { - "@types/caseless": "*", - "@types/node": "*", - "@types/tough-cookie": "*", - "form-data": "^2.5.0" - } - }, - "@types/request-promise": { - "version": "4.1.46", - "resolved": "https://registry.npmjs.org/@types/request-promise/-/request-promise-4.1.46.tgz", - "integrity": "sha512-3Thpj2Va5m0ji3spaCk8YKrjkZyZc6RqUVOphA0n/Xet66AW/AiOAs5vfXhQIL5NmkaO7Jnun7Nl9NEjJ2zBaw==", - "requires": { - "@types/bluebird": "*", - "@types/request": "*" - } - }, "@types/serve-static": { "version": "1.13.4", "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.4.tgz", @@ -414,11 +384,6 @@ "@types/mime": "*" } }, - "@types/tough-cookie": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.0.tgz", - "integrity": "sha512-I99sngh224D0M7XgW1s120zxCt3VYQ3IQsuw3P3jbq5GG4yc79+ZjyKznyOGIQrflfylLgcfekeZW/vk0yng6A==" - }, "abort-controller": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", @@ -501,11 +466,6 @@ "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==" }, - "asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" - }, "available-typed-arrays": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.2.tgz", @@ -556,11 +516,6 @@ "integrity": "sha512-S4XzBk5sMB+Rcb/LNcpzXr57VRTxgAvaAEDAl1AwRx27j00hT84O6OkteE7u8UB3NuaaygCRrEpqox4uDOrbdQ==", "optional": true }, - "bluebird": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", - "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==" - }, "body-parser": { "version": "1.19.0", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", @@ -694,14 +649,6 @@ "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", "dev": true }, - "combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "requires": { - "delayed-stream": "~1.0.0" - } - }, "commander": { "version": "2.20.3", "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", @@ -862,11 +809,6 @@ "object-keys": "^1.0.12" } }, - "delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" - }, "depd": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", @@ -1250,16 +1192,6 @@ "integrity": "sha1-C+4AUBiusmDQo6865ljdATbsG5k=", "optional": true }, - "form-data": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.1.tgz", - "integrity": "sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA==", - "requires": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.6", - "mime-types": "^2.1.12" - } - }, "forwarded": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", @@ -2049,8 +1981,7 @@ "node-fetch": { "version": "2.6.0", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.0.tgz", - "integrity": "sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA==", - "optional": true + "integrity": "sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA==" }, "node-forge": { "version": "0.7.4", @@ -2216,11 +2147,6 @@ "ipaddr.js": "1.9.1" } }, - "psl": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", - "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==" - }, "pug": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pug/-/pug-3.0.0.tgz", @@ -2368,11 +2294,6 @@ } } }, - "punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" - }, "qs": { "version": "6.7.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", @@ -2434,25 +2355,6 @@ "es-abstract": "^1.17.0-next.1" } }, - "request-promise": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/request-promise/-/request-promise-4.2.5.tgz", - "integrity": "sha512-ZgnepCykFdmpq86fKGwqntyTiUrHycALuGggpyCZwMvGaZWgxW6yagT0FHkgo5LzYvOaCNvxYwWYIjevSH1EDg==", - "requires": { - "bluebird": "^3.5.0", - "request-promise-core": "1.1.3", - "stealthy-require": "^1.1.1", - "tough-cookie": "^2.3.3" - } - }, - "request-promise-core": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.3.tgz", - "integrity": "sha512-QIs2+ArIGQVp5ZYbWD5ZLCY29D5CfWizP8eWnm8FoGD1TX61veauETVQbrV60662V0oFBkrDOuaBI8XgtuyYAQ==", - "requires": { - "lodash": "^4.17.15" - } - }, "resolve": { "version": "1.17.0", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz", @@ -2583,11 +2485,6 @@ "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" }, - "stealthy-require": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz", - "integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=" - }, "stream-events": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/stream-events/-/stream-events-1.0.5.tgz", @@ -2715,15 +2612,6 @@ "resolved": "https://registry.npmjs.org/token-stream/-/token-stream-1.0.0.tgz", "integrity": "sha1-zCAOqyYT9BZtJ/+a/HylbUnfbrQ=" }, - "tough-cookie": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", - "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", - "requires": { - "psl": "^1.1.28", - "punycode": "^2.1.1" - } - }, "tslib": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.11.1.tgz", @@ -2937,11 +2825,6 @@ "integrity": "sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==", "optional": true }, - "xhr2": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/xhr2/-/xhr2-0.2.0.tgz", - "integrity": "sha512-BDtiD0i2iKPK/S8OAZfpk6tyzEDnKKSjxWHcMBVmh+LuqJ8A32qXTyOx+TVOg2dKvq6zGBq2sgKPkEeRs1qTRA==" - }, "xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", diff --git a/functions/package.json b/functions/package.json index 6a2913b..fb955f7 100644 --- a/functions/package.json +++ b/functions/package.json @@ -7,11 +7,14 @@ "shell": "npm run build && firebase functions:shell", "start": "npm run shell", "deploy": "firebase deploy --only functions", - "logs": "firebase functions:log" + "logs": "firebase functions:log", + "server": "npm run build && node lib/bin/www.js" + }, + "engines": { + "node": "8" }, "main": "lib/bin/www.js", "dependencies": { - "@types/request-promise": "^4.1.46", "body-parser": "^1.19.0", "express": "^4.17.1", "firebase-admin": "^8.10.0", @@ -19,9 +22,8 @@ "firebase-functions-helper": "^0.7.5", "http-errors": "^1.7.3", "morgan": "^1.10.0", - "pug": "^3.0.0", - "request-promise": "^4.2.5", - "xhr2": "^0.2.0" + "node-fetch": "^2.6.0", + "pug": "^3.0.0" }, "devDependencies": { "tslint": "^5.12.0", diff --git a/functions/src/app.ts b/functions/src/app.ts index 0a59874..7b46e59 100644 --- a/functions/src/app.ts +++ b/functions/src/app.ts @@ -1,10 +1,13 @@ +import * as functions from "firebase-functions"; + const createError = require('http-errors'); const apiRouter = require('./routes/api'); const express = require('express'); const logger = require('morgan'); +const updater = require('./updater'); const path = require('path'); -const app = express(); +export const app = express(); // view engine setup app.set('views', path.join(__dirname, 'views')); @@ -12,7 +15,20 @@ app.set('view engine', 'pug'); app.use(logger('dev')); app.use(express.json()); -app.use(express.urlencoded({ extended: false })); +app.use(express.urlencoded({extended: false})); +app.use(express.static(path.join(__dirname, 'public'))); + +/** + * ------------- + * Test purposes + * ------------- + const testUpdater = new updater.Updater(null, null, ['covid-19', 'enfermedad'], functions.config().newsriver.key, 'es'); + app.get('/api', (req, res) => + testUpdater.request() + .then(it => res.json(it)) + ); + * --------------- + */ app.use(apiRouter); @@ -22,10 +38,8 @@ app.use((req, res, next) => next(createError(404))); // error handler app.use((err, req, res, next) => { res.locals.message = err.message; - res.locals.error = res.app.get('env') === 'development' ? err : {}; + res.locals.error = res.app.get('env') === 'development' ? err : {}; res.status(err.status || 500); res.render('error'); }); - -export const expressApp = app; diff --git a/functions/src/bin/www.ts b/functions/src/bin/www.ts index 1da42d1..0aa9e39 100644 --- a/functions/src/bin/www.ts +++ b/functions/src/bin/www.ts @@ -1,16 +1,16 @@ -import { expressApp } from "../app"; -import * as http from 'http2'; +import { app } from "../app"; +import * as http from 'http'; /** * Get port from environment and store in Express */ const port = normalizePort(process.env.PORT || '3000'); -expressApp.set('port', port); +app.set('port', port); /** * Create the http server */ -const server = http.createServer(expressApp); +const server = http.createServer(app); /** * Listen on a provided port, on all network interfaces diff --git a/functions/src/interfaces/dictionary.ts b/functions/src/interfaces/dictionary.ts new file mode 100644 index 0000000..3d4d110 --- /dev/null +++ b/functions/src/interfaces/dictionary.ts @@ -0,0 +1,7 @@ +export interface Dictionary { + [Key: string]: T +} + +export interface NDictionary { + [Key: number]: T +} \ No newline at end of file diff --git a/functions/src/interfaces/projectProperties.ts b/functions/src/interfaces/projectProperties.ts new file mode 100644 index 0000000..2bdee61 --- /dev/null +++ b/functions/src/interfaces/projectProperties.ts @@ -0,0 +1,5 @@ +export interface ProjectProperties { + collection: string, + database: FirebaseFirestore.Firestore, + authToken: string +} \ No newline at end of file diff --git a/functions/src/models/api.ts b/functions/src/models/api.ts new file mode 100644 index 0000000..d23c7bf --- /dev/null +++ b/functions/src/models/api.ts @@ -0,0 +1,72 @@ +import * as functions from 'firebase-functions'; +import * as admin from 'firebase-admin'; + +import {ProjectProperties} from '../interfaces/projectProperties'; +import {Dictionary} from '../interfaces/dictionary'; +import {RemoteConfigData} from '../rcdata'; +import {Updater} from '../updater'; + +class Api { + properties: ProjectProperties; + remoteConfig: RemoteConfigData; + languages: Array; + updaters: Dictionary; + + constructor(properties: ProjectProperties, + remoteConfig: RemoteConfigData, + languages: Array) { + this.properties = properties; + this.remoteConfig = remoteConfig; + this.languages = languages; + this.updaters = {}; + for (const language in languages) { + this.updaters[language] = undefined; + } + } + + async init() { + this.languages.forEach(language => { + this.remoteConfig.getSearchTermsForLanguage(language) + .then(terms => { + const updater = new Updater( + this.properties.database, + `${this.properties.collection}_${language}`, + terms, + this.properties.authToken, + language + ); + this.updaters[language] = updater; + updater.schedule(); + }); + }); + } + + newsForLanguage(language: string) { + if (language !in this.languages) + language = 'en'; + const collection = this.updaters[language].collection; + collection.get() + .then(snapshot => { + snapshot.forEach(doc => { + console.log(`${doc.id} => ${doc.data()}`); + }); + }); + } +} + +const serviceAccount = require('../../handwashing-firebase-adminsdk.json'); +const firebaseApp = admin.initializeApp({ + credential: admin.credential.cert(serviceAccount), + databaseURL: 'https://handwashing.firebaseio.com' +}); +const properties: ProjectProperties = { + collection: 'news', + database: admin.firestore(), + authToken: functions.config().newsriver.key +}; +const languages = ['es', 'en']; +const remoteConfig = new RemoteConfigData(firebaseApp); + +export const api = new Api(properties, remoteConfig, languages) +api.init() + .catch(reason => console.error(`API initialization failed: ${reason}`)); \ No newline at end of file diff --git a/functions/src/public/stylesheets/style.css b/functions/src/public/stylesheets/style.css new file mode 100644 index 0000000..b993201 --- /dev/null +++ b/functions/src/public/stylesheets/style.css @@ -0,0 +1,8 @@ +body { + padding: 50px; + font: 14px "Lucida Grande", Helvetica, Arial, sans-serif; +} + +a { + color: #00B7FF; +} diff --git a/functions/src/routes/api.ts b/functions/src/routes/api.ts new file mode 100644 index 0000000..2245648 --- /dev/null +++ b/functions/src/routes/api.ts @@ -0,0 +1,5 @@ +import * as express from 'express'; + +export const router = express.Router(); + +router.use('/api/v1') \ No newline at end of file diff --git a/functions/src/updater.ts b/functions/src/updater.ts index ff6b59c..f131a1d 100644 --- a/functions/src/updater.ts +++ b/functions/src/updater.ts @@ -1,22 +1,37 @@ -// import { NewsriverData } from "./newsriver"; +import {NewsriverData} from "./newsriver"; import * as firebaseHelper from 'firebase-functions-helper'; -import * as XMLHttpRequest from 'xhr2'; +import * as fetch from 'node-fetch'; export class Updater { - db: FirebaseFirestore.Firestore; - collectionName: string; - interval: number; - searchTerms: Array; - language: string; - auth: string; - url: string | undefined; + private readonly db: FirebaseFirestore.Firestore; + private readonly collectionName: string; + private readonly interval: number; + private readonly searchTerms: Array; + private readonly language: string; + private readonly auth: string; + private _url: string | undefined; - constructor(db: FirebaseFirestore.Firestore, - collectionName: string, - searchTerms: Array, - auth: string, - language: string = 'en', - intervalMins: number = 15) { + // @ts-ignore + set url(value: string) { + this._url = value; + } + + // @ts-ignore + get url(): Promise { + while (this._url === undefined) ; + return Promise.resolve(this._url); + } + + get collection(): FirebaseFirestore.CollectionReference { + return this.db.collection(this.collectionName); + } + + constructor(db: FirebaseFirestore.Firestore | null, + collectionName: string | null, + searchTerms: Array, + auth: string, + language: string = 'en', + intervalMins: number = 15) { this.db = db; this.collectionName = collectionName; this.searchTerms = searchTerms; @@ -24,41 +39,25 @@ export class Updater { this.auth = auth; this.interval = intervalMins * 60 * 1000; this.buildURL() + // @ts-ignore .then(url => this.url = url); } schedule(): NodeJS.Timer { return setInterval(() => { - const httpRequest = new XMLHttpRequest(); const that = this; - console.log('Requesting news'); - while (this.url === undefined); - console.log(`URL: ${this.url}`); - httpRequest.open('GET', this.url); - httpRequest.setRequestHeader('Authorization', this.auth); - httpRequest.send(); - httpRequest.onreadystatechange = () => { - console.log('Callback called'); - if (httpRequest.status === 200) { - console.log('Response is OK'); - that.updateData(httpRequest.responseText); - } - }; - httpRequest.onerror = (error) => { - console.log(`error ocurred during process ${error}`) - } + this.request() + .then(response => { + that.updateData(response) + .catch(error => console.error(`error occurred while updating firebase data ${error}`)); + }) + .catch(error => console.log(`error occurred during process ${error}`)); }, this.interval); } - async updateData(content: string) { + async updateData(content: Array) { try { - console.log(content); - const response = JSON.parse(content); - console.log(`Got response: ${response}`); - - return; - - response.forEach(element => { + content.forEach(element => { if (!firebaseHelper.firestore.checkDocumentExists(this.db, this.collectionName, element.id)) { firebaseHelper.firestore.createDocumentWithID(this.db, this.collectionName, element.id, element); } @@ -68,6 +67,16 @@ export class Updater { } } + async request(): Promise> { + try { + const requestUrl = await this.url; + const response = await fetch(requestUrl, {method: 'GET', headers: {'Authorization': this.auth}}); + return response.json() as Array; + } catch (e) { + return e.message; + } + } + buildURL(): Promise { return new Promise(resolve => { const parts = ['https://api.newsriver.io/v2/search?query=']; @@ -78,8 +87,12 @@ export class Updater { }); let language: string; switch (this.language) { - case 'es': language = 'ES'; break; - default: language = 'EN'; break; + case 'es': + language = 'ES'; + break; + default: + language = 'EN'; + break; } parts.push(encodeURI(` AND language:${language}`)); parts.push(encodeURI('&sortBy=discoverDate')); From 1629f820758f5c9468efe601d9a6378ab2a6b7a2 Mon Sep 17 00:00:00 2001 From: Javinator9889 Date: Mon, 1 Jun 2020 20:00:48 +0200 Subject: [PATCH 08/95] Finished web API model - needs test and further implementation --- firebase.json | 6 +++ functions/src/app.ts | 18 +++++---- functions/src/bin/www.ts | 2 +- functions/src/controllers/api.ts | 7 ++++ functions/src/index.ts | 52 -------------------------- functions/src/interfaces/dictionary.ts | 7 ---- functions/src/models/api.ts | 31 +++++++++------ functions/src/rcdata.ts | 41 +++++++++++++++++++- functions/src/routes/api.ts | 15 +++++++- functions/src/updater.ts | 14 ++++++- 10 files changed, 109 insertions(+), 84 deletions(-) create mode 100644 functions/src/controllers/api.ts delete mode 100644 functions/src/index.ts delete mode 100644 functions/src/interfaces/dictionary.ts diff --git a/firebase.json b/firebase.json index 9c5b99f..0db4534 100644 --- a/firebase.json +++ b/firebase.json @@ -11,6 +11,12 @@ "firebase.json", "**/.*", "**/node_modules/**" + ], + "rewrites": [ + { + "source": "/api/v1/**", + "function": "webApi" + } ] } } diff --git a/functions/src/app.ts b/functions/src/app.ts index 7b46e59..732847a 100644 --- a/functions/src/app.ts +++ b/functions/src/app.ts @@ -1,11 +1,10 @@ -import * as functions from "firebase-functions"; - -const createError = require('http-errors'); -const apiRouter = require('./routes/api'); -const express = require('express'); -const logger = require('morgan'); -const updater = require('./updater'); -const path = require('path'); +import * as path from 'path'; +import * as logger from 'morgan'; +import * as express from 'express'; +// import * as admin from 'firebase-admin;' +import * as apiRouter from './routes/api'; +import * as createError from 'http-errors'; +import * as functions from 'firebase-functions'; export const app = express(); @@ -30,11 +29,14 @@ app.use(express.static(path.join(__dirname, 'public'))); * --------------- */ +// @ts-ignore app.use(apiRouter); // catch 404 and forward to error handler app.use((req, res, next) => next(createError(404))); +export const webApi = functions.https.onRequest(app); + // error handler app.use((err, req, res, next) => { res.locals.message = err.message; diff --git a/functions/src/bin/www.ts b/functions/src/bin/www.ts index 0aa9e39..15f7c58 100644 --- a/functions/src/bin/www.ts +++ b/functions/src/bin/www.ts @@ -1,4 +1,4 @@ -import { app } from "../app"; +import {app} from '../app'; import * as http from 'http'; /** diff --git a/functions/src/controllers/api.ts b/functions/src/controllers/api.ts new file mode 100644 index 0000000..f98687e --- /dev/null +++ b/functions/src/controllers/api.ts @@ -0,0 +1,7 @@ +import {api} from '../models/api'; + +export async function getNewsByLanguage(req, res) { + const language = req.query.lang; + const data = await api.newsForLanguage(language); + return res.json(data); +} \ No newline at end of file diff --git a/functions/src/index.ts b/functions/src/index.ts deleted file mode 100644 index c103b93..0000000 --- a/functions/src/index.ts +++ /dev/null @@ -1,52 +0,0 @@ -import * as functions from 'firebase-functions'; -import * as admin from 'firebase-admin'; -import * as firebaseHelper from 'firebase-functions-helper'; -import * as express from 'express'; -import * as bodyParser from 'body-parser'; -import { RemoteConfigData } from './rcdata'; -import { Updater } from './updater'; - -const serviceAccount = require('../handwashing-firebase-adminsdk.json'); -const firebaseApp = admin.initializeApp( - { - credential: admin.credential.cert(serviceAccount), - databaseURL: 'https://handwashing.firebaseio.com' - } -); -const newsCollection = 'news'; -const db = admin.firestore(); -const authToken = functions.config().newsriver.key; -const updaters = new Set(); -const languages = new Set(['es', 'en']); -const rc = new RemoteConfigData(firebaseApp); - -languages.forEach(language => { - rc.getSearchTermsForLanguage(language) - .then(res => { - console.log(`Search terms for language ${language} obtained: ${res}`); - const updater = new Updater(db, newsCollection, res, authToken, language, 1); - updaters.add(updater); - updater.schedule(); - }); -}); - -const app = express(); -const main = express(); - -main.use('/api/v1', app); -main.use(bodyParser.json()); -main.use(bodyParser.urlencoded({ extended: false })); - -export const webApi = functions.https.onRequest(main); - -app.get('/', async (req, res) => { - try { - // const language = req.body['lang']; - const doc = await firebaseHelper.firestore.createNewDocument(db, newsCollection, { 'test': 'test' }); - res.status(201).send(`Created new contact: ${doc.id}`); - } catch (error) { - res.status(400).send('Data must contain langauge'); - } -}); - -module.exports = app; diff --git a/functions/src/interfaces/dictionary.ts b/functions/src/interfaces/dictionary.ts deleted file mode 100644 index 3d4d110..0000000 --- a/functions/src/interfaces/dictionary.ts +++ /dev/null @@ -1,7 +0,0 @@ -export interface Dictionary { - [Key: string]: T -} - -export interface NDictionary { - [Key: number]: T -} \ No newline at end of file diff --git a/functions/src/models/api.ts b/functions/src/models/api.ts index d23c7bf..70e8e93 100644 --- a/functions/src/models/api.ts +++ b/functions/src/models/api.ts @@ -2,15 +2,16 @@ import * as functions from 'firebase-functions'; import * as admin from 'firebase-admin'; import {ProjectProperties} from '../interfaces/projectProperties'; -import {Dictionary} from '../interfaces/dictionary'; import {RemoteConfigData} from '../rcdata'; import {Updater} from '../updater'; +import {NewsriverData} from '../newsriver'; class Api { properties: ProjectProperties; remoteConfig: RemoteConfigData; languages: Array; - updaters: Dictionary; + updaters: Record; + timers: Set; constructor(properties: ProjectProperties, remoteConfig: RemoteConfigData, @@ -22,6 +23,7 @@ class Api { for (const language in languages) { this.updaters[language] = undefined; } + this.timers = new Set(); } async init() { @@ -36,21 +38,28 @@ class Api { language ); this.updaters[language] = updater; - updater.schedule(); + this.timers.add(updater.schedule()); }); }); + this.remoteConfig.subscribeUpdaters(this.updaters); } - newsForLanguage(language: string) { - if (language !in this.languages) + async newsForLanguage(language: string): Promise> { + if (language ! in this.languages) language = 'en'; const collection = this.updaters[language].collection; - collection.get() - .then(snapshot => { - snapshot.forEach(doc => { - console.log(`${doc.id} => ${doc.data()}`); - }); - }); + const snapshot = await collection.get(); + const data = Array(snapshot.size); + snapshot.forEach(item => { + data.push(item.data() as NewsriverData); + }); + return data; + } + + finish() { + for (const timer of this.timers) { + clearInterval(timer); + } } } diff --git a/functions/src/rcdata.ts b/functions/src/rcdata.ts index 5442063..ccbe159 100644 --- a/functions/src/rcdata.ts +++ b/functions/src/rcdata.ts @@ -1,10 +1,15 @@ +import {Updater} from './updater'; import * as admin from 'firebase-admin'; +import * as functions from 'firebase-functions' export class RemoteConfigData { remoteConfig: admin.remoteConfig.RemoteConfig; + updaters: Record; constructor(app: admin.app.App) { this.remoteConfig = admin.remoteConfig(app); + this.updaters = {}; + this.listenToRCChanges(); } getSearchTermsForLanguage(language: string): Promise> { @@ -13,12 +18,44 @@ export class RemoteConfigData { .then(template => { let condition: string; switch (language) { - case 'es': condition = 'Spanish users'; break; - default: condition = 'Default language users'; break; + case 'es': + condition = 'Spanish users'; + break; + default: + condition = 'Default language users'; + break; } const values = JSON.parse(template.parameters['search_terms'].conditionalValues[condition]['value']); resolve(values); }); }); } + + subscribeUpdaters(updaters: Record) { + this.updaters = updaters; + } + + listenToRCChanges() { + functions.remoteConfig.onUpdate(_ => { + return admin.credential.applicationDefault().getAccessToken() + .then(_ => { + this.remoteConfig.getTemplate() + .then(template => { + const languages = Object.keys(template.parameters['search_terms'].conditionalValues); + for (const language of languages) { + const terms = JSON.parse( + template.parameters['search_terms'].conditionalValues[language]['value'] + ); + try { + if (this.updaters[language].searchTerms.length !== terms.lenght) + this.updaters[language].searchTerms = terms; + } catch (e) { + console.warn(`Updaters are not set yet - ${e}`); + } + } + }); + }) + .catch(err => console.error(`Error while obtaining data from RC: ${err}`)); + }); + } } diff --git a/functions/src/routes/api.ts b/functions/src/routes/api.ts index 2245648..73cd084 100644 --- a/functions/src/routes/api.ts +++ b/functions/src/routes/api.ts @@ -1,5 +1,16 @@ import * as express from 'express'; +import * as admin from 'firebase-admin'; +import * as apiController from '../controllers/api'; -export const router = express.Router(); +// export const router = express.Router(); +const router = express.Router(); -router.use('/api/v1') \ No newline at end of file +router.use('/api/v1', (req, res, next) => { + const tokenId = req.get('Authorization').split('Bearer ')[1]; + admin.auth().verifyIdToken(tokenId) + .then(_ => next()) + .catch(err => res.status(401).send(err)); +}); +router.use('/api/v1', apiController.getNewsByLanguage); + +module.exports = router; diff --git a/functions/src/updater.ts b/functions/src/updater.ts index f131a1d..1d61168 100644 --- a/functions/src/updater.ts +++ b/functions/src/updater.ts @@ -6,7 +6,7 @@ export class Updater { private readonly db: FirebaseFirestore.Firestore; private readonly collectionName: string; private readonly interval: number; - private readonly searchTerms: Array; + private _searchTerms: Array; private readonly language: string; private readonly auth: string; private _url: string | undefined; @@ -22,6 +22,18 @@ export class Updater { return Promise.resolve(this._url); } + set searchTerms(value: Array) { + this._searchTerms = value; + this.url = undefined; + this.buildURL() + // @ts-ignore + .then(url => this.url = url); + } + + get searchTerms() { + return this._searchTerms; + } + get collection(): FirebaseFirestore.CollectionReference { return this.db.collection(this.collectionName); } From a6ec23c46467efdd3e116c58edb4daa542c6bae6 Mon Sep 17 00:00:00 2001 From: Javinator9889 Date: Mon, 1 Jun 2020 20:18:14 +0200 Subject: [PATCH 09/95] Updated API model and Updater one --- functions/src/bin/www.ts | 8 ++++---- functions/src/models/api.ts | 8 ++++---- functions/src/routes/api.ts | 15 ++++++++++++--- functions/tslint.json | 2 +- 4 files changed, 21 insertions(+), 12 deletions(-) diff --git a/functions/src/bin/www.ts b/functions/src/bin/www.ts index 15f7c58..d4ed204 100644 --- a/functions/src/bin/www.ts +++ b/functions/src/bin/www.ts @@ -24,16 +24,16 @@ server.on('listening', onListening); * @param val the port */ function normalizePort(val) { - const port = parseInt(val, 10); + const parsedPort = parseInt(val, 10); - if (isNaN(port)) { + if (isNaN(parsedPort)) { // named pipe return val; } - if (port >= 0) { + if (parsedPort >= 0) { // port number - return port; + return parsedPort; } return false; diff --git a/functions/src/models/api.ts b/functions/src/models/api.ts index 70e8e93..b283271 100644 --- a/functions/src/models/api.ts +++ b/functions/src/models/api.ts @@ -68,14 +68,14 @@ const firebaseApp = admin.initializeApp({ credential: admin.credential.cert(serviceAccount), databaseURL: 'https://handwashing.firebaseio.com' }); -const properties: ProjectProperties = { +const projectProperties: ProjectProperties = { collection: 'news', database: admin.firestore(), authToken: functions.config().newsriver.key }; -const languages = ['es', 'en']; -const remoteConfig = new RemoteConfigData(firebaseApp); +const projectLanguages = ['es', 'en']; +const remoteConfigConnector = new RemoteConfigData(firebaseApp); -export const api = new Api(properties, remoteConfig, languages) +export const api = new Api(projectProperties, remoteConfigConnector, projectLanguages) api.init() .catch(reason => console.error(`API initialization failed: ${reason}`)); \ No newline at end of file diff --git a/functions/src/routes/api.ts b/functions/src/routes/api.ts index 73cd084..2df9fe2 100644 --- a/functions/src/routes/api.ts +++ b/functions/src/routes/api.ts @@ -6,10 +6,19 @@ import * as apiController from '../controllers/api'; const router = express.Router(); router.use('/api/v1', (req, res, next) => { - const tokenId = req.get('Authorization').split('Bearer ')[1]; - admin.auth().verifyIdToken(tokenId) - .then(_ => next()) + try { + const tokenId = req.get('Authorization').split('Bearer ')[1]; + admin.auth().verifyIdToken(tokenId) + .then(_ => { + const language = req.params.lang; + if (language === undefined) { + res.status(403).send('lang must be given [?lang=...]'); + } else next() + }) .catch(err => res.status(401).send(err)); + } catch (e) { + res.status(401).send(e); + } }); router.use('/api/v1', apiController.getNewsByLanguage); diff --git a/functions/tslint.json b/functions/tslint.json index 98b2bfd..3c51143 100644 --- a/functions/tslint.json +++ b/functions/tslint.json @@ -111,5 +111,5 @@ "trailing-comma": {"severity": "warning"} }, - "defaultSeverity": "error" + "defaultSeverity": "warn" } From d07a5d613e38f7be4bb8f1bbc97dca85a3331a7d Mon Sep 17 00:00:00 2001 From: Javinator9889 Date: Wed, 3 Jun 2020 10:43:17 +0200 Subject: [PATCH 10/95] Updated functions model for working with Firebase Functions --- functions/src/app.ts | 4 ---- functions/src/bin/www.ts | 4 ++-- functions/src/models/api.ts | 9 +++++++-- functions/src/routes/api.ts | 7 +++---- functions/src/updater.ts | 24 +++++++++++++++++------- 5 files changed, 29 insertions(+), 19 deletions(-) diff --git a/functions/src/app.ts b/functions/src/app.ts index 732847a..a674cc5 100644 --- a/functions/src/app.ts +++ b/functions/src/app.ts @@ -1,10 +1,8 @@ import * as path from 'path'; import * as logger from 'morgan'; import * as express from 'express'; -// import * as admin from 'firebase-admin;' import * as apiRouter from './routes/api'; import * as createError from 'http-errors'; -import * as functions from 'firebase-functions'; export const app = express(); @@ -35,8 +33,6 @@ app.use(apiRouter); // catch 404 and forward to error handler app.use((req, res, next) => next(createError(404))); -export const webApi = functions.https.onRequest(app); - // error handler app.use((err, req, res, next) => { res.locals.message = err.message; diff --git a/functions/src/bin/www.ts b/functions/src/bin/www.ts index d4ed204..9e84e76 100644 --- a/functions/src/bin/www.ts +++ b/functions/src/bin/www.ts @@ -1,11 +1,11 @@ import {app} from '../app'; import * as http from 'http'; +import * as functions from 'firebase-functions'; /** * Get port from environment and store in Express */ const port = normalizePort(process.env.PORT || '3000'); -app.set('port', port); /** * Create the http server @@ -15,9 +15,9 @@ const server = http.createServer(app); /** * Listen on a provided port, on all network interfaces */ -server.listen(port); server.on('error', onError); server.on('listening', onListening); +export const webApi = functions.https.onRequest(app); /** * Normalize a port into a number, string or false diff --git a/functions/src/models/api.ts b/functions/src/models/api.ts index b283271..3b88c19 100644 --- a/functions/src/models/api.ts +++ b/functions/src/models/api.ts @@ -63,9 +63,14 @@ class Api { } } -const serviceAccount = require('../../handwashing-firebase-adminsdk.json'); +const sdkInfo = functions.config().sdk; const firebaseApp = admin.initializeApp({ - credential: admin.credential.cert(serviceAccount), + projectId: sdkInfo.project_id, + credential: admin.credential.cert({ + projectId: sdkInfo.project_id, + clientEmail: sdkInfo.client_email, + privateKey: sdkInfo.private_key + }), databaseURL: 'https://handwashing.firebaseio.com' }); const projectProperties: ProjectProperties = { diff --git a/functions/src/routes/api.ts b/functions/src/routes/api.ts index 2df9fe2..ab2278a 100644 --- a/functions/src/routes/api.ts +++ b/functions/src/routes/api.ts @@ -2,20 +2,19 @@ import * as express from 'express'; import * as admin from 'firebase-admin'; import * as apiController from '../controllers/api'; -// export const router = express.Router(); const router = express.Router(); router.use('/api/v1', (req, res, next) => { try { + const language = req.query.lang; const tokenId = req.get('Authorization').split('Bearer ')[1]; admin.auth().verifyIdToken(tokenId) .then(_ => { - const language = req.params.lang; if (language === undefined) { res.status(403).send('lang must be given [?lang=...]'); - } else next() + } else next(); }) - .catch(err => res.status(401).send(err)); + .catch(err => res.status(401).send(err)); } catch (e) { res.status(401).send(e); } diff --git a/functions/src/updater.ts b/functions/src/updater.ts index 1d61168..19297b6 100644 --- a/functions/src/updater.ts +++ b/functions/src/updater.ts @@ -18,8 +18,10 @@ export class Updater { // @ts-ignore get url(): Promise { - while (this._url === undefined) ; - return Promise.resolve(this._url); + return new Promise(resolve => { + while (this._url === undefined) ; + return resolve(this._url); + }); } set searchTerms(value: Array) { @@ -43,7 +45,7 @@ export class Updater { searchTerms: Array, auth: string, language: string = 'en', - intervalMins: number = 15) { + intervalMins: number = 60) { this.db = db; this.collectionName = collectionName; this.searchTerms = searchTerms; @@ -53,6 +55,12 @@ export class Updater { this.buildURL() // @ts-ignore .then(url => this.url = url); + this.request() + .then(response => { + this.updateData(response) + .catch(ignored => { + }); + }) } schedule(): NodeJS.Timer { @@ -63,7 +71,7 @@ export class Updater { that.updateData(response) .catch(error => console.error(`error occurred while updating firebase data ${error}`)); }) - .catch(error => console.log(`error occurred during process ${error}`)); + .catch(error => console.error(`error occurred during process ${error}`)); }, this.interval); } @@ -75,7 +83,7 @@ export class Updater { } }); } catch (error) { - console.error(error); + throw error; } } @@ -83,9 +91,11 @@ export class Updater { try { const requestUrl = await this.url; const response = await fetch(requestUrl, {method: 'GET', headers: {'Authorization': this.auth}}); - return response.json() as Array; + const body = await response.json(); + return body as Array; } catch (e) { - return e.message; + console.error(`Captured error ${e}`); + throw e; } } From 93840289d3bdb187c557db82ebab172d00090f0f Mon Sep 17 00:00:00 2001 From: Javinator9889 Date: Wed, 3 Jun 2020 10:57:56 +0200 Subject: [PATCH 11/95] Updated library versions and version code --- app/build.gradle | 16 ++++++++-------- appintro/build.gradle | 2 -- build.gradle | 6 +++--- 3 files changed, 11 insertions(+), 13 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index b0c70fc..9a3fc90 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -42,8 +42,8 @@ android { applicationId "com.javinator9889.handwashingreminder" minSdkVersion 17 targetSdkVersion 29 - versionCode 121 - versionName "1.1.2-${gitCommitHash}" + versionCode 122 + versionName "1.2.0-${gitCommitHash}" multiDexEnabled true resConfigs "en", "es" @@ -107,7 +107,7 @@ dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) api "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" api 'androidx.appcompat:appcompat:1.1.0' - api 'androidx.core:core-ktx:1.2.0' + api 'androidx.core:core-ktx:1.3.0' api 'androidx.legacy:legacy-support-v4:1.0.0' api 'androidx.constraintlayout:constraintlayout:1.1.3' testImplementation 'junit:junit:4.13' @@ -138,7 +138,7 @@ dependencies { implementation "com.mikepenz:aboutlibraries-core:${latestAboutLibsRelease}" implementation "com.mikepenz:aboutlibraries:${latestAboutLibsRelease}" // https://developer.android.com/kotlin/ktx#play-core - api 'com.google.android.play:core:1.7.2' + api 'com.google.android.play:core:1.7.3' api 'com.google.android.play:core-ktx:1.7.0' // https://developer.android.com/kotlin/ktx#collection implementation 'androidx.collection:collection-ktx:1.1.0' @@ -146,7 +146,7 @@ dependencies { implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version" // https://firebase.google.com/docs/android/setup#add-sdks api 'com.google.firebase:firebase-common-ktx:19.3.0' - api 'com.google.firebase:firebase-analytics:17.4.0' + api 'com.google.firebase:firebase-analytics:17.4.2' api 'com.google.firebase:firebase-crashlytics:17.0.0' api 'com.google.firebase:firebase-perf:19.0.7' // http://airbnb.io/lottie/#/android?id=getting-started @@ -158,7 +158,7 @@ dependencies { api 'androidx.emoji:emoji:1.0.0' api 'androidx.emoji:emoji-appcompat:1.0.0' // https://github.com/mikepenz/FastAdapter - implementation "com.mikepenz:fastadapter:${latestFastAdapterRelease}" + api "com.mikepenz:fastadapter:${latestFastAdapterRelease}" // https://developer.android.com/kotlin/ktx#lifecycle implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.2.0' // https://developer.android.com/kotlin/ktx#viewmodel @@ -179,8 +179,8 @@ dependencies { // https://github.com/afollestad/material-dialogs/ implementation 'com.afollestad.material-dialogs:core:3.3.0' // https://developer.android.com/google/play/billing/billing_library_overview - implementation 'com.android.billingclient:billing:2.2.0' - implementation 'com.android.billingclient:billing-ktx:2.2.0' + implementation 'com.android.billingclient:billing:2.2.1' + implementation 'com.android.billingclient:billing-ktx:2.2.1' // https://github.com/cbeust/klaxon implementation 'com.beust:klaxon:5.2' // https://github.com/SufficientlySecure/html-textview diff --git a/appintro/build.gradle b/appintro/build.gradle index d26d497..fb794f2 100644 --- a/appintro/build.gradle +++ b/appintro/build.gradle @@ -43,6 +43,4 @@ dependencies { // https://github.com/AppIntro/AppIntro implementation 'com.github.AppIntro:AppIntro:5.1.0' - // https://github.com/mikepenz/FastAdapter - implementation "com.mikepenz:fastadapter:${latestFastAdapterRelease}" } diff --git a/build.gradle b/build.gradle index 0d4325a..02d6767 100644 --- a/build.gradle +++ b/build.gradle @@ -2,8 +2,8 @@ buildscript { ext.kotlin_version = '1.3.72' - ext.latestAboutLibsRelease = '8.1.0' - ext.latestFastAdapterRelease = '5.0.0' + ext.latestAboutLibsRelease = '8.1.6' + ext.latestFastAdapterRelease = '5.0.2' repositories { google() jcenter() @@ -16,7 +16,7 @@ buildscript { classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "com.mikepenz.aboutlibraries.plugin:aboutlibraries-plugin:${latestAboutLibsRelease}" classpath 'com.google.gms:google-services:4.3.3' - classpath 'com.google.firebase:firebase-crashlytics-gradle:2.0.0' + classpath 'com.google.firebase:firebase-crashlytics-gradle:2.1.1' classpath 'com.google.firebase:perf-plugin:1.3.1' // Performance Monitoring plugin // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files From cb3cb4d5b93acf4d4a0c8b7c3848c62ea7242071 Mon Sep 17 00:00:00 2001 From: Javinator9889 Date: Wed, 3 Jun 2020 12:04:29 +0200 Subject: [PATCH 12/95] Created main "News" card view --- app/build.gradle | 4 +- .../fragments/diseases/DiseasesFragment.kt | 12 +- .../views/fragments/news/adapter/News.kt | 51 ++++++ .../activities/views/viewmodels/VideoModel.kt | 9 +- ...ses_list.xml => loading_recycler_view.xml} | 2 +- app/src/main/res/layout/news_card_view.xml | 146 ++++++++++++++++++ 6 files changed, 212 insertions(+), 12 deletions(-) create mode 100644 app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/news/adapter/News.kt rename app/src/main/res/layout/{diseases_list.xml => loading_recycler_view.xml} (91%) create mode 100644 app/src/main/res/layout/news_card_view.xml diff --git a/app/build.gradle b/app/build.gradle index 9a3fc90..50509f5 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -129,8 +129,8 @@ dependencies { // https://developer.android.com/studio/build/multidex api 'androidx.multidex:multidex:2.0.1' // https://github.com/mikepenz/Android-Iconics - api 'com.mikepenz:iconics-core:5.0.2' - api 'com.mikepenz:iconics-views:5.0.2' + api 'com.mikepenz:iconics-core:5.0.3' + api 'com.mikepenz:iconics-views:5.0.3' //noinspection GradleDependency api 'com.mikepenz:google-material-typeface:3.0.1.4.original-kotlin@aar' api 'com.mikepenz:ionicons-typeface:2.0.1.5-kotlin@aar' diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/diseases/DiseasesFragment.kt b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/diseases/DiseasesFragment.kt index 4284573..a67f7ae 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/diseases/DiseasesFragment.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/diseases/DiseasesFragment.kt @@ -40,13 +40,13 @@ import com.mikepenz.fastadapter.FastAdapter import com.mikepenz.fastadapter.GenericItem import com.mikepenz.fastadapter.adapters.ItemAdapter import com.mikepenz.fastadapter.listeners.ClickEventHook -import kotlinx.android.synthetic.main.diseases_list.* -import kotlinx.android.synthetic.main.diseases_list.view.* +import kotlinx.android.synthetic.main.loading_recycler_view.* +import kotlinx.android.synthetic.main.loading_recycler_view.view.* import kotlinx.coroutines.launch import timber.log.Timber class DiseasesFragment : BaseFragmentView() { - override val layoutId: Int = R.layout.diseases_list + override val layoutId: Int = R.layout.loading_recycler_view private lateinit var parsedHTMLTexts: List private lateinit var fastAdapter: FastAdapter private val upperAdsAdapter: ItemAdapter = ItemAdapter() @@ -78,7 +78,7 @@ class DiseasesFragment : BaseFragmentView() { ) } loading.visibility = View.INVISIBLE - diseasesContainer.visibility = View.VISIBLE + container.visibility = View.VISIBLE }) } } @@ -89,7 +89,7 @@ class DiseasesFragment : BaseFragmentView() { val adapters = listOf(upperAdsAdapter, diseasesAdapter, lowerAdsAdapter) fastAdapter = FastAdapter.with(adapters) val rvManager = LinearLayoutManager(context) - with(view.diseasesContainer) { + with(view.container) { layoutManager = rvManager adapter = fastAdapter } @@ -104,7 +104,7 @@ class DiseasesFragment : BaseFragmentView() { fun onBackPressed() { try { - diseasesContainer.adapter = null + container.adapter = null diseasesAdapter.clear() } catch (e: Exception) { Timber.w(e, "Exception when calling 'onBackPressed'") diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/news/adapter/News.kt b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/news/adapter/News.kt new file mode 100644 index 0000000..5a84d8f --- /dev/null +++ b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/news/adapter/News.kt @@ -0,0 +1,51 @@ +/* + * Copyright © 2020 - present | Handwashing reminder by Javinator9889 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see https://www.gnu.org/licenses/. + * + * Created by Javinator9889 on 3/06/20 - Handwashing reminder. + */ +package com.javinator9889.handwashingreminder.activities.views.fragments.news.adapter + +import android.view.View +import com.javinator9889.handwashingreminder.R +import com.mikepenz.fastadapter.FastAdapter +import com.mikepenz.fastadapter.items.AbstractItem +import java.util.* + +data class News( + val title: String, + val short: String, + val url: String, + val publishDate: Date, + val imageUrl: String?, + val website: String, + val websiteImageUrl: String, + override val layoutRes: Int = R.layout.news_card_view, + override val type: Int = 1 +) : AbstractItem() { + override fun getViewHolder(v: View) = ViewHolder(v) + + class ViewHolder(view: View) : FastAdapter.ViewHolder(view) { + + override fun bindView(item: News, payloads: List) { + TODO("Not yet implemented") + } + + override fun unbindView(item: News) { + TODO("Not yet implemented") + } + + } +} \ No newline at end of file diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/viewmodels/VideoModel.kt b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/viewmodels/VideoModel.kt index 09c6ed2..db0abca 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/viewmodels/VideoModel.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/viewmodels/VideoModel.kt @@ -18,6 +18,7 @@ */ package com.javinator9889.handwashingreminder.activities.views.viewmodels +import android.annotation.SuppressLint import androidx.lifecycle.LiveData import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel @@ -48,7 +49,8 @@ private const val LIVEDATA_KEY = "videomodel:livedata" class VideoModel( private val state: SavedStateHandle, private val position: Int ) : ViewModel() { - private val cachePath: File = HandwashingApplication.instance.applicationContext.cacheDir + private val cachePath: File = + HandwashingApplication.instance.applicationContext.cacheDir val videos: LiveData = liveData { emitSource(state.getLiveData(LIVEDATA_KEY, loadVideo())) } @@ -116,14 +118,15 @@ class VideoModel( } } + @SuppressLint("DefaultLocale") private fun sameSHA2Hash( hash: String, messageDigest: MessageDigest ): Boolean { val sha2sum = messageDigest.digest() val bigInt = BigInteger(1, sha2sum) - val obtainedHash = String.format("%064x", bigInt) - return obtainedHash.equals(hash, true) + val obtainedHash = String.format("%064x", bigInt).toLowerCase() + return obtainedHash.equals(hash.toLowerCase(), true) } } diff --git a/app/src/main/res/layout/diseases_list.xml b/app/src/main/res/layout/loading_recycler_view.xml similarity index 91% rename from app/src/main/res/layout/diseases_list.xml rename to app/src/main/res/layout/loading_recycler_view.xml index 0c67738..b211ad1 100644 --- a/app/src/main/res/layout/diseases_list.xml +++ b/app/src/main/res/layout/loading_recycler_view.xml @@ -15,7 +15,7 @@ app:layout_constraintTop_toTopOf="parent" /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file From 4b74f14e2f39c0df1953f69ce23c9787978fc4f6 Mon Sep 17 00:00:00 2001 From: Javinator9889 Date: Wed, 3 Jun 2020 14:06:04 +0200 Subject: [PATCH 13/95] Created News ViewModel and Klaxon structure for parsing JSON data. In addition, the News fragment was updated including the application logic --- app/build.gradle | 8 +- .../views/fragments/news/NewsFragment.kt | 125 +++++++++++++++--- .../views/fragments/news/adapter/News.kt | 51 ++++++- .../views/viewmodels/NewsViewModel.kt | 58 ++++++++ .../collections/NewsInformation.kt | 67 ++++++++++ .../network/HttpDownloader.kt | 16 +++ 6 files changed, 298 insertions(+), 27 deletions(-) create mode 100644 app/src/main/java/com/javinator9889/handwashingreminder/activities/views/viewmodels/NewsViewModel.kt create mode 100644 app/src/main/java/com/javinator9889/handwashingreminder/collections/NewsInformation.kt diff --git a/app/build.gradle b/app/build.gradle index 50509f5..421f45b 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -135,8 +135,8 @@ dependencies { api 'com.mikepenz:google-material-typeface:3.0.1.4.original-kotlin@aar' api 'com.mikepenz:ionicons-typeface:2.0.1.5-kotlin@aar' // https://github.com/mikepenz/AboutLibraries - implementation "com.mikepenz:aboutlibraries-core:${latestAboutLibsRelease}" - implementation "com.mikepenz:aboutlibraries:${latestAboutLibsRelease}" + implementation "com.mikepenz:aboutlibraries-core:$latestAboutLibsRelease" + implementation "com.mikepenz:aboutlibraries:$latestAboutLibsRelease" // https://developer.android.com/kotlin/ktx#play-core api 'com.google.android.play:core:1.7.3' api 'com.google.android.play:core-ktx:1.7.0' @@ -158,7 +158,9 @@ dependencies { api 'androidx.emoji:emoji:1.0.0' api 'androidx.emoji:emoji-appcompat:1.0.0' // https://github.com/mikepenz/FastAdapter - api "com.mikepenz:fastadapter:${latestFastAdapterRelease}" + api "com.mikepenz:fastadapter:$latestFastAdapterRelease" + implementation "com.mikepenz:fastadapter-extensions-scroll:$latestFastAdapterRelease" + implementation "com.mikepenz:fastadapter-extensions-ui:$latestFastAdapterRelease" // https://developer.android.com/kotlin/ktx#lifecycle implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.2.0' // https://developer.android.com/kotlin/ktx#viewmodel diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/news/NewsFragment.kt b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/news/NewsFragment.kt index 6cad6a3..2165675 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/news/NewsFragment.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/news/NewsFragment.kt @@ -21,37 +21,126 @@ package com.javinator9889.handwashingreminder.activities.views.fragments.news import android.os.Bundle import android.view.View import androidx.annotation.LayoutRes -import com.google.firebase.ktx.Firebase -import com.google.firebase.remoteconfig.ktx.remoteConfig +import androidx.fragment.app.viewModels +import androidx.lifecycle.Observer +import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.whenStarted +import androidx.recyclerview.widget.DefaultItemAnimator +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView import com.javinator9889.handwashingreminder.R import com.javinator9889.handwashingreminder.activities.base.BaseFragmentView -import com.javinator9889.handwashingreminder.utils.RemoteConfig -import kotlinx.android.synthetic.main.under_construction.* - -private const val ARG_UNDER_CONSTRUCTION_TEXT = "news:text:content" +import com.javinator9889.handwashingreminder.activities.views.fragments.news.adapter.News +import com.javinator9889.handwashingreminder.activities.views.viewmodels.NewsViewModel +import com.mikepenz.fastadapter.FastAdapter +import com.mikepenz.fastadapter.GenericItem +import com.mikepenz.fastadapter.adapters.GenericItemAdapter +import com.mikepenz.fastadapter.adapters.ItemAdapter +import com.mikepenz.fastadapter.listeners.ClickEventHook +import com.mikepenz.fastadapter.scroll.EndlessRecyclerOnScrollListener +import com.mikepenz.fastadapter.ui.items.ProgressItem +import kotlinx.android.synthetic.main.loading_recycler_view.* +import kotlinx.android.synthetic.main.loading_recycler_view.view.* +import kotlinx.coroutines.async +import kotlinx.coroutines.launch class NewsFragment : BaseFragmentView() { @LayoutRes - override val layoutId: Int = R.layout.under_construction + override val layoutId: Int = R.layout.loading_recycler_view + private lateinit var fastAdapter: FastAdapter + private lateinit var footerAdapter: GenericItemAdapter + private val newsAdapter = ItemAdapter() + private val newsViewModel: NewsViewModel by viewModels() + private val activeItems = mutableSetOf() + + init { + lifecycleScope.launch { + whenStarted { + loading.visibility = View.VISIBLE + newsViewModel.newsData.observe(viewLifecycleOwner, Observer { + if (it.id !in activeItems) { + val newsObject = News( + title = it.title, + short = "${it.text.take(100)}…", + url = it.url, + publishDate = it.publishDate, + imageUrl = it.elements[0].url, + website = it.website?.name, + websiteImageUrl = it.website?.iconURL + ) + newsAdapter.add(newsObject) + if (::footerAdapter.isInitialized) + footerAdapter.clear() + loading.visibility = View.INVISIBLE + container.visibility = View.VISIBLE + activeItems.add(it.id) + } + }) + } + } + } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - if (savedInstanceState != null) { - underConstructionText.text = savedInstanceState.getString( - ARG_UNDER_CONSTRUCTION_TEXT - ) - } else { - with(Firebase.remoteConfig) { - underConstructionText.text = - getString(RemoteConfig.WORK_IN_PROGRESS) + footerAdapter = ItemAdapter.items() + fastAdapter = FastAdapter.with(listOf(newsAdapter, footerAdapter)) + val rvManager = LinearLayoutManager(context) + val scrollListener = + object : EndlessRecyclerOnScrollListener(footerAdapter) { + override fun onLoadMore(currentPage: Int) { + footerAdapter.clear() + val progressItem = ProgressItem() + progressItem.isEnabled = true + footerAdapter.add(progressItem) + lifecycleScope.async { + newsViewModel.populateData( + from = newsAdapter.adapterItemCount + ) + } + } } + with(view.container) { + layoutManager = rvManager + adapter = fastAdapter + itemAnimator = DefaultItemAnimator() + addOnScrollListener(scrollListener) } + fastAdapter.addEventHooks(listOf(NewsClickHook(), ShareClickHook())) + fastAdapter.withSavedInstanceState(savedInstanceState) } override fun onSaveInstanceState(outState: Bundle) { super.onSaveInstanceState(outState) - outState.putString( - ARG_UNDER_CONSTRUCTION_TEXT, underConstructionText.text.toString() - ) + fastAdapter.saveInstanceState(outState) + } + + private inner class NewsClickHook : ClickEventHook() { + override fun onBind(viewHolder: RecyclerView.ViewHolder) = + if (viewHolder is News.ViewHolder) viewHolder.cardContainer + else null + + override fun onClick( + v: View, + position: Int, + fastAdapter: FastAdapter, + item: News + ) { + TODO("Not yet implemented - open web browser") + } + } + + private inner class ShareClickHook : ClickEventHook() { + override fun onBind(viewHolder: RecyclerView.ViewHolder) = + if (viewHolder is News.ViewHolder) viewHolder.shareImage + else null + + override fun onClick( + v: View, + position: Int, + fastAdapter: FastAdapter, + item: News + ) { + TODO("Not yet implemented - share intent") + } } } \ No newline at end of file diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/news/adapter/News.kt b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/news/adapter/News.kt index 5a84d8f..55758aa 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/news/adapter/News.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/news/adapter/News.kt @@ -18,10 +18,16 @@ */ package com.javinator9889.handwashingreminder.activities.views.fragments.news.adapter +import android.annotation.SuppressLint import android.view.View +import android.widget.ImageView +import android.widget.TextView +import com.google.android.material.card.MaterialCardView import com.javinator9889.handwashingreminder.R +import com.javinator9889.handwashingreminder.graphics.GlideApp import com.mikepenz.fastadapter.FastAdapter import com.mikepenz.fastadapter.items.AbstractItem +import java.text.SimpleDateFormat import java.util.* data class News( @@ -30,22 +36,55 @@ data class News( val url: String, val publishDate: Date, val imageUrl: String?, - val website: String, - val websiteImageUrl: String, + val website: String?, + val websiteImageUrl: String?, override val layoutRes: Int = R.layout.news_card_view, override val type: Int = 1 ) : AbstractItem() { override fun getViewHolder(v: View) = ViewHolder(v) - class ViewHolder(view: View) : FastAdapter.ViewHolder(view) { + class ViewHolder(private val view: View) : + FastAdapter.ViewHolder(view) { + private val title: TextView = view.findViewById(R.id.title) + private val description: TextView = view.findViewById(R.id.description) + private val imageHeader: ImageView = view.findViewById(R.id.imageHeader) + private val websiteLogo: ImageView = view.findViewById(R.id.ws_logo) + private val websiteName: TextView = view.findViewById(R.id.ws_name) + private val publishDate: TextView = view.findViewById(R.id.date) + val cardContainer: MaterialCardView = view.findViewById(R.id.root) + val shareImage: ImageView = view.findViewById(R.id.share) + @SuppressLint("SetTextI18n") override fun bindView(item: News, payloads: List) { - TODO("Not yet implemented") + val formatter = + SimpleDateFormat("dd-MM-yyyy HH:mm", Locale.getDefault()) + title.text = item.title + description.text = item.short + if (item.imageUrl != null) { + GlideApp.with(view) + .load(item.imageUrl) + .centerCrop() + .into(imageHeader) + } else imageHeader.visibility = View.GONE + if (item.websiteImageUrl != null) { + GlideApp.with(view) + .load(item.websiteImageUrl) + .centerCrop() + .into(websiteLogo) + } else websiteLogo.visibility = View.GONE + if (item.website != null) + websiteName.text = item.website + else websiteName.visibility = View.GONE + publishDate.text = formatter.format(item.publishDate) } override fun unbindView(item: News) { - TODO("Not yet implemented") + title.text = null + description.text = null + imageHeader.setImageDrawable(null) + websiteLogo.setImageDrawable(null) + websiteName.text = null + publishDate.text = null } - } } \ No newline at end of file diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/viewmodels/NewsViewModel.kt b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/viewmodels/NewsViewModel.kt new file mode 100644 index 0000000..d36ba45 --- /dev/null +++ b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/viewmodels/NewsViewModel.kt @@ -0,0 +1,58 @@ +/* + * Copyright © 2020 - present | Handwashing reminder by Javinator9889 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see https://www.gnu.org/licenses/. + * + * Created by Javinator9889 on 3/06/20 - Handwashing reminder. + */ +package com.javinator9889.handwashingreminder.activities.views.viewmodels + +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import com.beust.klaxon.JsonReader +import com.beust.klaxon.Klaxon +import com.javinator9889.handwashingreminder.collections.NewsData +import com.javinator9889.handwashingreminder.collections.newsStrategy +import com.javinator9889.handwashingreminder.network.HttpDownloader +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import java.io.Reader + +class NewsViewModel : ViewModel() { + val newsData: MutableLiveData = MutableLiveData() + + suspend fun populateData(from: Int = 0, amount: Int = 10) { + val httpRequest = HttpDownloader() + val klaxon = Klaxon().propertyStrategy(newsStrategy) + var requestReader: Reader? = null + withContext(Dispatchers.IO) { + requestReader = httpRequest.json("http://0.0.0.0:3000/api/v1/") + } + withContext(Dispatchers.Default) { + JsonReader(requestReader!!).use { reader -> + var position = 0 + reader.beginArray { + while (reader.hasNext()) { + if (position < from) + continue + if (position > from + amount) + break + newsData.value = klaxon.parse(reader) + position++ + } + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/collections/NewsInformation.kt b/app/src/main/java/com/javinator9889/handwashingreminder/collections/NewsInformation.kt new file mode 100644 index 0000000..6cc389b --- /dev/null +++ b/app/src/main/java/com/javinator9889/handwashingreminder/collections/NewsInformation.kt @@ -0,0 +1,67 @@ +/* + * Copyright © 2020 - present | Handwashing reminder by Javinator9889 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see https://www.gnu.org/licenses/. + * + * Created by Javinator9889 on 3/06/20 - Handwashing reminder. + */ +package com.javinator9889.handwashingreminder.collections + +import com.beust.klaxon.PropertyStrategy +import java.util.* +import kotlin.reflect.KProperty + +data class NewsData( + val id: String, + val publishDate: Date, + val title: String, + val language: String, + val text: String, + val url: String, + val elements: List, + val website: Website? +) + +data class Elements( + val type: String, + val url: String? +) + +data class Website( + val name: String, + val hostName: String, + val iconURL: String? +) + +val newsStrategy = object : PropertyStrategy { + private val acceptedProperties = + setOf( + "id", + "publishDate", + "title", + "language", + "text", + "url", + "elements", + "website", + "type", + "url", + "name", + "hostName", + "iconURL" + ) + + override fun accept(property: KProperty<*>) = + property.name !in acceptedProperties +} \ No newline at end of file diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/network/HttpDownloader.kt b/app/src/main/java/com/javinator9889/handwashingreminder/network/HttpDownloader.kt index ad4dced..b29ca1b 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/network/HttpDownloader.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/network/HttpDownloader.kt @@ -23,6 +23,7 @@ import okhttp3.OkHttpClient import okhttp3.Request import okio.BufferedSource import java.io.IOException +import java.io.Reader class HttpDownloader : OkHttpDownloader { private val client = OkHttpClient() @@ -41,4 +42,19 @@ class HttpDownloader : OkHttpDownloader { return body()!!.source() } } + + fun json(url: String): Reader { + val request = with(Request.Builder()) { + url(url) + cacheControl(CacheControl.FORCE_NETWORK) + build() + } + with(client.newCall(request).execute()) { + if (!isSuccessful) { + close() + throw IOException("Unexpected code $this") + } + return body()!!.charStream() + } + } } \ No newline at end of file From 1158810d181707762aeba393b8747e3eb60ff516 Mon Sep 17 00:00:00 2001 From: Javinator9889 Date: Wed, 3 Jun 2020 14:54:33 +0200 Subject: [PATCH 14/95] Created debug manifest file and updated view models and activities --- app/src/debug/AndroidManifest.xml | 14 ++++++++++++++ .../views/fragments/news/NewsFragment.kt | 1 + .../views/fragments/news/adapter/News.kt | 2 +- .../activities/views/viewmodels/NewsViewModel.kt | 4 ++-- .../collections/NewsInformation.kt | 6 +++--- 5 files changed, 21 insertions(+), 6 deletions(-) create mode 100644 app/src/debug/AndroidManifest.xml diff --git a/app/src/debug/AndroidManifest.xml b/app/src/debug/AndroidManifest.xml new file mode 100644 index 0000000..44a6fd2 --- /dev/null +++ b/app/src/debug/AndroidManifest.xml @@ -0,0 +1,14 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/news/NewsFragment.kt b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/news/NewsFragment.kt index 2165675..5f6db14 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/news/NewsFragment.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/news/NewsFragment.kt @@ -57,6 +57,7 @@ class NewsFragment : BaseFragmentView() { lifecycleScope.launch { whenStarted { loading.visibility = View.VISIBLE + async { newsViewModel.populateData() } newsViewModel.newsData.observe(viewLifecycleOwner, Observer { if (it.id !in activeItems) { val newsObject = News( diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/news/adapter/News.kt b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/news/adapter/News.kt index 55758aa..1346352 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/news/adapter/News.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/news/adapter/News.kt @@ -34,7 +34,7 @@ data class News( val title: String, val short: String, val url: String, - val publishDate: Date, + val publishDate: String, val imageUrl: String?, val website: String?, val websiteImageUrl: String?, diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/viewmodels/NewsViewModel.kt b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/viewmodels/NewsViewModel.kt index d36ba45..0c1e085 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/viewmodels/NewsViewModel.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/viewmodels/NewsViewModel.kt @@ -37,7 +37,7 @@ class NewsViewModel : ViewModel() { val klaxon = Klaxon().propertyStrategy(newsStrategy) var requestReader: Reader? = null withContext(Dispatchers.IO) { - requestReader = httpRequest.json("http://0.0.0.0:3000/api/v1/") + requestReader = httpRequest.json("http://10.0.2.2:3000/api/v1/") } withContext(Dispatchers.Default) { JsonReader(requestReader!!).use { reader -> @@ -48,7 +48,7 @@ class NewsViewModel : ViewModel() { continue if (position > from + amount) break - newsData.value = klaxon.parse(reader) + newsData.postValue(klaxon.parse(reader)) position++ } } diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/collections/NewsInformation.kt b/app/src/main/java/com/javinator9889/handwashingreminder/collections/NewsInformation.kt index 6cc389b..1a99da6 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/collections/NewsInformation.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/collections/NewsInformation.kt @@ -19,12 +19,12 @@ package com.javinator9889.handwashingreminder.collections import com.beust.klaxon.PropertyStrategy -import java.util.* +//import java.util.* import kotlin.reflect.KProperty data class NewsData( val id: String, - val publishDate: Date, + val publishDate: String, val title: String, val language: String, val text: String, @@ -63,5 +63,5 @@ val newsStrategy = object : PropertyStrategy { ) override fun accept(property: KProperty<*>) = - property.name !in acceptedProperties + property.name in acceptedProperties } \ No newline at end of file From 28681a393c857bb765f070aab8192d25aa1c7363 Mon Sep 17 00:00:00 2001 From: Javinator9889 Date: Wed, 3 Jun 2020 20:14:43 +0200 Subject: [PATCH 15/95] Updated Klaxon model for working with the expected JSON data --- .../views/fragments/news/NewsFragment.kt | 28 +++++---- .../views/fragments/news/adapter/News.kt | 13 +++-- .../views/viewmodels/NewsViewModel.kt | 21 ++++--- .../collections/NewsInformation.kt | 58 +++++++++++++++---- .../main/res/layout/dynamic_content_pb.xml | 5 +- app/src/main/res/layout/news_card_view.xml | 12 +++- app/src/main/res/raw/downloading.json | 1 + 7 files changed, 98 insertions(+), 40 deletions(-) create mode 100644 app/src/main/res/raw/downloading.json diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/news/NewsFragment.kt b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/news/NewsFragment.kt index 5f6db14..56218f1 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/news/NewsFragment.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/news/NewsFragment.kt @@ -24,6 +24,7 @@ import androidx.annotation.LayoutRes import androidx.fragment.app.viewModels import androidx.lifecycle.Observer import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.whenResumed import androidx.lifecycle.whenStarted import androidx.recyclerview.widget.DefaultItemAnimator import androidx.recyclerview.widget.LinearLayoutManager @@ -43,6 +44,7 @@ import kotlinx.android.synthetic.main.loading_recycler_view.* import kotlinx.android.synthetic.main.loading_recycler_view.view.* import kotlinx.coroutines.async import kotlinx.coroutines.launch +import timber.log.Timber class NewsFragment : BaseFragmentView() { @LayoutRes @@ -62,10 +64,10 @@ class NewsFragment : BaseFragmentView() { if (it.id !in activeItems) { val newsObject = News( title = it.title, - short = "${it.text.take(100)}…", + short = "${it.text.take(200)}…", url = it.url, publishDate = it.publishDate, - imageUrl = it.elements[0].url, + imageUrl = it.imageUrl, website = it.website?.name, websiteImageUrl = it.website?.iconURL ) @@ -78,6 +80,9 @@ class NewsFragment : BaseFragmentView() { } }) } + whenResumed { + Timber.d("OnResumed - lifecycle") + } } } @@ -89,14 +94,17 @@ class NewsFragment : BaseFragmentView() { val scrollListener = object : EndlessRecyclerOnScrollListener(footerAdapter) { override fun onLoadMore(currentPage: Int) { - footerAdapter.clear() - val progressItem = ProgressItem() - progressItem.isEnabled = true - footerAdapter.add(progressItem) - lifecycleScope.async { - newsViewModel.populateData( - from = newsAdapter.adapterItemCount - ) + view.container.post { + footerAdapter.clear() + Timber.d("Loading more") + val progressItem = ProgressItem() + progressItem.isEnabled = true + footerAdapter.add(progressItem) + lifecycleScope.async { + newsViewModel.populateData( + from = newsAdapter.adapterItemCount + ) + } } } } diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/news/adapter/News.kt b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/news/adapter/News.kt index 1346352..20e5451 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/news/adapter/News.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/news/adapter/News.kt @@ -27,14 +27,14 @@ import com.javinator9889.handwashingreminder.R import com.javinator9889.handwashingreminder.graphics.GlideApp import com.mikepenz.fastadapter.FastAdapter import com.mikepenz.fastadapter.items.AbstractItem -import java.text.SimpleDateFormat +import java.text.DateFormat import java.util.* data class News( val title: String, val short: String, val url: String, - val publishDate: String, + val publishDate: Date?, val imageUrl: String?, val website: String?, val websiteImageUrl: String?, @@ -56,13 +56,13 @@ data class News( @SuppressLint("SetTextI18n") override fun bindView(item: News, payloads: List) { - val formatter = - SimpleDateFormat("dd-MM-yyyy HH:mm", Locale.getDefault()) + val formatter = DateFormat.getDateTimeInstance() title.text = item.title description.text = item.short if (item.imageUrl != null) { GlideApp.with(view) .load(item.imageUrl) + .placeholder(R.drawable.ic_handwashing_icon) .centerCrop() .into(imageHeader) } else imageHeader.visibility = View.GONE @@ -75,7 +75,10 @@ data class News( if (item.website != null) websiteName.text = item.website else websiteName.visibility = View.GONE - publishDate.text = formatter.format(item.publishDate) + if (item.publishDate != null) + publishDate.text = formatter.format(item.publishDate) + else + publishDate.visibility = View.GONE } override fun unbindView(item: News) { diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/viewmodels/NewsViewModel.kt b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/viewmodels/NewsViewModel.kt index 0c1e085..11ac2b0 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/viewmodels/NewsViewModel.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/viewmodels/NewsViewModel.kt @@ -22,8 +22,7 @@ import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import com.beust.klaxon.JsonReader import com.beust.klaxon.Klaxon -import com.javinator9889.handwashingreminder.collections.NewsData -import com.javinator9889.handwashingreminder.collections.newsStrategy +import com.javinator9889.handwashingreminder.collections.* import com.javinator9889.handwashingreminder.network.HttpDownloader import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext @@ -34,20 +33,26 @@ class NewsViewModel : ViewModel() { suspend fun populateData(from: Int = 0, amount: Int = 10) { val httpRequest = HttpDownloader() - val klaxon = Klaxon().propertyStrategy(newsStrategy) + val klaxon = with(Klaxon()) { + propertyStrategy(newsStrategy) + fieldConverter(KlaxonDate::class, dateConverter) + fieldConverter(KlaxonElements::class, elementConverter) + } var requestReader: Reader? = null withContext(Dispatchers.IO) { - requestReader = httpRequest.json("http://10.0.2.2:3000/api/v1/") + requestReader = httpRequest.json( + "http://10.0.2.2:3000/api/v1?from=$from&amount=$amount" + ) } withContext(Dispatchers.Default) { JsonReader(requestReader!!).use { reader -> var position = 0 reader.beginArray { while (reader.hasNext()) { - if (position < from) - continue - if (position > from + amount) - break +// if (position < from) +// continue +// if (position > from + amount) +// break newsData.postValue(klaxon.parse(reader)) position++ } diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/collections/NewsInformation.kt b/app/src/main/java/com/javinator9889/handwashingreminder/collections/NewsInformation.kt index 1a99da6..df1d276 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/collections/NewsInformation.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/collections/NewsInformation.kt @@ -18,25 +18,26 @@ */ package com.javinator9889.handwashingreminder.collections -import com.beust.klaxon.PropertyStrategy -//import java.util.* +import com.beust.klaxon.* +import timber.log.Timber +import java.text.SimpleDateFormat +import java.util.* import kotlin.reflect.KProperty data class NewsData( val id: String, - val publishDate: String, + @KlaxonDate + val publishDate: Date? = null, val title: String, - val language: String, val text: String, val url: String, - val elements: List, - val website: Website? + @Json(name = "elements") + @KlaxonElements + val imageUrl: String? = null, + val website: Website? = null ) -data class Elements( - val type: String, - val url: String? -) +data class Elements(val url: String?) data class Website( val name: String, @@ -44,18 +45,51 @@ data class Website( val iconURL: String? ) +@Target(AnnotationTarget.FIELD) +annotation class KlaxonDate + +val dateConverter = object : Converter { + override fun canConvert(cls: Class<*>) = cls == Date::class.java + + override fun fromJson(jv: JsonValue) = + if (jv.string != null) { + val format = SimpleDateFormat("yyyy-MM-dd'T'HH:mm", Locale.US) + format.parse(jv.string!!) + } else throw KlaxonException("Couldn't parse date: ${jv.string}") + + override fun toJson(value: Any) = """ { "date": $value } """ +} + +@Target(AnnotationTarget.FIELD) +annotation class KlaxonElements + +val elementConverter = object : Converter { + override fun canConvert(cls: Class<*>) = cls == String::class.java + + override fun fromJson(jv: JsonValue) = + if (jv.array != null) { + val elements = jv.array!! + try { + (elements[0] as Elements).url + } catch (e: Exception) { + Timber.w(e, "Captured exception while parsing Klaxon value") + null + } + } else throw KlaxonException("Couldn't parse array ${jv.array}") + + override fun toJson(value: Any) = """ { "imageUrl": "$value" } """ +} + val newsStrategy = object : PropertyStrategy { private val acceptedProperties = setOf( "id", "publishDate", "title", - "language", "text", "url", "elements", "website", - "type", "url", "name", "hostName", diff --git a/app/src/main/res/layout/dynamic_content_pb.xml b/app/src/main/res/layout/dynamic_content_pb.xml index 27e8018..5d42059 100644 --- a/app/src/main/res/layout/dynamic_content_pb.xml +++ b/app/src/main/res/layout/dynamic_content_pb.xml @@ -33,7 +33,7 @@ app:layout_constraintTop_toTopOf="parent" /> + app:layout_constraintTop_toBottomOf="@+id/animattedView"> @@ -21,10 +24,13 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent"> - @@ -49,7 +55,7 @@ android:layout_height="wrap_content" android:fontFamily="@font/raleway_bold" android:textAppearance="@style/TextAppearance.AppCompat.Display1" - android:textSize="24sp" + android:textSize="18sp" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" @@ -135,7 +141,7 @@ android:clickable="true" android:focusable="true" app:iiv_color="@color/dkgray" - app:iiv_icon="ion_share" + app:iiv_icon="ion_android_share_alt" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toTopOf="parent" /> diff --git a/app/src/main/res/raw/downloading.json b/app/src/main/res/raw/downloading.json new file mode 100644 index 0000000..2f7b54b --- /dev/null +++ b/app/src/main/res/raw/downloading.json @@ -0,0 +1 @@ +{"v":"5.4.3","fr":29.9700012207031,"ip":0,"op":70.0000028511585,"w":307,"h":389,"nm":"refresh-button","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":2,"ty":4,"nm":"Shape Layer 6","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":-44,"ix":10},"p":{"a":0,"k":[154.149,195.327,0],"ix":2},"a":{"a":0,"k":[-2.021,-4,0],"ix":1},"s":{"a":0,"k":[71.946,71.946,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[217,217],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.211764705882,0.211764705882,0.211764705882,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":10,"ix":5},"lc":2,"lj":1,"ml":4,"ml2":{"a":0,"k":4,"ix":8},"bm":0,"d":[{"n":"d","nm":"dash","v":{"a":0,"k":33,"ix":1}},{"n":"o","nm":"offset","v":{"a":0,"k":0,"ix":7}}],"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[-1.5,-4],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.625],"y":[0]},"n":["0p667_1_0p625_0"],"t":26,"s":[0],"e":[100]},{"t":65.0000026475043}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[1],"y":[0]},"n":["0p667_1_1_0"],"t":7,"s":[0],"e":[100]},{"t":41.0000016699642}],"ix":2},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"n":["0p833_0p833_0p167_0p167"],"t":0,"s":[-179],"e":[181]},{"t":65.0000026475043}],"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":0,"op":70.0000028511585,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"Shape Layer 3","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":-44,"ix":10},"p":{"a":0,"k":[154.149,195.327,0],"ix":2},"a":{"a":0,"k":[-2.021,-4,0],"ix":1},"s":{"a":0,"k":[71.946,71.946,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[217,217],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.31397151573,0.909803921569,0.778370337393,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":10,"ix":5},"lc":2,"lj":1,"ml":4,"ml2":{"a":0,"k":4,"ix":8},"bm":0,"d":[{"n":"d","nm":"dash","v":{"a":0,"k":33,"ix":1}},{"n":"o","nm":"offset","v":{"a":0,"k":0,"ix":7}}],"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[-1.5,-4],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.625],"y":[0]},"n":["0p667_1_0p625_0"],"t":26,"s":[0],"e":[100]},{"t":65.0000026475043}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[1],"y":[0]},"n":["0p667_1_1_0"],"t":0,"s":[0],"e":[100]},{"t":41.0000016699642}],"ix":2},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"n":["0p833_0p833_0p167_0p167"],"t":0,"s":[-224],"e":[136]},{"t":65.0000026475043}],"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":0,"op":70.0000028511585,"st":0,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"Shape Layer 4","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":-44,"ix":10},"p":{"a":0,"k":[154.149,195.327,0],"ix":2},"a":{"a":0,"k":[-2.021,-4,0],"ix":1},"s":{"a":0,"k":[46.072,46.072,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[217,217],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0,0.862745098039,0.669896324008,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":10,"ix":5},"lc":2,"lj":1,"ml":4,"ml2":{"a":0,"k":4,"ix":8},"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[-1.5,-4],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[1],"y":[0]},"n":["0p667_1_1_0"],"t":0,"s":[100],"e":[0]},{"t":38.0000015477717}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.733],"y":[0.015]},"n":["0p667_1_0p733_0p015"],"t":19,"s":[100],"e":[0]},{"t":60.0000024438501}],"ix":2},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"n":["0p833_0p833_0p167_0p167"],"t":0,"s":[-319],"e":[-679]},{"t":65.0000026475043}],"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":0,"op":70.0000028511585,"st":0,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"Shape Layer 2","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":-44,"ix":10},"p":{"a":0,"k":[154.149,195.327,0],"ix":2},"a":{"a":0,"k":[-2.021,-4,0],"ix":1},"s":{"a":0,"k":[46.072,46.072,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[217,217],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.105882352941,0.105882352941,0.105882352941,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":10,"ix":5},"lc":2,"lj":1,"ml":4,"ml2":{"a":0,"k":4,"ix":8},"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[-1.5,-4],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[1],"y":[0]},"n":["0p667_1_1_0"],"t":0,"s":[100],"e":[0]},{"t":38.0000015477717}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.733],"y":[0.015]},"n":["0p667_1_0p733_0p015"],"t":19,"s":[100],"e":[0]},{"t":60.0000024438501}],"ix":2},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"n":["0p833_0p833_0p167_0p167"],"t":0,"s":[-224],"e":[-584]},{"t":65.0000026475043}],"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":0,"op":70.0000028511585,"st":0,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":"Shape Layer 5","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"n":["0p833_0p833_0p167_0p167"],"t":0,"s":[4.436],"e":[-355.564]},{"t":69.0000028104276}],"ix":10},"p":{"a":0,"k":[155,194,0],"ix":2},"a":{"a":0,"k":[-1.5,-4,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[217,217],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.317031890271,0.850980392157,0.731588206572,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":5,"ix":5},"lc":1,"lj":1,"ml":4,"ml2":{"a":0,"k":4,"ix":8},"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[-1.5,-4],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":0,"k":85,"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":0,"op":70.0000028511585,"st":0,"bm":0},{"ddd":0,"ind":7,"ty":4,"nm":"Shape Layer 1","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"n":["0p833_0p833_0p167_0p167"],"t":0,"s":[4.436],"e":[-355.564]},{"t":69.0000028104276}],"ix":10},"p":{"a":0,"k":[155,194,0],"ix":2},"a":{"a":0,"k":[-1.5,-4,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[217,217],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0,0.462745098039,0.359274142396,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":5,"ix":5},"lc":1,"lj":1,"ml":4,"ml2":{"a":0,"k":4,"ix":8},"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[-1.5,-4],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":0,"k":85,"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":0,"op":70.0000028511585,"st":0,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":"refresh-button Outlines","parent":7,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":85.093,"ix":10},"p":{"a":0,"k":[-6.619,-112.041,0],"ix":2},"a":{"a":0,"k":[188.881,115.959,0],"ix":1},"s":{"a":0,"k":[58.516,58.516,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[1.875,-1.875],[-1.875,-1.875],[0,0],[-1.274,0],[-0.898,0.903],[0,0],[1.875,1.875],[1.875,-1.875],[0,0]],"o":[[-1.875,-1.875],[-1.875,1.875],[0,0],[0.899,0.903],[1.277,0],[0,0],[1.875,-1.875],[-1.875,-1.875],[0,0],[0,0]],"v":[[-14.662,-12.188],[-21.451,-12.188],[-21.451,-5.398],[-3.393,12.656],[-0.002,14.063],[3.392,12.656],[21.451,-5.398],[21.451,-12.188],[14.662,-12.188],[-0.002,2.473]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[0,0]],"o":[[0,0]],"v":[[-14.662,-12.188]],"c":false},"ix":2},"nm":"Path 2","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"mm","mm":1,"nm":"Merge Paths 1","mn":"ADBE Vector Filter - Merge","hd":false},{"ty":"fl","c":{"a":0,"k":[0.003921568627,0.862745098039,0.670588235294,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[188.943,118.162],"ix":2},"a":{"a":0,"k":[0.062,2.438],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":4,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":70.0000028511585,"st":0,"bm":0}],"markers":[]} \ No newline at end of file From 8fb2e256113d7217c80639636b8906d699f92045 Mon Sep 17 00:00:00 2001 From: Javinator9889 Date: Wed, 3 Jun 2020 20:30:17 +0200 Subject: [PATCH 16/95] Updated Glide for using OkHttp3 integration library and added OkHttp3 to gradle --- app/build.gradle | 3 +++ app/proguard-rules.pro | 2 ++ .../graphics/CustomGraphicsModule.java | 13 +++++-------- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 421f45b..ebedb75 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -177,6 +177,7 @@ dependencies { api 'androidx.preference:preference-ktx:1.1.1' // https://github.com/bumptech/glide api 'com.github.bumptech.glide:glide:4.11.0' + api 'com.github.bumptech.glide:okhttp3-integration:4.11.0' kapt 'com.github.bumptech.glide:compiler:4.11.0' // https://github.com/afollestad/material-dialogs/ implementation 'com.afollestad.material-dialogs:core:3.3.0' @@ -187,6 +188,8 @@ dependencies { implementation 'com.beust:klaxon:5.2' // https://github.com/SufficientlySecure/html-textview implementation 'org.sufficientlysecure:html-textview:3.9' + // https://github.com/square/okhttp/tree/okhttp_3.12.x + implementation 'com.squareup.okhttp3:okhttp:3.12.1' // https://square.github.io/okio/#releases implementation 'com.squareup.okio:okio:2.5.0' // https://github.com/google/conscrypt/ diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index 7d0df06..3c68aca 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -70,6 +70,8 @@ -keep class com.bumptech.glide.load.data.ParcelFileDescriptorRewinder$InternalRewinder { *** rewind(); } +-keep class com.bumptech.glide.integration.okhttp3.OkHttpGlideModule + -dontwarn com.bumptech.glide.load.resource.bitmap.VideoDecoder # Klaxon -keep public class kotlin.reflect.jvm.internal.impl.** { public *; } diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/graphics/CustomGraphicsModule.java b/app/src/main/java/com/javinator9889/handwashingreminder/graphics/CustomGraphicsModule.java index ad9cd59..fda998a 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/graphics/CustomGraphicsModule.java +++ b/app/src/main/java/com/javinator9889/handwashingreminder/graphics/CustomGraphicsModule.java @@ -22,11 +22,10 @@ import androidx.annotation.NonNull; -import com.bumptech.glide.Glide; import com.bumptech.glide.GlideBuilder; -import com.bumptech.glide.Registry; import com.bumptech.glide.annotation.GlideModule; import com.bumptech.glide.load.DecodeFormat; +import com.bumptech.glide.load.engine.cache.InternalCacheDiskCacheFactory; import com.bumptech.glide.module.AppGlideModule; import com.bumptech.glide.request.RequestOptions; @@ -38,11 +37,9 @@ public void applyOptions(@NonNull Context context, @NonNull GlideBuilder builder new RequestOptions() .format(DecodeFormat.PREFER_ARGB_8888) ); - } - - @Override - public void registerComponents(@NonNull Context context, @NonNull Glide glide, @NonNull Registry registry) { -// super.registerComponents(context, glide, registry); -// glide.getRegistry(). + int diskCacheSizeBytes = 1024 * 1024 * 20; // 20 MB + builder.setDiskCache( + new InternalCacheDiskCacheFactory(context, diskCacheSizeBytes) + ); } } From da48cfdb02fe59cfa1dc1515722baa39996e7b12 Mon Sep 17 00:00:00 2001 From: Javinator9889 Date: Fri, 5 Jun 2020 10:57:26 +0200 Subject: [PATCH 17/95] Updated modules for using OkHttp3 --- .gitignore | 1 + .../views/fragments/news/NewsFragment.kt | 2 +- .../views/fragments/news/adapter/News.kt | 17 ++++++++--------- .../views/viewmodels/NewsViewModel.kt | 4 ---- .../collections/NewsInformation.kt | 6 ++++-- app/src/main/res/layout/news_card_view.xml | 11 ++++++----- app/src/main/res/values-es/strings.xml | 2 ++ app/src/main/res/values/strings.xml | 2 ++ ...92-downloading-icon-by-amin-edalatipour.json | 1 + gradle.properties | 6 +++--- 10 files changed, 28 insertions(+), 24 deletions(-) create mode 100644 extras/6592-downloading-icon-by-amin-edalatipour.json diff --git a/.gitignore b/.gitignore index 9752de8..e005d3d 100644 --- a/.gitignore +++ b/.gitignore @@ -18,3 +18,4 @@ api-5245190277294621651-718463-f914fb7573c6.json *.jks google-services.json services.xml +test-app-js/ diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/news/NewsFragment.kt b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/news/NewsFragment.kt index 56218f1..449a14a 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/news/NewsFragment.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/news/NewsFragment.kt @@ -66,7 +66,7 @@ class NewsFragment : BaseFragmentView() { title = it.title, short = "${it.text.take(200)}…", url = it.url, - publishDate = it.publishDate, + discoverDate = it.date, imageUrl = it.imageUrl, website = it.website?.name, websiteImageUrl = it.website?.iconURL diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/news/adapter/News.kt b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/news/adapter/News.kt index 20e5451..e647d69 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/news/adapter/News.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/news/adapter/News.kt @@ -27,6 +27,7 @@ import com.javinator9889.handwashingreminder.R import com.javinator9889.handwashingreminder.graphics.GlideApp import com.mikepenz.fastadapter.FastAdapter import com.mikepenz.fastadapter.items.AbstractItem +import timber.log.Timber import java.text.DateFormat import java.util.* @@ -34,7 +35,7 @@ data class News( val title: String, val short: String, val url: String, - val publishDate: Date?, + val discoverDate: Date?, val imageUrl: String?, val website: String?, val websiteImageUrl: String?, @@ -56,13 +57,13 @@ data class News( @SuppressLint("SetTextI18n") override fun bindView(item: News, payloads: List) { + Timber.d("Binding view") val formatter = DateFormat.getDateTimeInstance() title.text = item.title description.text = item.short if (item.imageUrl != null) { GlideApp.with(view) .load(item.imageUrl) - .placeholder(R.drawable.ic_handwashing_icon) .centerCrop() .into(imageHeader) } else imageHeader.visibility = View.GONE @@ -72,16 +73,14 @@ data class News( .centerCrop() .into(websiteLogo) } else websiteLogo.visibility = View.GONE - if (item.website != null) - websiteName.text = item.website - else websiteName.visibility = View.GONE - if (item.publishDate != null) - publishDate.text = formatter.format(item.publishDate) - else - publishDate.visibility = View.GONE + websiteName.text = + item.website ?: view.context.getString(R.string.no_website) + publishDate.text = item.discoverDate?.let { formatter.format(it) } + ?: view.context.getString(R.string.no_date) } override fun unbindView(item: News) { + Timber.d("Called 'unbind'") title.text = null description.text = null imageHeader.setImageDrawable(null) diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/viewmodels/NewsViewModel.kt b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/viewmodels/NewsViewModel.kt index 11ac2b0..3205f44 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/viewmodels/NewsViewModel.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/viewmodels/NewsViewModel.kt @@ -49,10 +49,6 @@ class NewsViewModel : ViewModel() { var position = 0 reader.beginArray { while (reader.hasNext()) { -// if (position < from) -// continue -// if (position > from + amount) -// break newsData.postValue(klaxon.parse(reader)) position++ } diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/collections/NewsInformation.kt b/app/src/main/java/com/javinator9889/handwashingreminder/collections/NewsInformation.kt index df1d276..5bf7167 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/collections/NewsInformation.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/collections/NewsInformation.kt @@ -26,8 +26,9 @@ import kotlin.reflect.KProperty data class NewsData( val id: String, + @Json(name = "discoverDate") @KlaxonDate - val publishDate: Date? = null, + val date: Date, val title: String, val text: String, val url: String, @@ -53,7 +54,8 @@ val dateConverter = object : Converter { override fun fromJson(jv: JsonValue) = if (jv.string != null) { - val format = SimpleDateFormat("yyyy-MM-dd'T'HH:mm", Locale.US) + val format = + SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ", Locale.US) format.parse(jv.string!!) } else throw KlaxonException("Couldn't parse date: ${jv.string}") diff --git a/app/src/main/res/layout/news_card_view.xml b/app/src/main/res/layout/news_card_view.xml index 93e70be..d56ec80 100644 --- a/app/src/main/res/layout/news_card_view.xml +++ b/app/src/main/res/layout/news_card_view.xml @@ -90,7 +90,7 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/heading"> - - + app:layout_constraintTop_toTopOf="parent" + tools:ignore="HardcodedText" /> diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index a57c4f8..64586e5 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -265,4 +265,6 @@ unos segundos 😉 Valorar No preguntar de nuevo + Sitio no disponible + Fecha no disponible diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index a244424..f4a3fad 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -293,4 +293,6 @@ people! It only takes a few seconds 😉 Rate! Don\'t ask again + Website not available + Date not available diff --git a/extras/6592-downloading-icon-by-amin-edalatipour.json b/extras/6592-downloading-icon-by-amin-edalatipour.json new file mode 100644 index 0000000..2f7b54b --- /dev/null +++ b/extras/6592-downloading-icon-by-amin-edalatipour.json @@ -0,0 +1 @@ +{"v":"5.4.3","fr":29.9700012207031,"ip":0,"op":70.0000028511585,"w":307,"h":389,"nm":"refresh-button","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":2,"ty":4,"nm":"Shape Layer 6","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":-44,"ix":10},"p":{"a":0,"k":[154.149,195.327,0],"ix":2},"a":{"a":0,"k":[-2.021,-4,0],"ix":1},"s":{"a":0,"k":[71.946,71.946,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[217,217],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.211764705882,0.211764705882,0.211764705882,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":10,"ix":5},"lc":2,"lj":1,"ml":4,"ml2":{"a":0,"k":4,"ix":8},"bm":0,"d":[{"n":"d","nm":"dash","v":{"a":0,"k":33,"ix":1}},{"n":"o","nm":"offset","v":{"a":0,"k":0,"ix":7}}],"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[-1.5,-4],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.625],"y":[0]},"n":["0p667_1_0p625_0"],"t":26,"s":[0],"e":[100]},{"t":65.0000026475043}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[1],"y":[0]},"n":["0p667_1_1_0"],"t":7,"s":[0],"e":[100]},{"t":41.0000016699642}],"ix":2},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"n":["0p833_0p833_0p167_0p167"],"t":0,"s":[-179],"e":[181]},{"t":65.0000026475043}],"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":0,"op":70.0000028511585,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"Shape Layer 3","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":-44,"ix":10},"p":{"a":0,"k":[154.149,195.327,0],"ix":2},"a":{"a":0,"k":[-2.021,-4,0],"ix":1},"s":{"a":0,"k":[71.946,71.946,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[217,217],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.31397151573,0.909803921569,0.778370337393,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":10,"ix":5},"lc":2,"lj":1,"ml":4,"ml2":{"a":0,"k":4,"ix":8},"bm":0,"d":[{"n":"d","nm":"dash","v":{"a":0,"k":33,"ix":1}},{"n":"o","nm":"offset","v":{"a":0,"k":0,"ix":7}}],"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[-1.5,-4],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.625],"y":[0]},"n":["0p667_1_0p625_0"],"t":26,"s":[0],"e":[100]},{"t":65.0000026475043}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[1],"y":[0]},"n":["0p667_1_1_0"],"t":0,"s":[0],"e":[100]},{"t":41.0000016699642}],"ix":2},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"n":["0p833_0p833_0p167_0p167"],"t":0,"s":[-224],"e":[136]},{"t":65.0000026475043}],"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":0,"op":70.0000028511585,"st":0,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"Shape Layer 4","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":-44,"ix":10},"p":{"a":0,"k":[154.149,195.327,0],"ix":2},"a":{"a":0,"k":[-2.021,-4,0],"ix":1},"s":{"a":0,"k":[46.072,46.072,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[217,217],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0,0.862745098039,0.669896324008,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":10,"ix":5},"lc":2,"lj":1,"ml":4,"ml2":{"a":0,"k":4,"ix":8},"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[-1.5,-4],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[1],"y":[0]},"n":["0p667_1_1_0"],"t":0,"s":[100],"e":[0]},{"t":38.0000015477717}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.733],"y":[0.015]},"n":["0p667_1_0p733_0p015"],"t":19,"s":[100],"e":[0]},{"t":60.0000024438501}],"ix":2},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"n":["0p833_0p833_0p167_0p167"],"t":0,"s":[-319],"e":[-679]},{"t":65.0000026475043}],"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":0,"op":70.0000028511585,"st":0,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"Shape Layer 2","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":-44,"ix":10},"p":{"a":0,"k":[154.149,195.327,0],"ix":2},"a":{"a":0,"k":[-2.021,-4,0],"ix":1},"s":{"a":0,"k":[46.072,46.072,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[217,217],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.105882352941,0.105882352941,0.105882352941,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":10,"ix":5},"lc":2,"lj":1,"ml":4,"ml2":{"a":0,"k":4,"ix":8},"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[-1.5,-4],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[1],"y":[0]},"n":["0p667_1_1_0"],"t":0,"s":[100],"e":[0]},{"t":38.0000015477717}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.733],"y":[0.015]},"n":["0p667_1_0p733_0p015"],"t":19,"s":[100],"e":[0]},{"t":60.0000024438501}],"ix":2},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"n":["0p833_0p833_0p167_0p167"],"t":0,"s":[-224],"e":[-584]},{"t":65.0000026475043}],"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":0,"op":70.0000028511585,"st":0,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":"Shape Layer 5","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"n":["0p833_0p833_0p167_0p167"],"t":0,"s":[4.436],"e":[-355.564]},{"t":69.0000028104276}],"ix":10},"p":{"a":0,"k":[155,194,0],"ix":2},"a":{"a":0,"k":[-1.5,-4,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[217,217],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.317031890271,0.850980392157,0.731588206572,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":5,"ix":5},"lc":1,"lj":1,"ml":4,"ml2":{"a":0,"k":4,"ix":8},"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[-1.5,-4],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":0,"k":85,"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":0,"op":70.0000028511585,"st":0,"bm":0},{"ddd":0,"ind":7,"ty":4,"nm":"Shape Layer 1","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"n":["0p833_0p833_0p167_0p167"],"t":0,"s":[4.436],"e":[-355.564]},{"t":69.0000028104276}],"ix":10},"p":{"a":0,"k":[155,194,0],"ix":2},"a":{"a":0,"k":[-1.5,-4,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[217,217],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0,0.462745098039,0.359274142396,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":5,"ix":5},"lc":1,"lj":1,"ml":4,"ml2":{"a":0,"k":4,"ix":8},"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[-1.5,-4],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":0,"k":85,"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":0,"op":70.0000028511585,"st":0,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":"refresh-button Outlines","parent":7,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":85.093,"ix":10},"p":{"a":0,"k":[-6.619,-112.041,0],"ix":2},"a":{"a":0,"k":[188.881,115.959,0],"ix":1},"s":{"a":0,"k":[58.516,58.516,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[1.875,-1.875],[-1.875,-1.875],[0,0],[-1.274,0],[-0.898,0.903],[0,0],[1.875,1.875],[1.875,-1.875],[0,0]],"o":[[-1.875,-1.875],[-1.875,1.875],[0,0],[0.899,0.903],[1.277,0],[0,0],[1.875,-1.875],[-1.875,-1.875],[0,0],[0,0]],"v":[[-14.662,-12.188],[-21.451,-12.188],[-21.451,-5.398],[-3.393,12.656],[-0.002,14.063],[3.392,12.656],[21.451,-5.398],[21.451,-12.188],[14.662,-12.188],[-0.002,2.473]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[0,0]],"o":[[0,0]],"v":[[-14.662,-12.188]],"c":false},"ix":2},"nm":"Path 2","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"mm","mm":1,"nm":"Merge Paths 1","mn":"ADBE Vector Filter - Merge","hd":false},{"ty":"fl","c":{"a":0,"k":[0.003921568627,0.862745098039,0.670588235294,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[188.943,118.162],"ix":2},"a":{"a":0,"k":[0.062,2.438],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":4,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":70.0000028511585,"st":0,"bm":0}],"markers":[]} \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index 47cd138..db512ab 100644 --- a/gradle.properties +++ b/gradle.properties @@ -10,11 +10,11 @@ # This option should only be used with decoupled projects. More details, visit # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects # org.gradle.parallel=true -#Mon Apr 27 18:01:43 CEST 2020 -kotlin.code.style=official +#Thu Jun 04 12:03:50 CEST 2020 android.enableJetifier=true -org.gradle.jvmargs=-Xms512M -Xmx1536M -XX\:MaxPermSize\=512M -XX\:MaxMetaspaceSize\=512M -Dkotlin.daemon.jvm.options\="-Xmx1024M" android.enableR8.fullMode=true android.useAndroidX=true +kotlin.code.style=official org.gradle.caching=true +org.gradle.jvmargs=-Xms512M -Xmx2048M -XX\:MaxPermSize\=512M -XX\:MaxMetaspaceSize\=512M -Dkotlin.daemon.jvm.options\="-Xmx2048M" org.gradle.parallel=true From 8b9f246a24b413ba758e7f7241742cd7aa0baa6c Mon Sep 17 00:00:00 2001 From: Javinator9889 Date: Fri, 5 Jun 2020 12:54:26 +0200 Subject: [PATCH 18/95] Updated card view and card model --- .../views/fragments/news/NewsFragment.kt | 23 +++---- .../views/fragments/news/adapter/News.kt | 64 +++++++++++++------ .../collections/NewsInformation.kt | 38 +++++++---- .../handwashingreminder/utils/Android.kt | 4 +- app/src/main/res/layout/news_card_view.xml | 28 ++++---- 5 files changed, 97 insertions(+), 60 deletions(-) diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/news/NewsFragment.kt b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/news/NewsFragment.kt index 449a14a..0b5645a 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/news/NewsFragment.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/news/NewsFragment.kt @@ -24,7 +24,6 @@ import androidx.annotation.LayoutRes import androidx.fragment.app.viewModels import androidx.lifecycle.Observer import androidx.lifecycle.lifecycleScope -import androidx.lifecycle.whenResumed import androidx.lifecycle.whenStarted import androidx.recyclerview.widget.DefaultItemAnimator import androidx.recyclerview.widget.LinearLayoutManager @@ -42,7 +41,6 @@ import com.mikepenz.fastadapter.scroll.EndlessRecyclerOnScrollListener import com.mikepenz.fastadapter.ui.items.ProgressItem import kotlinx.android.synthetic.main.loading_recycler_view.* import kotlinx.android.synthetic.main.loading_recycler_view.view.* -import kotlinx.coroutines.async import kotlinx.coroutines.launch import timber.log.Timber @@ -59,30 +57,28 @@ class NewsFragment : BaseFragmentView() { lifecycleScope.launch { whenStarted { loading.visibility = View.VISIBLE - async { newsViewModel.populateData() } + launch { newsViewModel.populateData() } newsViewModel.newsData.observe(viewLifecycleOwner, Observer { + if (::footerAdapter.isInitialized) + footerAdapter.clear() if (it.id !in activeItems) { val newsObject = News( title = it.title, short = "${it.text.take(200)}…", url = it.url, - discoverDate = it.date, - imageUrl = it.imageUrl, + discoverDate = it.discoverDate, + imageUrl = it.elements?.url, website = it.website?.name, - websiteImageUrl = it.website?.iconURL + websiteImageUrl = it.website?.iconURL, + lifecycleScope = this@NewsFragment.lifecycleScope ) newsAdapter.add(newsObject) - if (::footerAdapter.isInitialized) - footerAdapter.clear() loading.visibility = View.INVISIBLE container.visibility = View.VISIBLE activeItems.add(it.id) } }) } - whenResumed { - Timber.d("OnResumed - lifecycle") - } } } @@ -100,9 +96,10 @@ class NewsFragment : BaseFragmentView() { val progressItem = ProgressItem() progressItem.isEnabled = true footerAdapter.add(progressItem) - lifecycleScope.async { + lifecycleScope.launch { newsViewModel.populateData( - from = newsAdapter.adapterItemCount + from = newsAdapter.adapterItemCount, + amount = 20 ) } } diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/news/adapter/News.kt b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/news/adapter/News.kt index e647d69..8d5371c 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/news/adapter/News.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/news/adapter/News.kt @@ -22,11 +22,15 @@ import android.annotation.SuppressLint import android.view.View import android.widget.ImageView import android.widget.TextView +import androidx.lifecycle.LifecycleCoroutineScope +import com.airbnb.lottie.LottieAnimationView import com.google.android.material.card.MaterialCardView import com.javinator9889.handwashingreminder.R import com.javinator9889.handwashingreminder.graphics.GlideApp +import com.javinator9889.handwashingreminder.utils.isHighPerformingDevice import com.mikepenz.fastadapter.FastAdapter import com.mikepenz.fastadapter.items.AbstractItem +import kotlinx.coroutines.* import timber.log.Timber import java.text.DateFormat import java.util.* @@ -39,6 +43,7 @@ data class News( val imageUrl: String?, val website: String?, val websiteImageUrl: String?, + val lifecycleScope: LifecycleCoroutineScope, override val layoutRes: Int = R.layout.news_card_view, override val type: Int = 1 ) : AbstractItem() { @@ -48,35 +53,54 @@ data class News( FastAdapter.ViewHolder(view) { private val title: TextView = view.findViewById(R.id.title) private val description: TextView = view.findViewById(R.id.description) - private val imageHeader: ImageView = view.findViewById(R.id.imageHeader) + private val imageHeader: LottieAnimationView = + view.findViewById(R.id.imageHeader) private val websiteLogo: ImageView = view.findViewById(R.id.ws_logo) private val websiteName: TextView = view.findViewById(R.id.ws_name) private val publishDate: TextView = view.findViewById(R.id.date) val cardContainer: MaterialCardView = view.findViewById(R.id.root) val shareImage: ImageView = view.findViewById(R.id.share) + init { + setIsRecyclable(!isHighPerformingDevice()) + } + @SuppressLint("SetTextI18n") override fun bindView(item: News, payloads: List) { - Timber.d("Binding view") val formatter = DateFormat.getDateTimeInstance() - title.text = item.title - description.text = item.short - if (item.imageUrl != null) { - GlideApp.with(view) - .load(item.imageUrl) - .centerCrop() - .into(imageHeader) - } else imageHeader.visibility = View.GONE - if (item.websiteImageUrl != null) { - GlideApp.with(view) - .load(item.websiteImageUrl) - .centerCrop() - .into(websiteLogo) - } else websiteLogo.visibility = View.GONE - websiteName.text = - item.website ?: view.context.getString(R.string.no_website) - publishDate.text = item.discoverDate?.let { formatter.format(it) } - ?: view.context.getString(R.string.no_date) + val context = view.context + imageHeader.setAnimation(R.raw.downloading) + item.lifecycleScope.launch(context = Dispatchers.Main) { + title.text = item.title + description.text = item.short + val deferreds = mutableSetOf>() + if (item.imageUrl != null) + deferreds.add( + async { + GlideApp.with(context) + .load(item.imageUrl) + .optionalCenterCrop() + .into(imageHeader) + } + ) + else imageHeader.visibility = View.GONE + if (item.websiteImageUrl != null) + deferreds.add( + async { + GlideApp.with(context) + .load(item.websiteImageUrl) + .optionalCenterCrop() + .into(websiteLogo) + } + ) + else websiteLogo.visibility = View.GONE + websiteName.text = item.website + ?: context.getString(R.string.no_website) + publishDate.text = + item.discoverDate?.let { formatter.format(it) } + ?: context.getString(R.string.no_date) + deferreds.awaitAll() + } } override fun unbindView(item: News) { diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/collections/NewsInformation.kt b/app/src/main/java/com/javinator9889/handwashingreminder/collections/NewsInformation.kt index 5bf7167..4812578 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/collections/NewsInformation.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/collections/NewsInformation.kt @@ -26,15 +26,13 @@ import kotlin.reflect.KProperty data class NewsData( val id: String, - @Json(name = "discoverDate") @KlaxonDate - val date: Date, + val discoverDate: Date?, val title: String, val text: String, val url: String, - @Json(name = "elements") @KlaxonElements - val imageUrl: String? = null, + val elements: Elements? = null, val website: Website? = null ) @@ -52,32 +50,44 @@ annotation class KlaxonDate val dateConverter = object : Converter { override fun canConvert(cls: Class<*>) = cls == Date::class.java - override fun fromJson(jv: JsonValue) = + override fun fromJson(jv: JsonValue): Date? = if (jv.string != null) { - val format = - SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ", Locale.US) - format.parse(jv.string!!) + try { + with( + SimpleDateFormat( + "yyyy-MM-dd'T'HH:mm:ss.SSSZ", Locale.US + ) + ) { + parse(jv.string!!) + } + } catch (e: Exception) { + Timber.w(e, "Captured error while parsing date") + null + } } else throw KlaxonException("Couldn't parse date: ${jv.string}") - override fun toJson(value: Any) = """ { "date": $value } """ + override fun toJson(value: Any) = """ { "discoverDate": $value } """ } @Target(AnnotationTarget.FIELD) annotation class KlaxonElements val elementConverter = object : Converter { - override fun canConvert(cls: Class<*>) = cls == String::class.java + override fun canConvert(cls: Class<*>) = cls == Elements::class.java - override fun fromJson(jv: JsonValue) = + override fun fromJson(jv: JsonValue): Elements? { + Timber.d("Parsing 'KlaxonElements'") if (jv.array != null) { + Timber.d("Parsing 'Elements'") val elements = jv.array!! - try { - (elements[0] as Elements).url + return try { + Elements(url = (elements[0] as JsonObject)["url"] as String) } catch (e: Exception) { Timber.w(e, "Captured exception while parsing Klaxon value") null } } else throw KlaxonException("Couldn't parse array ${jv.array}") + } override fun toJson(value: Any) = """ { "imageUrl": "$value" } """ } @@ -86,7 +96,7 @@ val newsStrategy = object : PropertyStrategy { private val acceptedProperties = setOf( "id", - "publishDate", + "discoverDate", "title", "text", "url", diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/utils/Android.kt b/app/src/main/java/com/javinator9889/handwashingreminder/utils/Android.kt index eadf927..272322f 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/utils/Android.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/utils/Android.kt @@ -50,9 +50,9 @@ fun isHighPerformingDevice(): Boolean { getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager val isLowRamDevice = if (isAtLeast(AndroidVersion.KITKAT_WATCH)) - !activityManager.isLowRamDevice + activityManager.isLowRamDevice else true - return isLowRamDevice && + return !isLowRamDevice && Runtime.getRuntime().availableProcessors() >= 4 && activityManager.memoryClass >= 128 } diff --git a/app/src/main/res/layout/news_card_view.xml b/app/src/main/res/layout/news_card_view.xml index d56ec80..d652344 100644 --- a/app/src/main/res/layout/news_card_view.xml +++ b/app/src/main/res/layout/news_card_view.xml @@ -5,7 +5,10 @@ android:id="@+id/root" android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_margin="16dp" + android:layout_marginStart="16dp" + android:layout_marginTop="8dp" + android:layout_marginEnd="16dp" + android:layout_marginBottom="8dp" android:clickable="true" android:focusable="true" app:cardCornerRadius="8dp" @@ -26,14 +29,14 @@ + app:layout_constraintTop_toTopOf="parent" + app:lottie_autoPlay="true" + app:lottie_loop="true" + app:lottie_rawRes="@raw/downloading" /> - - Date: Mon, 8 Jun 2020 14:50:30 +0200 Subject: [PATCH 19/95] Updated express application for running correctly --- functions/package-lock.json | 562 +++++++++++++++++++++++-- functions/package.json | 3 - functions/src/app.ts | 12 +- functions/src/bin/www.ts | 6 +- functions/src/controllers/api.ts | 55 ++- functions/src/controllers/newsriver.ts | 17 + functions/src/models/api.ts | 27 +- functions/src/models/newsriver.ts | 70 +++ functions/src/routes/api.ts | 168 +++++++- functions/src/routes/newsriver.ts | 51 +++ functions/src/updater.ts | 46 +- 11 files changed, 912 insertions(+), 105 deletions(-) create mode 100644 functions/src/controllers/newsriver.ts create mode 100644 functions/src/models/newsriver.ts create mode 100644 functions/src/routes/newsriver.ts diff --git a/functions/package-lock.json b/functions/package-lock.json index 9116fb9..833ca3e 100644 --- a/functions/package-lock.json +++ b/functions/package-lock.json @@ -51,16 +51,109 @@ } } }, + "@firebase/analytics": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/@firebase/analytics/-/analytics-0.3.6.tgz", + "integrity": "sha512-OgBPLsLcYSLBCBLMkuOPnW2YmGo0ruVBtnZ1Yhk0y54oCtrAcm0ijuI98h0evAdA7XfHPgmfKRpke9rU2X9OQQ==", + "requires": { + "@firebase/analytics-types": "0.3.1", + "@firebase/component": "0.1.13", + "@firebase/installations": "0.4.11", + "@firebase/logger": "0.2.5", + "@firebase/util": "0.2.48", + "tslib": "1.11.1" + }, + "dependencies": { + "@firebase/component": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.13.tgz", + "integrity": "sha512-DuSIM96NQkE3Yo77IOa5BWw8VBdvCR5cbMLNiFT4X3dTU15Dm0zHjncQHt/6rQpABGNYWAfOCJmSU1v6vc3DFA==", + "requires": { + "@firebase/util": "0.2.48", + "tslib": "1.11.1" + } + }, + "@firebase/logger": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.2.5.tgz", + "integrity": "sha512-qqw3m0tWs/qrg7axTZG/QZq24DIMdSY6dGoWuBn08ddq7+GLF5HiqkRj71XznYeUUbfRq5W9C/PSFnN4JxX+WA==" + }, + "@firebase/util": { + "version": "0.2.48", + "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.2.48.tgz", + "integrity": "sha512-6Wzq6IBF//3mrMTmTQ+JmceM0PMQpxV2GVfXhZn/4sMMkkhB0MA908nPDnatoHwUKyWE3BMw+uTLkyBnkuTu5A==", + "requires": { + "tslib": "1.11.1" + } + } + } + }, + "@firebase/analytics-types": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@firebase/analytics-types/-/analytics-types-0.3.1.tgz", + "integrity": "sha512-63vVJ5NIBh/JF8l9LuPrQYSzFimk7zYHySQB4Dk9rVdJ8kV/vGQoVTvRu1UW05sEc2Ug5PqtEChtTHU+9hvPcA==" + }, + "@firebase/app": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/@firebase/app/-/app-0.6.5.tgz", + "integrity": "sha512-rhId5P4egyaVp4HaLsqxV1dJYWbjAyyTnoW8r6t2uXyUGBKUiv+tdK97abkHCo4UQwq/GvUu0Drd96Cm7nEIeA==", + "requires": { + "@firebase/app-types": "0.6.1", + "@firebase/component": "0.1.13", + "@firebase/logger": "0.2.5", + "@firebase/util": "0.2.48", + "dom-storage": "2.1.0", + "tslib": "1.11.1", + "xmlhttprequest": "1.8.0" + }, + "dependencies": { + "@firebase/component": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.13.tgz", + "integrity": "sha512-DuSIM96NQkE3Yo77IOa5BWw8VBdvCR5cbMLNiFT4X3dTU15Dm0zHjncQHt/6rQpABGNYWAfOCJmSU1v6vc3DFA==", + "requires": { + "@firebase/util": "0.2.48", + "tslib": "1.11.1" + } + }, + "@firebase/logger": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.2.5.tgz", + "integrity": "sha512-qqw3m0tWs/qrg7axTZG/QZq24DIMdSY6dGoWuBn08ddq7+GLF5HiqkRj71XznYeUUbfRq5W9C/PSFnN4JxX+WA==" + }, + "@firebase/util": { + "version": "0.2.48", + "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.2.48.tgz", + "integrity": "sha512-6Wzq6IBF//3mrMTmTQ+JmceM0PMQpxV2GVfXhZn/4sMMkkhB0MA908nPDnatoHwUKyWE3BMw+uTLkyBnkuTu5A==", + "requires": { + "tslib": "1.11.1" + } + } + } + }, "@firebase/app-types": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.6.1.tgz", "integrity": "sha512-L/ZnJRAq7F++utfuoTKX4CLBG5YR7tFO3PLzG1/oXXKEezJ0kRL3CMRoueBEmTCzVb/6SIs2Qlaw++uDgi5Xyg==" }, + "@firebase/auth": { + "version": "0.14.6", + "resolved": "https://registry.npmjs.org/@firebase/auth/-/auth-0.14.6.tgz", + "integrity": "sha512-7gaEUWhUubWBGfOXAZvpTpJqBJT9KyG83RXC6VnjSQIfNUaarHZ485WkzERil43A6KvIl+f4kHxfZShE6ZCK3A==", + "requires": { + "@firebase/auth-types": "0.10.1" + } + }, "@firebase/auth-interop-types": { "version": "0.1.5", "resolved": "https://registry.npmjs.org/@firebase/auth-interop-types/-/auth-interop-types-0.1.5.tgz", "integrity": "sha512-88h74TMQ6wXChPA6h9Q3E1Jg6TkTHep2+k63OWg3s0ozyGVMeY+TTOti7PFPzq5RhszQPQOoCi59es4MaRvgCw==" }, + "@firebase/auth-types": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/@firebase/auth-types/-/auth-types-0.10.1.tgz", + "integrity": "sha512-/+gBHb1O9x/YlG7inXfxff/6X3BPZt4zgBv4kql6HEmdzNQCodIRlEYnI+/da+lN+dha7PjaFH7C7ewMmfV7rw==" + }, "@firebase/component": { "version": "0.1.12", "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.12.tgz", @@ -92,11 +185,308 @@ "@firebase/app-types": "0.6.1" } }, + "@firebase/firestore": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/@firebase/firestore/-/firestore-1.15.0.tgz", + "integrity": "sha512-4eSBDY2hb/og8OEFZSjjzlb0y5+cWpridtQHLYsM4IPcOwxnnlE6RjGWN+UUxgKtvlkvOBdUIlTjL/YYoF+QcA==", + "requires": { + "@firebase/component": "0.1.13", + "@firebase/firestore-types": "1.11.0", + "@firebase/logger": "0.2.5", + "@firebase/util": "0.2.48", + "@firebase/webchannel-wrapper": "0.2.41", + "@grpc/grpc-js": "0.8.1", + "@grpc/proto-loader": "^0.5.0", + "tslib": "1.11.1" + }, + "dependencies": { + "@firebase/component": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.13.tgz", + "integrity": "sha512-DuSIM96NQkE3Yo77IOa5BWw8VBdvCR5cbMLNiFT4X3dTU15Dm0zHjncQHt/6rQpABGNYWAfOCJmSU1v6vc3DFA==", + "requires": { + "@firebase/util": "0.2.48", + "tslib": "1.11.1" + } + }, + "@firebase/logger": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.2.5.tgz", + "integrity": "sha512-qqw3m0tWs/qrg7axTZG/QZq24DIMdSY6dGoWuBn08ddq7+GLF5HiqkRj71XznYeUUbfRq5W9C/PSFnN4JxX+WA==" + }, + "@firebase/util": { + "version": "0.2.48", + "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.2.48.tgz", + "integrity": "sha512-6Wzq6IBF//3mrMTmTQ+JmceM0PMQpxV2GVfXhZn/4sMMkkhB0MA908nPDnatoHwUKyWE3BMw+uTLkyBnkuTu5A==", + "requires": { + "tslib": "1.11.1" + } + }, + "@grpc/grpc-js": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-0.8.1.tgz", + "integrity": "sha512-e8gSjRZnOUefsR3obOgxG9RtYW2Mw83hh7ogE2ByCdgRhoX0mdnJwBcZOami3E0l643KCTZvORFwfSEi48KFIQ==", + "requires": { + "semver": "^6.2.0" + } + } + } + }, + "@firebase/firestore-types": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/@firebase/firestore-types/-/firestore-types-1.11.0.tgz", + "integrity": "sha512-hD7+cmMUvT5OJeWVrcRkE87PPuj/0/Wic6bntCopJE1WIX/Dm117AUkHgKd3S7Ici6DLp4bdlx1MjjwWL5942w==" + }, + "@firebase/functions": { + "version": "0.4.45", + "resolved": "https://registry.npmjs.org/@firebase/functions/-/functions-0.4.45.tgz", + "integrity": "sha512-Sy0D+52bkabdapTGxPlX+1b2FH+0BEJBmboLfM7EySZV/32oI277pDYKhyp9Pm//eOLspMOpEDvJz1WK8xmQcw==", + "requires": { + "@firebase/component": "0.1.13", + "@firebase/functions-types": "0.3.17", + "@firebase/messaging-types": "0.4.5", + "isomorphic-fetch": "2.2.1", + "tslib": "1.11.1" + }, + "dependencies": { + "@firebase/component": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.13.tgz", + "integrity": "sha512-DuSIM96NQkE3Yo77IOa5BWw8VBdvCR5cbMLNiFT4X3dTU15Dm0zHjncQHt/6rQpABGNYWAfOCJmSU1v6vc3DFA==", + "requires": { + "@firebase/util": "0.2.48", + "tslib": "1.11.1" + } + }, + "@firebase/util": { + "version": "0.2.48", + "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.2.48.tgz", + "integrity": "sha512-6Wzq6IBF//3mrMTmTQ+JmceM0PMQpxV2GVfXhZn/4sMMkkhB0MA908nPDnatoHwUKyWE3BMw+uTLkyBnkuTu5A==", + "requires": { + "tslib": "1.11.1" + } + } + } + }, + "@firebase/functions-types": { + "version": "0.3.17", + "resolved": "https://registry.npmjs.org/@firebase/functions-types/-/functions-types-0.3.17.tgz", + "integrity": "sha512-DGR4i3VI55KnYk4IxrIw7+VG7Q3gA65azHnZxo98Il8IvYLr2UTBlSh72dTLlDf25NW51HqvJgYJDKvSaAeyHQ==" + }, + "@firebase/installations": { + "version": "0.4.11", + "resolved": "https://registry.npmjs.org/@firebase/installations/-/installations-0.4.11.tgz", + "integrity": "sha512-ri+8O6vZPF0JKXboMzYFAbN7rn0OeUKLeMuCWdKZGJZD8NS8NRk/YvGRBa+IrkrwBeNNA/bMBUTEnhjN4CdVgQ==", + "requires": { + "@firebase/component": "0.1.13", + "@firebase/installations-types": "0.3.4", + "@firebase/util": "0.2.48", + "idb": "3.0.2", + "tslib": "1.11.1" + }, + "dependencies": { + "@firebase/component": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.13.tgz", + "integrity": "sha512-DuSIM96NQkE3Yo77IOa5BWw8VBdvCR5cbMLNiFT4X3dTU15Dm0zHjncQHt/6rQpABGNYWAfOCJmSU1v6vc3DFA==", + "requires": { + "@firebase/util": "0.2.48", + "tslib": "1.11.1" + } + }, + "@firebase/util": { + "version": "0.2.48", + "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.2.48.tgz", + "integrity": "sha512-6Wzq6IBF//3mrMTmTQ+JmceM0PMQpxV2GVfXhZn/4sMMkkhB0MA908nPDnatoHwUKyWE3BMw+uTLkyBnkuTu5A==", + "requires": { + "tslib": "1.11.1" + } + } + } + }, + "@firebase/installations-types": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@firebase/installations-types/-/installations-types-0.3.4.tgz", + "integrity": "sha512-RfePJFovmdIXb6rYwtngyxuEcWnOrzdZd9m7xAW0gRxDIjBT20n3BOhjpmgRWXo/DAxRmS7bRjWAyTHY9cqN7Q==" + }, "@firebase/logger": { "version": "0.2.4", "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.2.4.tgz", "integrity": "sha512-akHkOU7izYB1okp/B5sxClGjjw6KvZdSHyjNM5pKd67Zg5W6PsbkI/GFNv21+y6LkUkJwDRbdeDgJoYXWT3mMA==" }, + "@firebase/messaging": { + "version": "0.6.17", + "resolved": "https://registry.npmjs.org/@firebase/messaging/-/messaging-0.6.17.tgz", + "integrity": "sha512-wwAn2HrklhBxHpk5UpudJ0wCrlUC9ovFJ88cSOALf82po423IOwR5ijq1z2zKzZiz4D1dLv7rJIqZ0N1MZ/Giw==", + "requires": { + "@firebase/component": "0.1.13", + "@firebase/installations": "0.4.11", + "@firebase/messaging-types": "0.4.5", + "@firebase/util": "0.2.48", + "idb": "3.0.2", + "tslib": "1.11.1" + }, + "dependencies": { + "@firebase/component": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.13.tgz", + "integrity": "sha512-DuSIM96NQkE3Yo77IOa5BWw8VBdvCR5cbMLNiFT4X3dTU15Dm0zHjncQHt/6rQpABGNYWAfOCJmSU1v6vc3DFA==", + "requires": { + "@firebase/util": "0.2.48", + "tslib": "1.11.1" + } + }, + "@firebase/util": { + "version": "0.2.48", + "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.2.48.tgz", + "integrity": "sha512-6Wzq6IBF//3mrMTmTQ+JmceM0PMQpxV2GVfXhZn/4sMMkkhB0MA908nPDnatoHwUKyWE3BMw+uTLkyBnkuTu5A==", + "requires": { + "tslib": "1.11.1" + } + } + } + }, + "@firebase/messaging-types": { + "version": "0.4.5", + "resolved": "https://registry.npmjs.org/@firebase/messaging-types/-/messaging-types-0.4.5.tgz", + "integrity": "sha512-sux4fgqr/0KyIxqzHlatI04Ajs5rc3WM+WmtCpxrKP1E5Bke8xu/0M+2oy4lK/sQ7nov9z15n3iltAHCgTRU3Q==" + }, + "@firebase/performance": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/@firebase/performance/-/performance-0.3.6.tgz", + "integrity": "sha512-AB+ohBYgF8fe9FacDAcwJaBLRrXgt93no6Pj14xzQ4oX31dQpPrWZdFfNYEUZRU1Gnb/fqWlCaBTObzUXD5cag==", + "requires": { + "@firebase/component": "0.1.13", + "@firebase/installations": "0.4.11", + "@firebase/logger": "0.2.5", + "@firebase/performance-types": "0.0.13", + "@firebase/util": "0.2.48", + "tslib": "1.11.1" + }, + "dependencies": { + "@firebase/component": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.13.tgz", + "integrity": "sha512-DuSIM96NQkE3Yo77IOa5BWw8VBdvCR5cbMLNiFT4X3dTU15Dm0zHjncQHt/6rQpABGNYWAfOCJmSU1v6vc3DFA==", + "requires": { + "@firebase/util": "0.2.48", + "tslib": "1.11.1" + } + }, + "@firebase/logger": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.2.5.tgz", + "integrity": "sha512-qqw3m0tWs/qrg7axTZG/QZq24DIMdSY6dGoWuBn08ddq7+GLF5HiqkRj71XznYeUUbfRq5W9C/PSFnN4JxX+WA==" + }, + "@firebase/util": { + "version": "0.2.48", + "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.2.48.tgz", + "integrity": "sha512-6Wzq6IBF//3mrMTmTQ+JmceM0PMQpxV2GVfXhZn/4sMMkkhB0MA908nPDnatoHwUKyWE3BMw+uTLkyBnkuTu5A==", + "requires": { + "tslib": "1.11.1" + } + } + } + }, + "@firebase/performance-types": { + "version": "0.0.13", + "resolved": "https://registry.npmjs.org/@firebase/performance-types/-/performance-types-0.0.13.tgz", + "integrity": "sha512-6fZfIGjQpwo9S5OzMpPyqgYAUZcFzZxHFqOyNtorDIgNXq33nlldTL/vtaUZA8iT9TT5cJlCrF/jthKU7X21EA==" + }, + "@firebase/polyfill": { + "version": "0.3.36", + "resolved": "https://registry.npmjs.org/@firebase/polyfill/-/polyfill-0.3.36.tgz", + "integrity": "sha512-zMM9oSJgY6cT2jx3Ce9LYqb0eIpDE52meIzd/oe/y70F+v9u1LDqk5kUF5mf16zovGBWMNFmgzlsh6Wj0OsFtg==", + "requires": { + "core-js": "3.6.5", + "promise-polyfill": "8.1.3", + "whatwg-fetch": "2.0.4" + }, + "dependencies": { + "whatwg-fetch": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-2.0.4.tgz", + "integrity": "sha512-dcQ1GWpOD/eEQ97k66aiEVpNnapVj90/+R+SXTPYGHpYBBypfKJEQjLrvMZ7YXbKm21gXd4NcuxUTjiv1YtLng==" + } + } + }, + "@firebase/remote-config": { + "version": "0.1.22", + "resolved": "https://registry.npmjs.org/@firebase/remote-config/-/remote-config-0.1.22.tgz", + "integrity": "sha512-xX/b+euI/RP1qAWqNI5YkZ4VFNL00CI7jBJmPzoBbSl1vVglR1ya7u1fbC5SeowxnD1/0QIOYbe5sdnqjmLsyg==", + "requires": { + "@firebase/component": "0.1.13", + "@firebase/installations": "0.4.11", + "@firebase/logger": "0.2.5", + "@firebase/remote-config-types": "0.1.9", + "@firebase/util": "0.2.48", + "tslib": "1.11.1" + }, + "dependencies": { + "@firebase/component": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.13.tgz", + "integrity": "sha512-DuSIM96NQkE3Yo77IOa5BWw8VBdvCR5cbMLNiFT4X3dTU15Dm0zHjncQHt/6rQpABGNYWAfOCJmSU1v6vc3DFA==", + "requires": { + "@firebase/util": "0.2.48", + "tslib": "1.11.1" + } + }, + "@firebase/logger": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.2.5.tgz", + "integrity": "sha512-qqw3m0tWs/qrg7axTZG/QZq24DIMdSY6dGoWuBn08ddq7+GLF5HiqkRj71XznYeUUbfRq5W9C/PSFnN4JxX+WA==" + }, + "@firebase/util": { + "version": "0.2.48", + "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.2.48.tgz", + "integrity": "sha512-6Wzq6IBF//3mrMTmTQ+JmceM0PMQpxV2GVfXhZn/4sMMkkhB0MA908nPDnatoHwUKyWE3BMw+uTLkyBnkuTu5A==", + "requires": { + "tslib": "1.11.1" + } + } + } + }, + "@firebase/remote-config-types": { + "version": "0.1.9", + "resolved": "https://registry.npmjs.org/@firebase/remote-config-types/-/remote-config-types-0.1.9.tgz", + "integrity": "sha512-G96qnF3RYGbZsTRut7NBX0sxyczxt1uyCgXQuH/eAfUCngxjEGcZQnBdy6mvSdqdJh5mC31rWPO4v9/s7HwtzA==" + }, + "@firebase/storage": { + "version": "0.3.35", + "resolved": "https://registry.npmjs.org/@firebase/storage/-/storage-0.3.35.tgz", + "integrity": "sha512-kS+P5X3lla9bpeWVwmRzJ5atMDPlhLPa8jgutN9vWXWfVnlj82U8VqeAqWc8ndHumHiV0TYLDk9DdGfs6rFL3w==", + "requires": { + "@firebase/component": "0.1.13", + "@firebase/storage-types": "0.3.12", + "@firebase/util": "0.2.48", + "tslib": "1.11.1" + }, + "dependencies": { + "@firebase/component": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.13.tgz", + "integrity": "sha512-DuSIM96NQkE3Yo77IOa5BWw8VBdvCR5cbMLNiFT4X3dTU15Dm0zHjncQHt/6rQpABGNYWAfOCJmSU1v6vc3DFA==", + "requires": { + "@firebase/util": "0.2.48", + "tslib": "1.11.1" + } + }, + "@firebase/util": { + "version": "0.2.48", + "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.2.48.tgz", + "integrity": "sha512-6Wzq6IBF//3mrMTmTQ+JmceM0PMQpxV2GVfXhZn/4sMMkkhB0MA908nPDnatoHwUKyWE3BMw+uTLkyBnkuTu5A==", + "requires": { + "tslib": "1.11.1" + } + } + } + }, + "@firebase/storage-types": { + "version": "0.3.12", + "resolved": "https://registry.npmjs.org/@firebase/storage-types/-/storage-types-0.3.12.tgz", + "integrity": "sha512-DDV6Fs6aYoGw3w/zZZTkqiipxihnsvHf6znbeZYjIIHit3tr1uLJdGPDPiCTfZcTGPpg2ux6ZmvNDvVgJdHALw==" + }, "@firebase/util": { "version": "0.2.47", "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.2.47.tgz", @@ -105,6 +495,11 @@ "tslib": "1.11.1" } }, + "@firebase/webchannel-wrapper": { + "version": "0.2.41", + "resolved": "https://registry.npmjs.org/@firebase/webchannel-wrapper/-/webchannel-wrapper-0.2.41.tgz", + "integrity": "sha512-XcdMT5PSZHiuf7LJIhzKIe+RyYa25S3LHRRvLnZc6iFjwXkrSDJ8J/HWO6VT8d2ZTbawp3VcLEjRF/VN8glCrA==" + }, "@google-cloud/common": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/@google-cloud/common/-/common-2.4.0.tgz", @@ -220,7 +615,6 @@ "version": "0.5.4", "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.5.4.tgz", "integrity": "sha512-HTM4QpI9B2XFkPz7pjwMyMgZchJ93TVkL3kWPW8GDMDKYxsMnmf4w2TNMJK7+KNiYHS5cJrCEAFlF+AwtXWVPA==", - "optional": true, "requires": { "lodash.camelcase": "^4.3.0", "protobufjs": "^6.8.6" @@ -229,32 +623,27 @@ "@protobufjs/aspromise": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", - "integrity": "sha1-m4sMxmPWaafY9vXQiToU00jzD78=", - "optional": true + "integrity": "sha1-m4sMxmPWaafY9vXQiToU00jzD78=" }, "@protobufjs/base64": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", - "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", - "optional": true + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==" }, "@protobufjs/codegen": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", - "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==", - "optional": true + "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==" }, "@protobufjs/eventemitter": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", - "integrity": "sha1-NVy8mLr61ZePntCV85diHx0Ga3A=", - "optional": true + "integrity": "sha1-NVy8mLr61ZePntCV85diHx0Ga3A=" }, "@protobufjs/fetch": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", "integrity": "sha1-upn7WYYUr2VwDBYZ/wbUVLDYTEU=", - "optional": true, "requires": { "@protobufjs/aspromise": "^1.1.1", "@protobufjs/inquire": "^1.1.0" @@ -263,32 +652,27 @@ "@protobufjs/float": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", - "integrity": "sha1-Xp4avctz/Ap8uLKR33jIy9l7h9E=", - "optional": true + "integrity": "sha1-Xp4avctz/Ap8uLKR33jIy9l7h9E=" }, "@protobufjs/inquire": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", - "integrity": "sha1-/yAOPnzyQp4tyvwRQIKOjMY48Ik=", - "optional": true + "integrity": "sha1-/yAOPnzyQp4tyvwRQIKOjMY48Ik=" }, "@protobufjs/path": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", - "integrity": "sha1-bMKyDFya1q0NzP0hynZz2Nf79o0=", - "optional": true + "integrity": "sha1-bMKyDFya1q0NzP0hynZz2Nf79o0=" }, "@protobufjs/pool": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", - "integrity": "sha1-Cf0V8tbTq/qbZbw2ZQbWrXhG/1Q=", - "optional": true + "integrity": "sha1-Cf0V8tbTq/qbZbw2ZQbWrXhG/1Q=" }, "@protobufjs/utf8": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", - "integrity": "sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA=", - "optional": true + "integrity": "sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA=" }, "@tootallnate/once": { "version": "1.1.2", @@ -352,8 +736,7 @@ "@types/long": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.1.tgz", - "integrity": "sha512-5tXH6Bx/kNGd3MgffdmP4dy2Z+G4eaXw0SE81Tq3BNadtnMR5/ySMzX4SLEzHJzSmPNn4HIdpQsBvXMUykr58w==", - "optional": true + "integrity": "sha512-5tXH6Bx/kNGd3MgffdmP4dy2Z+G4eaXw0SE81Tq3BNadtnMR5/ySMzX4SLEzHJzSmPNn4HIdpQsBvXMUykr58w==" }, "@types/mime": { "version": "2.0.2", @@ -735,6 +1118,11 @@ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" }, + "core-js": { + "version": "3.6.5", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.6.5.tgz", + "integrity": "sha512-vZVEEwZoIsI+vPEuoF9Iqf5H7/M3eeQqWlQnYa8FSKKePuYTf5MWnxb5SDAzCa60b3JBRS5g9b+Dq7b1y/RCrA==" + }, "core-util-is": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", @@ -838,6 +1226,11 @@ "resolved": "https://registry.npmjs.org/doctypes/-/doctypes-1.1.0.tgz", "integrity": "sha1-6oCxBqh1OHdOijpKWv4pPeSJ4Kk=" }, + "dom-storage": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/dom-storage/-/dom-storage-2.1.0.tgz", + "integrity": "sha512-g6RpyWXzl0RR6OTElHKBl7nwnK87GUyZMYC7JWsB/IA73vpqK2K6LT39x4VepLxlSsWBFrPVLnsSR5Jyty0+2Q==" + }, "dot-prop": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.2.0.tgz", @@ -906,6 +1299,14 @@ "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" }, + "encoding": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.12.tgz", + "integrity": "sha1-U4tm8+5izRq1HsMjgp0flIDHS+s=", + "requires": { + "iconv-lite": "~0.4.13" + } + }, "end-of-stream": { "version": "1.4.4", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", @@ -1098,6 +1499,65 @@ } } }, + "firebase": { + "version": "7.15.0", + "resolved": "https://registry.npmjs.org/firebase/-/firebase-7.15.0.tgz", + "integrity": "sha512-0t8w/TT+230/n/XWBw9jGMApCkIEb5K1b+6t4R+SAMOZHMJZvoAwMcwgQXoYeUBFOJOQpgDhIZK8PzApq4iUXw==", + "requires": { + "@firebase/analytics": "0.3.6", + "@firebase/app": "0.6.5", + "@firebase/app-types": "0.6.1", + "@firebase/auth": "0.14.6", + "@firebase/database": "0.6.4", + "@firebase/firestore": "1.15.0", + "@firebase/functions": "0.4.45", + "@firebase/installations": "0.4.11", + "@firebase/messaging": "0.6.17", + "@firebase/performance": "0.3.6", + "@firebase/polyfill": "0.3.36", + "@firebase/remote-config": "0.1.22", + "@firebase/storage": "0.3.35", + "@firebase/util": "0.2.48" + }, + "dependencies": { + "@firebase/component": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.13.tgz", + "integrity": "sha512-DuSIM96NQkE3Yo77IOa5BWw8VBdvCR5cbMLNiFT4X3dTU15Dm0zHjncQHt/6rQpABGNYWAfOCJmSU1v6vc3DFA==", + "requires": { + "@firebase/util": "0.2.48", + "tslib": "1.11.1" + } + }, + "@firebase/database": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/@firebase/database/-/database-0.6.4.tgz", + "integrity": "sha512-m3jaElEEXhr3a9D+M/kbDuRCQG5EmrnSqyEq7iNk3s5ankIrALid0AYm2RZF764F/DIeMFtAzng4EyyEqsaQlQ==", + "requires": { + "@firebase/auth-interop-types": "0.1.5", + "@firebase/component": "0.1.13", + "@firebase/database-types": "0.5.1", + "@firebase/logger": "0.2.5", + "@firebase/util": "0.2.48", + "faye-websocket": "0.11.3", + "tslib": "1.11.1" + } + }, + "@firebase/logger": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.2.5.tgz", + "integrity": "sha512-qqw3m0tWs/qrg7axTZG/QZq24DIMdSY6dGoWuBn08ddq7+GLF5HiqkRj71XznYeUUbfRq5W9C/PSFnN4JxX+WA==" + }, + "@firebase/util": { + "version": "0.2.48", + "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.2.48.tgz", + "integrity": "sha512-6Wzq6IBF//3mrMTmTQ+JmceM0PMQpxV2GVfXhZn/4sMMkkhB0MA908nPDnatoHwUKyWE3BMw+uTLkyBnkuTu5A==", + "requires": { + "tslib": "1.11.1" + } + } + } + }, "firebase-admin": { "version": "8.12.1", "resolved": "https://registry.npmjs.org/firebase-admin/-/firebase-admin-8.12.1.tgz", @@ -1530,6 +1990,11 @@ "safer-buffer": ">= 2.1.2 < 3" } }, + "idb": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/idb/-/idb-3.0.2.tgz", + "integrity": "sha512-+FLa/0sTXqyux0o6C+i2lOR0VoS60LU/jzUo5xjfY6+7sEEgy4Gz1O7yFBXvjd7N0NyIGWIRg8DcQSLEG+VSPw==" + }, "imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", @@ -1692,6 +2157,31 @@ "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", "optional": true }, + "isomorphic-fetch": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz", + "integrity": "sha1-YRrhrPFPXoH3KVB0coGf6XM1WKk=", + "requires": { + "node-fetch": "^1.0.1", + "whatwg-fetch": ">=0.10.0" + }, + "dependencies": { + "is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=" + }, + "node-fetch": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-1.7.3.tgz", + "integrity": "sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ==", + "requires": { + "encoding": "^0.1.11", + "is-stream": "^1.0.1" + } + } + } + }, "js-stringify": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/js-stringify/-/js-stringify-1.0.2.tgz", @@ -1804,8 +2294,7 @@ "lodash.camelcase": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", - "integrity": "sha1-soqmKIorn8ZRA1x3EfZathkDMaY=", - "optional": true + "integrity": "sha1-soqmKIorn8ZRA1x3EfZathkDMaY=" }, "lodash.has": { "version": "4.5.2", @@ -1851,8 +2340,7 @@ "long": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", - "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==", - "optional": true + "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" }, "lru-cache": { "version": "5.1.1", @@ -2109,11 +2597,15 @@ "asap": "~2.0.3" } }, + "promise-polyfill": { + "version": "8.1.3", + "resolved": "https://registry.npmjs.org/promise-polyfill/-/promise-polyfill-8.1.3.tgz", + "integrity": "sha512-MG5r82wBzh7pSKDRa9y+vllNHz3e3d4CNj1PQE4BQYxLme0gKYYBm9YENq+UkEikyZ0XbiGWxYlVw3Rl9O/U8g==" + }, "protobufjs": { "version": "6.9.0", "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.9.0.tgz", "integrity": "sha512-LlGVfEWDXoI/STstRDdZZKb/qusoAWUnmLg9R8OLSO473mBLWHowx8clbX5/+mKDEI+v7GzjoK9tRPZMMcoTrg==", - "optional": true, "requires": { "@protobufjs/aspromise": "^1.1.2", "@protobufjs/base64": "^1.1.2", @@ -2133,8 +2625,7 @@ "@types/node": { "version": "13.13.9", "resolved": "https://registry.npmjs.org/@types/node/-/node-13.13.9.tgz", - "integrity": "sha512-EPZBIGed5gNnfWCiwEIwTE2Jdg4813odnG8iNPMQGrqVxrI+wL68SPtPeCX+ZxGBaA6pKAVc6jaKgP/Q0QzfdQ==", - "optional": true + "integrity": "sha512-EPZBIGed5gNnfWCiwEIwTE2Jdg4813odnG8iNPMQGrqVxrI+wL68SPtPeCX+ZxGBaA6pKAVc6jaKgP/Q0QzfdQ==" } } }, @@ -2386,8 +2877,7 @@ "semver": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "optional": true + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" }, "send": { "version": "0.17.1", @@ -2752,6 +3242,11 @@ "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.3.tgz", "integrity": "sha512-nqHUnMXmBzT0w570r2JpJxfiSD1IzoI+HGVdd3aZ0yNi3ngvQ4jv1dtHt5VGxfI2yj5yqImPhOK4vmIh2xMbGg==" }, + "whatwg-fetch": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.0.0.tgz", + "integrity": "sha512-9GSJUgz1D4MfyKU7KRqwOjXCXTqWdFNvEr7eUBYchQiVc744mqK/MzXPNR2WsPkmkOa4ywfg8C2n8h+13Bey1Q==" + }, "which-boxed-primitive": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.1.tgz", @@ -2825,6 +3320,11 @@ "integrity": "sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==", "optional": true }, + "xmlhttprequest": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/xmlhttprequest/-/xmlhttprequest-1.8.0.tgz", + "integrity": "sha1-Z/4HXFwk/vOfnWX197f+dRcZaPw=" + }, "xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", diff --git a/functions/package.json b/functions/package.json index fb955f7..cdb7f18 100644 --- a/functions/package.json +++ b/functions/package.json @@ -10,9 +10,6 @@ "logs": "firebase functions:log", "server": "npm run build && node lib/bin/www.js" }, - "engines": { - "node": "8" - }, "main": "lib/bin/www.js", "dependencies": { "body-parser": "^1.19.0", diff --git a/functions/src/app.ts b/functions/src/app.ts index a674cc5..748ecb9 100644 --- a/functions/src/app.ts +++ b/functions/src/app.ts @@ -1,10 +1,11 @@ import * as path from 'path'; import * as logger from 'morgan'; import * as express from 'express'; -import * as apiRouter from './routes/api'; import * as createError from 'http-errors'; +import router = require("./routes/newsriver"); -export const app = express(); + +const app = express(); // view engine setup app.set('views', path.join(__dirname, 'views')); @@ -26,10 +27,7 @@ app.use(express.static(path.join(__dirname, 'public'))); ); * --------------- */ - -// @ts-ignore -app.use(apiRouter); - +app.use(router); // catch 404 and forward to error handler app.use((req, res, next) => next(createError(404))); @@ -41,3 +39,5 @@ app.use((err, req, res, next) => { res.status(err.status || 500); res.render('error'); }); + +export = app; diff --git a/functions/src/bin/www.ts b/functions/src/bin/www.ts index 9e84e76..941ed94 100644 --- a/functions/src/bin/www.ts +++ b/functions/src/bin/www.ts @@ -1,11 +1,11 @@ -import {app} from '../app'; +const app = require('../app'); import * as http from 'http'; -import * as functions from 'firebase-functions'; /** * Get port from environment and store in Express */ const port = normalizePort(process.env.PORT || '3000'); +app.set('port', port); /** * Create the http server @@ -15,9 +15,9 @@ const server = http.createServer(app); /** * Listen on a provided port, on all network interfaces */ +server.listen(port) server.on('error', onError); server.on('listening', onListening); -export const webApi = functions.https.onRequest(app); /** * Normalize a port into a number, string or false diff --git a/functions/src/controllers/api.ts b/functions/src/controllers/api.ts index f98687e..fc2d383 100644 --- a/functions/src/controllers/api.ts +++ b/functions/src/controllers/api.ts @@ -1,7 +1,50 @@ -import {api} from '../models/api'; +// import {Api} from '../models/api'; +// import api = require('../models/api'); +// import {api} from '../models/api'; +// import apiModels = require("../models/api"); -export async function getNewsByLanguage(req, res) { - const language = req.query.lang; - const data = await api.newsForLanguage(language); - return res.json(data); -} \ No newline at end of file +import {Request, Response} from 'express'; +import {Api} from "../models/api"; + +/*export function getNewsByLanguage(req: Request, res: Response) { + console.log('Getting news by language'); + const language = req.query.lang as string; + apiModels.api.newsForLanguage(language) + .then(data => res.json(data)) + .catch(err => { + console.error(err); + res.sendStatus(304); + }); +}*/ + +export function ApiController(api: Api) { + return { + getNewsByLanguage(req: Request, res: Response) { + const language = req.query.lang as string; + api.newsForLanguage(language) + .then(data => res.json(data)) + .catch(err => { + console.error(err); + res.sendStatus(500); + }); + } + } +} + +/*export class ApiController { + api: Api + + constructor(api: Api) { + this.api = api; + } + + getNewsByLanguage(req: Request, res: Response) { + const language = req.query.lang as string; + this.api.newsForLanguage(language) + .then(data => res.json(data)) + .catch(err => { + console.error(err); + res.sendStatus(500); + }); + } +}*/ diff --git a/functions/src/controllers/newsriver.ts b/functions/src/controllers/newsriver.ts new file mode 100644 index 0000000..538845b --- /dev/null +++ b/functions/src/controllers/newsriver.ts @@ -0,0 +1,17 @@ +export import apiModel = require("../models/newsriver"); +import {Request, Response} from "express"; + + +export async function queryNewsForLanguage(req: Request, res: Response) { + const language = req.query.lang as string; + apiModel.newsForLanguage(language) + .then(newsData => res.json(newsData)) + .catch(err => { + if (err !instanceof RangeError) { + console.error(`Error while getting news data: ${err}`); + res.sendStatus(500); + } + else + res.status(403).send(err); + }) +} \ No newline at end of file diff --git a/functions/src/models/api.ts b/functions/src/models/api.ts index 3b88c19..44ddc4e 100644 --- a/functions/src/models/api.ts +++ b/functions/src/models/api.ts @@ -1,12 +1,12 @@ -import * as functions from 'firebase-functions'; -import * as admin from 'firebase-admin'; +// import * as functions from 'firebase-functions'; +// import * as admin from 'firebase-admin'; import {ProjectProperties} from '../interfaces/projectProperties'; import {RemoteConfigData} from '../rcdata'; import {Updater} from '../updater'; import {NewsriverData} from '../newsriver'; -class Api { +export class Api { properties: ProjectProperties; remoteConfig: RemoteConfigData; languages: Array; @@ -62,25 +62,14 @@ class Api { } } } - -const sdkInfo = functions.config().sdk; +/*const serviceAccount = require('../../handwashing-firebase-adminsdk.json'); const firebaseApp = admin.initializeApp({ - projectId: sdkInfo.project_id, - credential: admin.credential.cert({ - projectId: sdkInfo.project_id, - clientEmail: sdkInfo.client_email, - privateKey: sdkInfo.private_key - }), + credential: admin.credential.cert(serviceAccount), databaseURL: 'https://handwashing.firebaseio.com' -}); +});*/ +/*const firebaseApp = admin.initializeApp(); const projectProperties: ProjectProperties = { collection: 'news', database: admin.firestore(), authToken: functions.config().newsriver.key -}; -const projectLanguages = ['es', 'en']; -const remoteConfigConnector = new RemoteConfigData(firebaseApp); - -export const api = new Api(projectProperties, remoteConfigConnector, projectLanguages) -api.init() - .catch(reason => console.error(`API initialization failed: ${reason}`)); \ No newline at end of file +};*/ \ No newline at end of file diff --git a/functions/src/models/newsriver.ts b/functions/src/models/newsriver.ts new file mode 100644 index 0000000..a6c42b8 --- /dev/null +++ b/functions/src/models/newsriver.ts @@ -0,0 +1,70 @@ +import admin = require('firebase-admin'); +import functions = require("firebase-functions"); +import {ProjectProperties} from "../interfaces/projectProperties"; +import {Updater} from "../updater"; +import {RemoteConfigData} from "../rcdata"; +import {NewsriverData} from "../newsriver"; + + +const serviceAccount = require('../../handwashing-firebase-adminsdk.json'); +export const firebaseApp = admin.initializeApp({ + credential: admin.credential.cert(serviceAccount), + databaseURL: 'https://handwashing.firebaseio.com' +}); +const languages = new Set(['es', 'en']); +const projectProperties: ProjectProperties = { + collection: 'news', + database: admin.firestore(), + authToken: functions.config().newsriver.key +} +const updaters: Record = {}; +const remoteConfig = new RemoteConfigData(firebaseApp); +const timers = new Set(); +let initCalled = false; + +export async function initialize() { + initCalled = true; + for (const language of languages) { + const terms = await remoteConfig.getSearchTermsForLanguage(language); + const updater = new Updater( + projectProperties.database, + `${projectProperties.collection}_${language}`, + terms, + projectProperties.authToken, + language + ); + console.log('Created updater object'); + updaters[language] = updater; + } + remoteConfig.subscribeUpdaters(updaters); +} + +export async function scheduleUpdates() { + if (!initCalled) + throw new Error('`initialize` not called'); + for (const language of languages) { + timers.add(updaters[language].schedule()); + } +} + +export async function newsForLanguage(language: string) { + if (!initCalled) + throw new Error('`initialize` not called'); + if (language !in languages) + throw new RangeError(`invalid language "${language}"`); + const collection = updaters[language].collection; + const snapshot = await collection.get(); + const data = new Array(); + snapshot.forEach(item => { + if (item.data() !== null) + data.push(item.data() as NewsriverData) + }); + return data; +} + +export async function stopScheduling() { + if (!initCalled) + throw new Error('`initialize` not called'); + for (const timer of timers) + clearInterval(timer); +} diff --git a/functions/src/routes/api.ts b/functions/src/routes/api.ts index ab2278a..362378d 100644 --- a/functions/src/routes/api.ts +++ b/functions/src/routes/api.ts @@ -1,10 +1,149 @@ -import * as express from 'express'; import * as admin from 'firebase-admin'; -import * as apiController from '../controllers/api'; +// import controller = require("../controllers/api"); +import {Router} from "express"; +// import get = Reflect.get; -const router = express.Router(); +/*const router = Router(); -router.use('/api/v1', (req, res, next) => { +router.get('/api/v1', (req, res, next) => { + try { + const language = req.query.lang; + const tokenId = req.get('Authorization').split('Bearer')[0]; + admin.auth().verifyIdToken(tokenId) + .then(_ => { + if (language === undefined) + res.status(403).send('lang must be given [?lang=...]'); + else next(); + }) + .catch(e => { + console.log('User unauthorized'); + next(); + // res.status(401).send(e) + }) + } catch (e) { + console.error(e); + res.status(401).send(e); + } +}); +router.get('/api/v1', controller.getNewsByLanguage);*/ +// app: admin.app.App, apiController: { getNewsByLanguage: (req, res) => any } +export function ApiRoutes(apiController: { getNewsByLanguage: (req, res) => any }) { + const router = Router(); + router.get('/api/v1', (req, res, next) => { + try { + const language = req.query.lang; + const tokenId = req.get('Authorization').split('Bearer')[0]; + admin.auth().verifyIdToken(tokenId) + .then(_ => { + if (language === undefined) + res.status(403).send('lang must be given [?lang=...]'); + else next(); + }) + .catch(e => { + console.log('User unauthorized'); + next(); + // res.status(401).send(e) + }) + } catch (e) { + console.error(e); + res.status(401).send(e); + } + }); + router.get('/api/v1', apiController.getNewsByLanguage); + + return {router}; +} + +/*export class ApiRouter { + router: Router + // firebaseApp: admin.app.App + + constructor(app: admin.app.App, apiController: ApiController) { + // this.firebaseApp = app; + this.router = Router(); + + this.router.get('/api/v1', (req, res, next) => { + try { + const language = req.query.lang; + const tokenId = req.get('Authorization').split('Bearer')[0]; + admin.auth(app).verifyIdToken(tokenId) + .then(_ => { + if (language === undefined) + res.status(403).send('lang must be given [?lang=...]'); + else next(); + }) + .catch(e => { + console.log('User unauthorized'); + next(); + // res.status(401).send(e) + }) + } catch (e) { + console.error(e); + res.status(401).send(e); + } + }); + // this.router.get('/api/v1', apiController.getNewsByLanguage); + } +}*/ + +// export = router; + +/*export class ApiRouter { + router: Router; + firebaseApp: admin.app.App; + + constructor(firebaseApp: admin.app.App, apiController: ApiController) { + this.router = Router(); + this.firebaseApp = firebaseApp; + // this.router.use('/api/v1', apiController.getNewsByLanguage); + this.router.use('/update', (req: Request, res: Response) => { + try { + apiController.api.updaters['en'].request() + .then(apiData => { + apiController.api.updaters['en'].updateData(apiData) + .then(_ => { + console.log("Data updated"); + res.json(apiData); + }) + .catch(err => { + console.error(err); + res.sendStatus(500); + }) + }) + .catch(e => { + console.error(e); + res.sendStatus(500); + }); + } catch (e) { + console.error(e); + res.sendStatus(500); + } + }); + } + + async init(): Promise { + this.router.use('/api/v1', (req: Request, res: Response, next: NextFunction) => { + try { + const language = req.query.lang; + const tokenId = req.get('Authorization').split('Bearer')[1]; + admin.auth(this.firebaseApp).verifyIdToken(tokenId) + .then(_ => { + if (language === undefined) + res.status(403).send('lang must be given [?lang=...]'); + else next(); + }) + .catch(err => res.status(401).send(err)); + } catch (e) { + res.status(401).send(e); + } + }); + return this.router; + } +}*/ + +// export const router = express.Router(); + +/*router.use('/api/v1', (req, res, next) => { try { const language = req.query.lang; const tokenId = req.get('Authorization').split('Bearer ')[1]; @@ -18,7 +157,22 @@ router.use('/api/v1', (req, res, next) => { } catch (e) { res.status(401).send(e); } -}); -router.use('/api/v1', apiController.getNewsByLanguage); +});*/ +// router.use('/api/v1', getNewsByLanguage); +// router.use('/update', (req, res) => { +// api.updaters['es'].request() +// .then(apiData => { +// api.updaters['es'].updateData(apiData) +// .then(() => { +// console.log("Data was updated"); +// res.json(apiData); +// }) +// .catch((err) => { +// console.error(err); +// res.status(304).send(err); +// }); +// }) +// .catch((err) => console.error(err)); +// }); -module.exports = router; +// module.exports = router; diff --git a/functions/src/routes/newsriver.ts b/functions/src/routes/newsriver.ts new file mode 100644 index 0000000..2ab181b --- /dev/null +++ b/functions/src/routes/newsriver.ts @@ -0,0 +1,51 @@ +import apiController = require("../controllers/newsriver"); +import express = require("express"); +// import admin = require("firebase-admin"); + + +const router = express.Router(); + +apiController.apiModel.initialize() + .then(_ => apiController.apiModel.scheduleUpdates()) + .catch(e => { + console.error(e); + throw e; + }); + +router.get('/api/v1', (req, res, next) => { + try { + const language = req.query.lang; + // const tokenId = req.get('Authorization').split('Bearer')[0]; + if (language === undefined) + res.status(403).send('lang must be given [?lang=...]'); + else next(); + /*admin.auth(apiController.apiModel.firebaseApp).verifyIdToken(tokenId) + .then(_ => { + if (language === undefined) + res.status(403).send('lang must be given [?lang=...]'); + else next(); + }) + .catch(e => { + console.error('Unauthorized'); + next(); + // res.status(401).send(e); + });*/ + } catch (e) { + console.error(`Possible missing authorization header: ${e}`); + res.status(401).send(e); + } +}); +router.get('/api/v1', apiController.queryNewsForLanguage); +router.get('/close', (req, res) => { + const authToken = req.get('Authorization'); + if (authToken === undefined) + return res.sendStatus(403); + if (authToken === process.env.ADMIN_TOKEN) { + return apiController.apiModel.stopScheduling() + .then(_ => res.sendStatus(200)) + .catch(_ => res.sendStatus(200)); + } else + return res.sendStatus(403); +}); + +export = router; diff --git a/functions/src/updater.ts b/functions/src/updater.ts index 19297b6..59289ad 100644 --- a/functions/src/updater.ts +++ b/functions/src/updater.ts @@ -1,39 +1,22 @@ import {NewsriverData} from "./newsriver"; import * as firebaseHelper from 'firebase-functions-helper'; import * as fetch from 'node-fetch'; +import {Headers} from "node-fetch"; export class Updater { private readonly db: FirebaseFirestore.Firestore; private readonly collectionName: string; private readonly interval: number; - private _searchTerms: Array; + searchTerms: Array; private readonly language: string; private readonly auth: string; private _url: string | undefined; - // @ts-ignore - set url(value: string) { - this._url = value; - } - - // @ts-ignore get url(): Promise { - return new Promise(resolve => { - while (this._url === undefined) ; - return resolve(this._url); - }); - } - - set searchTerms(value: Array) { - this._searchTerms = value; - this.url = undefined; - this.buildURL() - // @ts-ignore - .then(url => this.url = url); - } - - get searchTerms() { - return this._searchTerms; + if (this._url === undefined) + return this.buildURL() + .then(url => this._url = url); + return Promise.resolve(this._url); } get collection(): FirebaseFirestore.CollectionReference { @@ -52,9 +35,6 @@ export class Updater { this.language = language; this.auth = auth; this.interval = intervalMins * 60 * 1000; - this.buildURL() - // @ts-ignore - .then(url => this.url = url); this.request() .then(response => { this.updateData(response) @@ -76,11 +56,12 @@ export class Updater { } async updateData(content: Array) { + console.log('Updating database content data'); try { content.forEach(element => { - if (!firebaseHelper.firestore.checkDocumentExists(this.db, this.collectionName, element.id)) { - firebaseHelper.firestore.createDocumentWithID(this.db, this.collectionName, element.id, element); - } + firebaseHelper.firestore.checkDocumentExists(this.db, this.collectionName, element.id) + .then(_ => firebaseHelper.firestore.createDocumentWithID(this.db, this.collectionName, element.id, element)) + console.log(`Created element with ID: ${element.id}`); }); } catch (error) { throw error; @@ -90,7 +71,12 @@ export class Updater { async request(): Promise> { try { const requestUrl = await this.url; - const response = await fetch(requestUrl, {method: 'GET', headers: {'Authorization': this.auth}}); + const response = await fetch(requestUrl, { + method: 'GET', headers: new Headers({ + 'Authorization': this.auth, + 'Content-Type': 'application/json' + }) + }); const body = await response.json(); return body as Array; } catch (e) { From 9aeb202d4b47308cbed5938828926cd7261d34d0 Mon Sep 17 00:00:00 2001 From: Javinator9889 Date: Mon, 8 Jun 2020 16:34:38 +0200 Subject: [PATCH 20/95] Created Firebase functions listener on www.ts script --- functions/.gitignore | 3 ++- functions/src/bin/www.ts | 5 +++-- functions/src/updater.ts | 15 ++++++++++++--- 3 files changed, 17 insertions(+), 6 deletions(-) diff --git a/functions/.gitignore b/functions/.gitignore index 1f66c5c..0b9c6b4 100644 --- a/functions/.gitignore +++ b/functions/.gitignore @@ -11,4 +11,5 @@ handwashing-firebase-adminsdk.json .runtimeconfig.json # Log files -*.log \ No newline at end of file +*.log +admin-sdk.json \ No newline at end of file diff --git a/functions/src/bin/www.ts b/functions/src/bin/www.ts index 941ed94..4055b63 100644 --- a/functions/src/bin/www.ts +++ b/functions/src/bin/www.ts @@ -1,11 +1,12 @@ const app = require('../app'); import * as http from 'http'; +import functions = require("firebase-functions"); + /** * Get port from environment and store in Express */ const port = normalizePort(process.env.PORT || '3000'); -app.set('port', port); /** * Create the http server @@ -15,9 +16,9 @@ const server = http.createServer(app); /** * Listen on a provided port, on all network interfaces */ -server.listen(port) server.on('error', onError); server.on('listening', onListening); +export const webApi = functions.https.onRequest(app); /** * Normalize a port into a number, string or false diff --git a/functions/src/updater.ts b/functions/src/updater.ts index 59289ad..d0f5c30 100644 --- a/functions/src/updater.ts +++ b/functions/src/updater.ts @@ -1,13 +1,13 @@ import {NewsriverData} from "./newsriver"; import * as firebaseHelper from 'firebase-functions-helper'; import * as fetch from 'node-fetch'; -import {Headers} from "node-fetch"; +import {Headers} from 'node-fetch'; export class Updater { private readonly db: FirebaseFirestore.Firestore; private readonly collectionName: string; private readonly interval: number; - searchTerms: Array; + private _searchTerms: Array; private readonly language: string; private readonly auth: string; private _url: string | undefined; @@ -19,6 +19,15 @@ export class Updater { return Promise.resolve(this._url); } + get searchTerms(): Array { + return this._searchTerms; + } + + set searchTerms(value) { + this._searchTerms = value; + this._url = undefined; + } + get collection(): FirebaseFirestore.CollectionReference { return this.db.collection(this.collectionName); } @@ -64,7 +73,7 @@ export class Updater { console.log(`Created element with ID: ${element.id}`); }); } catch (error) { - throw error; + console.error(`Unhandled error ${error}`); } } From 1136d762b182c3a99b4c69b98428142d309fd2fe Mon Sep 17 00:00:00 2001 From: Javinator9889 Date: Mon, 8 Jun 2020 16:44:56 +0200 Subject: [PATCH 21/95] Moved folders to a different workspace for separating functions --- functions/RApi/.gitignore | 15 + functions/RApi/.idea/.gitignore | 5 + .../RApi/.idea/codeStyles/codeStyleConfig.xml | 5 + .../RApi/.idea/dictionaries/javinator9889.xml | 8 + .../inspectionProfiles/Project_Default.xml | 7 + functions/RApi/.idea/jsLibraryMappings.xml | 8 + functions/RApi/.idea/misc.xml | 6 + functions/RApi/.idea/modules.xml | 8 + functions/RApi/.idea/vcs.xml | 6 + functions/RApi/.idea/watcherTasks.xml | 25 + functions/RApi/lib/tsconfig.tsbuildinfo | 2500 ++++++++++++ functions/RApi/lib/views/error.pug | 6 + functions/RApi/lib/views/layout.pug | 7 + functions/RApi/package-lock.json | 3340 +++++++++++++++++ functions/RApi/package.json | 35 + functions/RApi/src/app.ts | 43 + functions/RApi/src/bin/www.ts | 81 + functions/RApi/src/controllers/api.ts | 50 + functions/RApi/src/controllers/newsriver.ts | 17 + .../RApi/src/interfaces/projectProperties.ts | 5 + functions/RApi/src/models/api.ts | 75 + functions/RApi/src/models/newsriver.ts | 70 + functions/RApi/src/newsriver.ts | 48 + .../RApi/src/public/stylesheets/style.css | 8 + functions/RApi/src/rcdata.ts | 61 + functions/RApi/src/routes/api.ts | 178 + functions/RApi/src/routes/newsriver.ts | 51 + functions/RApi/src/updater.ts | 121 + functions/RApi/src/views/error.pug | 6 + functions/RApi/src/views/layout.pug | 7 + functions/RApi/tsconfig.json | 15 + functions/RApi/tslint.json | 115 + 32 files changed, 6932 insertions(+) create mode 100644 functions/RApi/.gitignore create mode 100644 functions/RApi/.idea/.gitignore create mode 100644 functions/RApi/.idea/codeStyles/codeStyleConfig.xml create mode 100644 functions/RApi/.idea/dictionaries/javinator9889.xml create mode 100644 functions/RApi/.idea/inspectionProfiles/Project_Default.xml create mode 100644 functions/RApi/.idea/jsLibraryMappings.xml create mode 100644 functions/RApi/.idea/misc.xml create mode 100644 functions/RApi/.idea/modules.xml create mode 100644 functions/RApi/.idea/vcs.xml create mode 100644 functions/RApi/.idea/watcherTasks.xml create mode 100644 functions/RApi/lib/tsconfig.tsbuildinfo create mode 100644 functions/RApi/lib/views/error.pug create mode 100644 functions/RApi/lib/views/layout.pug create mode 100644 functions/RApi/package-lock.json create mode 100644 functions/RApi/package.json create mode 100644 functions/RApi/src/app.ts create mode 100644 functions/RApi/src/bin/www.ts create mode 100644 functions/RApi/src/controllers/api.ts create mode 100644 functions/RApi/src/controllers/newsriver.ts create mode 100644 functions/RApi/src/interfaces/projectProperties.ts create mode 100644 functions/RApi/src/models/api.ts create mode 100644 functions/RApi/src/models/newsriver.ts create mode 100644 functions/RApi/src/newsriver.ts create mode 100644 functions/RApi/src/public/stylesheets/style.css create mode 100644 functions/RApi/src/rcdata.ts create mode 100644 functions/RApi/src/routes/api.ts create mode 100644 functions/RApi/src/routes/newsriver.ts create mode 100644 functions/RApi/src/updater.ts create mode 100644 functions/RApi/src/views/error.pug create mode 100644 functions/RApi/src/views/layout.pug create mode 100644 functions/RApi/tsconfig.json create mode 100644 functions/RApi/tslint.json diff --git a/functions/RApi/.gitignore b/functions/RApi/.gitignore new file mode 100644 index 0000000..0b9c6b4 --- /dev/null +++ b/functions/RApi/.gitignore @@ -0,0 +1,15 @@ +## Compiled JavaScript files +**/*.js +**/*.js.map + +# Typescript v1 declaration files +typings/ + +node_modules/ + +handwashing-firebase-adminsdk.json +.runtimeconfig.json + +# Log files +*.log +admin-sdk.json \ No newline at end of file diff --git a/functions/RApi/.idea/.gitignore b/functions/RApi/.idea/.gitignore new file mode 100644 index 0000000..b58b603 --- /dev/null +++ b/functions/RApi/.idea/.gitignore @@ -0,0 +1,5 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ diff --git a/functions/RApi/.idea/codeStyles/codeStyleConfig.xml b/functions/RApi/.idea/codeStyles/codeStyleConfig.xml new file mode 100644 index 0000000..a55e7a1 --- /dev/null +++ b/functions/RApi/.idea/codeStyles/codeStyleConfig.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/functions/RApi/.idea/dictionaries/javinator9889.xml b/functions/RApi/.idea/dictionaries/javinator9889.xml new file mode 100644 index 0000000..c228d45 --- /dev/null +++ b/functions/RApi/.idea/dictionaries/javinator9889.xml @@ -0,0 +1,8 @@ + + + + mins + newsriver + + + \ No newline at end of file diff --git a/functions/RApi/.idea/inspectionProfiles/Project_Default.xml b/functions/RApi/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..9c69411 --- /dev/null +++ b/functions/RApi/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/functions/RApi/.idea/jsLibraryMappings.xml b/functions/RApi/.idea/jsLibraryMappings.xml new file mode 100644 index 0000000..f100f32 --- /dev/null +++ b/functions/RApi/.idea/jsLibraryMappings.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/functions/RApi/.idea/misc.xml b/functions/RApi/.idea/misc.xml new file mode 100644 index 0000000..28a804d --- /dev/null +++ b/functions/RApi/.idea/misc.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/functions/RApi/.idea/modules.xml b/functions/RApi/.idea/modules.xml new file mode 100644 index 0000000..17059ae --- /dev/null +++ b/functions/RApi/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/functions/RApi/.idea/vcs.xml b/functions/RApi/.idea/vcs.xml new file mode 100644 index 0000000..b2bdec2 --- /dev/null +++ b/functions/RApi/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/functions/RApi/.idea/watcherTasks.xml b/functions/RApi/.idea/watcherTasks.xml new file mode 100644 index 0000000..ef17274 --- /dev/null +++ b/functions/RApi/.idea/watcherTasks.xml @@ -0,0 +1,25 @@ + + + + + + + + \ No newline at end of file diff --git a/functions/RApi/lib/tsconfig.tsbuildinfo b/functions/RApi/lib/tsconfig.tsbuildinfo new file mode 100644 index 0000000..c99f439 --- /dev/null +++ b/functions/RApi/lib/tsconfig.tsbuildinfo @@ -0,0 +1,2500 @@ +{ + "program": { + "fileInfos": { + "../node_modules/typescript/lib/lib.es5.d.ts": { + "version": "70ae6416528e68c2ee7b62892200d2ca631759943d4429f8b779b947ff1e124d", + "signature": "70ae6416528e68c2ee7b62892200d2ca631759943d4429f8b779b947ff1e124d", + "affectsGlobalScope": true + }, + "../node_modules/typescript/lib/lib.es2015.d.ts": { + "version": "dc47c4fa66b9b9890cf076304de2a9c5201e94b740cffdf09f87296d877d71f6", + "signature": "dc47c4fa66b9b9890cf076304de2a9c5201e94b740cffdf09f87296d877d71f6", + "affectsGlobalScope": false + }, + "../node_modules/typescript/lib/lib.es2016.d.ts": { + "version": "7a387c58583dfca701b6c85e0adaf43fb17d590fb16d5b2dc0a2fbd89f35c467", + "signature": "7a387c58583dfca701b6c85e0adaf43fb17d590fb16d5b2dc0a2fbd89f35c467", + "affectsGlobalScope": false + }, + "../node_modules/typescript/lib/lib.es2017.d.ts": { + "version": "8a12173c586e95f4433e0c6dc446bc88346be73ffe9ca6eec7aa63c8f3dca7f9", + "signature": "8a12173c586e95f4433e0c6dc446bc88346be73ffe9ca6eec7aa63c8f3dca7f9", + "affectsGlobalScope": false + }, + "../node_modules/typescript/lib/lib.dom.d.ts": { + "version": "9affb0a2ddc57df5b8174c0af96c288d697a262e5bc9ca1f544c999dc64a91e6", + "signature": "9affb0a2ddc57df5b8174c0af96c288d697a262e5bc9ca1f544c999dc64a91e6", + "affectsGlobalScope": true + }, + "../node_modules/typescript/lib/lib.dom.iterable.d.ts": { + "version": "fb0c09b697dc42afa84d1587e3c994a2f554d2a45635e4f0618768d16a86b69a", + "signature": "fb0c09b697dc42afa84d1587e3c994a2f554d2a45635e4f0618768d16a86b69a", + "affectsGlobalScope": true + }, + "../node_modules/typescript/lib/lib.webworker.importscripts.d.ts": { + "version": "7fac8cb5fc820bc2a59ae11ef1c5b38d3832c6d0dfaec5acdb5569137d09a481", + "signature": "7fac8cb5fc820bc2a59ae11ef1c5b38d3832c6d0dfaec5acdb5569137d09a481", + "affectsGlobalScope": true + }, + "../node_modules/typescript/lib/lib.scripthost.d.ts": { + "version": "097a57355ded99c68e6df1b738990448e0bf170e606707df5a7c0481ff2427cd", + "signature": "097a57355ded99c68e6df1b738990448e0bf170e606707df5a7c0481ff2427cd", + "affectsGlobalScope": true + }, + "../node_modules/typescript/lib/lib.es2015.core.d.ts": { + "version": "63e0cc12d0f77394094bd19e84464f9840af0071e5b9358ced30511efef1d8d2", + "signature": "63e0cc12d0f77394094bd19e84464f9840af0071e5b9358ced30511efef1d8d2", + "affectsGlobalScope": true + }, + "../node_modules/typescript/lib/lib.es2015.collection.d.ts": { + "version": "43fb1d932e4966a39a41b464a12a81899d9ae5f2c829063f5571b6b87e6d2f9c", + "signature": "43fb1d932e4966a39a41b464a12a81899d9ae5f2c829063f5571b6b87e6d2f9c", + "affectsGlobalScope": true + }, + "../node_modules/typescript/lib/lib.es2015.generator.d.ts": { + "version": "cdccba9a388c2ee3fd6ad4018c640a471a6c060e96f1232062223063b0a5ac6a", + "signature": "cdccba9a388c2ee3fd6ad4018c640a471a6c060e96f1232062223063b0a5ac6a", + "affectsGlobalScope": true + }, + "../node_modules/typescript/lib/lib.es2015.iterable.d.ts": { + "version": "42f5e41e5893da663dbf0394268f54f1da4b43dc0ddd2ea4bf471fe5361d6faf", + "signature": "42f5e41e5893da663dbf0394268f54f1da4b43dc0ddd2ea4bf471fe5361d6faf", + "affectsGlobalScope": true + }, + "../node_modules/typescript/lib/lib.es2015.promise.d.ts": { + "version": "0b7a905675e6cb4211c128f0a3aa47d414b275180a299a9aad5d3ec298abbfc4", + "signature": "0b7a905675e6cb4211c128f0a3aa47d414b275180a299a9aad5d3ec298abbfc4", + "affectsGlobalScope": true + }, + "../node_modules/typescript/lib/lib.es2015.proxy.d.ts": { + "version": "dfff68b3c34338f6b307a25d4566de15eed7973b0dc5d69f9fde2bcac1c25315", + "signature": "dfff68b3c34338f6b307a25d4566de15eed7973b0dc5d69f9fde2bcac1c25315", + "affectsGlobalScope": true + }, + "../node_modules/typescript/lib/lib.es2015.reflect.d.ts": { + "version": "cb609802a8698aa28b9c56331d4b53f590ca3c1c3a255350304ae3d06017779d", + "signature": "cb609802a8698aa28b9c56331d4b53f590ca3c1c3a255350304ae3d06017779d", + "affectsGlobalScope": true + }, + "../node_modules/typescript/lib/lib.es2015.symbol.d.ts": { + "version": "3013574108c36fd3aaca79764002b3717da09725a36a6fc02eac386593110f93", + "signature": "3013574108c36fd3aaca79764002b3717da09725a36a6fc02eac386593110f93", + "affectsGlobalScope": true + }, + "../node_modules/typescript/lib/lib.es2015.symbol.wellknown.d.ts": { + "version": "4670208dd7da9d6c774ab1b75c1527a810388c7989c4905de6aaea8561cb9dce", + "signature": "4670208dd7da9d6c774ab1b75c1527a810388c7989c4905de6aaea8561cb9dce", + "affectsGlobalScope": true + }, + "../node_modules/typescript/lib/lib.es2016.array.include.d.ts": { + "version": "3be5a1453daa63e031d266bf342f3943603873d890ab8b9ada95e22389389006", + "signature": "3be5a1453daa63e031d266bf342f3943603873d890ab8b9ada95e22389389006", + "affectsGlobalScope": true + }, + "../node_modules/typescript/lib/lib.es2017.object.d.ts": { + "version": "17bb1fc99591b00515502d264fa55dc8370c45c5298f4a5c2083557dccba5a2a", + "signature": "17bb1fc99591b00515502d264fa55dc8370c45c5298f4a5c2083557dccba5a2a", + "affectsGlobalScope": true + }, + "../node_modules/typescript/lib/lib.es2017.sharedmemory.d.ts": { + "version": "d0db416bccdb33975548baf09a42ee8c47eace1aac7907351a000f1e568e7232", + "signature": "d0db416bccdb33975548baf09a42ee8c47eace1aac7907351a000f1e568e7232", + "affectsGlobalScope": true + }, + "../node_modules/typescript/lib/lib.es2017.string.d.ts": { + "version": "6a6b173e739a6a99629a8594bfb294cc7329bfb7b227f12e1f7c11bc163b8577", + "signature": "6a6b173e739a6a99629a8594bfb294cc7329bfb7b227f12e1f7c11bc163b8577", + "affectsGlobalScope": true + }, + "../node_modules/typescript/lib/lib.es2017.intl.d.ts": { + "version": "12a310447c5d23c7d0d5ca2af606e3bd08afda69100166730ab92c62999ebb9d", + "signature": "12a310447c5d23c7d0d5ca2af606e3bd08afda69100166730ab92c62999ebb9d", + "affectsGlobalScope": true + }, + "../node_modules/typescript/lib/lib.es2017.typedarrays.d.ts": { + "version": "b0124885ef82641903d232172577f2ceb5d3e60aed4da1153bab4221e1f6dd4e", + "signature": "b0124885ef82641903d232172577f2ceb5d3e60aed4da1153bab4221e1f6dd4e", + "affectsGlobalScope": true + }, + "../node_modules/typescript/lib/lib.es2017.full.d.ts": { + "version": "d2f31f19e1ba6ed59be9259d660a239d9a3fcbbc8e038c6b2009bde34b175fed", + "signature": "d2f31f19e1ba6ed59be9259d660a239d9a3fcbbc8e038c6b2009bde34b175fed", + "affectsGlobalScope": false + }, + "../node_modules/@types/node/inspector.d.ts": { + "version": "7e49dbf1543b3ee54853ade4c5e9fa460b6a4eca967efe6bf943e0c505d087ed", + "signature": "7e49dbf1543b3ee54853ade4c5e9fa460b6a4eca967efe6bf943e0c505d087ed", + "affectsGlobalScope": false + }, + "../node_modules/@types/node/base.d.ts": { + "version": "39daac3cc4e13d9f1031c4b208c4cd10cb206782a381e71dbaa2353d170b41b4", + "signature": "39daac3cc4e13d9f1031c4b208c4cd10cb206782a381e71dbaa2353d170b41b4", + "affectsGlobalScope": true + }, + "../node_modules/@types/node/ts3.2/index.d.ts": { + "version": "1de0ff6200b92798a5aef43f57029c79dbf69932037dee1c007fdd2c562db258", + "signature": "1de0ff6200b92798a5aef43f57029c79dbf69932037dee1c007fdd2c562db258", + "affectsGlobalScope": false + }, + "../node_modules/@types/range-parser/index.d.ts": { + "version": "4e88b833be14c7f384e0dcd57bb30acd799e8e34d212635d693e41a75a71164b", + "signature": "4e88b833be14c7f384e0dcd57bb30acd799e8e34d212635d693e41a75a71164b", + "affectsGlobalScope": false + }, + "../node_modules/@types/qs/index.d.ts": { + "version": "b9cd23278040e3d4ea73660829a5c0d25cc303e493f79668e605f69edaf6d7b7", + "signature": "b9cd23278040e3d4ea73660829a5c0d25cc303e493f79668e605f69edaf6d7b7", + "affectsGlobalScope": false + }, + "../node_modules/@types/express-serve-static-core/index.d.ts": { + "version": "814f4d16f7e7c47feaba5979673dcc60b6d5f7e87b247ea5da1dbc83a6da9387", + "signature": "814f4d16f7e7c47feaba5979673dcc60b6d5f7e87b247ea5da1dbc83a6da9387", + "affectsGlobalScope": true + }, + "../node_modules/@types/mime/index.d.ts": { + "version": "be27a64e821a3e5af838650e4aa25805c60f057d0c37a9762c378d19d364b3e6", + "signature": "be27a64e821a3e5af838650e4aa25805c60f057d0c37a9762c378d19d364b3e6", + "affectsGlobalScope": false + }, + "../node_modules/@types/serve-static/index.d.ts": { + "version": "5b56da2c458a9522dbfec8ee94287abe037f52fc9dea766d87bd9aaf100cf14f", + "signature": "5b56da2c458a9522dbfec8ee94287abe037f52fc9dea766d87bd9aaf100cf14f", + "affectsGlobalScope": false + }, + "../node_modules/@types/connect/index.d.ts": { + "version": "e6ffa74698f0a1d23e4223242ed7dcdb89d02bbbb063a1930e9f91d0385abe16", + "signature": "e6ffa74698f0a1d23e4223242ed7dcdb89d02bbbb063a1930e9f91d0385abe16", + "affectsGlobalScope": false + }, + "../node_modules/@types/body-parser/index.d.ts": { + "version": "ebddbd167c2fabd0151f50e5df94ca6d845149c47521280d8867afe3429dd078", + "signature": "ebddbd167c2fabd0151f50e5df94ca6d845149c47521280d8867afe3429dd078", + "affectsGlobalScope": false + }, + "../node_modules/@types/express/index.d.ts": { + "version": "ead1ed9dd4874f4907a506a31e4fe9c4e079b42816f6b7ea5016a6d5ddf2fde3", + "signature": "ead1ed9dd4874f4907a506a31e4fe9c4e079b42816f6b7ea5016a6d5ddf2fde3", + "affectsGlobalScope": false + }, + "../node_modules/event-target-shim/index.d.ts": { + "version": "2859adaa4f2db3d4f0fc37ad86f056045341496b58fba0dbc16a222f9d5d55b1", + "signature": "2859adaa4f2db3d4f0fc37ad86f056045341496b58fba0dbc16a222f9d5d55b1", + "affectsGlobalScope": false + }, + "../node_modules/abort-controller/dist/abort-controller.d.ts": { + "version": "655ed305e8f4cb95d3f578040301a4e4d6ace112b1bd8824cd32bda66c3677d1", + "signature": "655ed305e8f4cb95d3f578040301a4e4d6ace112b1bd8824cd32bda66c3677d1", + "affectsGlobalScope": false + }, + "../node_modules/google-auth-library/node_modules/gaxios/build/src/common.d.ts": { + "version": "a955fa0d2d8dbcfcb1296963e49b907a2ac501d02b5a8916108eb238afc19fc6", + "signature": "a955fa0d2d8dbcfcb1296963e49b907a2ac501d02b5a8916108eb238afc19fc6", + "affectsGlobalScope": false + }, + "../node_modules/google-auth-library/node_modules/gaxios/build/src/gaxios.d.ts": { + "version": "dc1a68ad8a9684ad4b452c03aa656d4d63bdde95b4010411e233a5c4326e6f6a", + "signature": "dc1a68ad8a9684ad4b452c03aa656d4d63bdde95b4010411e233a5c4326e6f6a", + "affectsGlobalScope": false + }, + "../node_modules/google-auth-library/node_modules/gaxios/build/src/index.d.ts": { + "version": "cb789da1f75dc9d53848949aed3bb1d521de13c6340e5792a6b3f2c5e0c53e29", + "signature": "cb789da1f75dc9d53848949aed3bb1d521de13c6340e5792a6b3f2c5e0c53e29", + "affectsGlobalScope": false + }, + "../node_modules/google-auth-library/build/src/transporters.d.ts": { + "version": "82b70e72d3e16b95c6f933b0d146240214ef01dfe8e270f6e50f349223b0615d", + "signature": "82b70e72d3e16b95c6f933b0d146240214ef01dfe8e270f6e50f349223b0615d", + "affectsGlobalScope": false + }, + "../node_modules/google-auth-library/build/src/crypto/crypto.d.ts": { + "version": "48426cea1f526c040f5e25b5b46c72dc58e28dbca236ecb2d2f3c23ee2f3147e", + "signature": "48426cea1f526c040f5e25b5b46c72dc58e28dbca236ecb2d2f3c23ee2f3147e", + "affectsGlobalScope": false + }, + "../node_modules/google-auth-library/build/src/auth/credentials.d.ts": { + "version": "683563004226b83f33e4759f7976b180fab13042224e319476669d833f9f1fe0", + "signature": "683563004226b83f33e4759f7976b180fab13042224e319476669d833f9f1fe0", + "affectsGlobalScope": false + }, + "../node_modules/google-auth-library/build/src/auth/authclient.d.ts": { + "version": "ee00a6280d0c2a54ca31bc935a678ec560ec47fdab207382b64290b06995e626", + "signature": "ee00a6280d0c2a54ca31bc935a678ec560ec47fdab207382b64290b06995e626", + "affectsGlobalScope": false + }, + "../node_modules/google-auth-library/build/src/auth/loginticket.d.ts": { + "version": "3da10acc144b58168388267b5bd91d0df6a4ca4a24badfd235d3947576d3f9a9", + "signature": "3da10acc144b58168388267b5bd91d0df6a4ca4a24badfd235d3947576d3f9a9", + "affectsGlobalScope": false + }, + "../node_modules/google-auth-library/build/src/auth/oauth2client.d.ts": { + "version": "1d40605771c09e1b999b87725e20b6fea9a117c4ff6832f41b19838ff2b95008", + "signature": "1d40605771c09e1b999b87725e20b6fea9a117c4ff6832f41b19838ff2b95008", + "affectsGlobalScope": false + }, + "../node_modules/google-auth-library/build/src/auth/computeclient.d.ts": { + "version": "615954e8fe193fb3a2a7b91c9bb46fe18aaf60d2014da08da3c4d9a610c83477", + "signature": "615954e8fe193fb3a2a7b91c9bb46fe18aaf60d2014da08da3c4d9a610c83477", + "affectsGlobalScope": false + }, + "../node_modules/google-auth-library/build/src/auth/idtokenclient.d.ts": { + "version": "01b8fe09ea0b18ceba458308fd349bd2545227995c1b370a6897ea7c8f4ae577", + "signature": "01b8fe09ea0b18ceba458308fd349bd2545227995c1b370a6897ea7c8f4ae577", + "affectsGlobalScope": false + }, + "../node_modules/google-auth-library/build/src/auth/envDetect.d.ts": { + "version": "ea9efc84288c02adb551e56f59235b5b3d613993acb0cbb74f1e1169a6ddaf23", + "signature": "ea9efc84288c02adb551e56f59235b5b3d613993acb0cbb74f1e1169a6ddaf23", + "affectsGlobalScope": false + }, + "../node_modules/google-auth-library/node_modules/gtoken/build/src/index.d.ts": { + "version": "645096a656964fdc919d68a791dc764c85eb9128fb164686a70c4f2d75fe1caa", + "signature": "645096a656964fdc919d68a791dc764c85eb9128fb164686a70c4f2d75fe1caa", + "affectsGlobalScope": false + }, + "../node_modules/google-auth-library/build/src/auth/jwtclient.d.ts": { + "version": "1445ef80723618786694bf663714c7cc1be265647d5b14299029554c9da115e1", + "signature": "1445ef80723618786694bf663714c7cc1be265647d5b14299029554c9da115e1", + "affectsGlobalScope": false + }, + "../node_modules/google-auth-library/build/src/auth/refreshclient.d.ts": { + "version": "e9203259c5f820618a0c10df0e0e28df87f2469770461540919b23d864b0e2f8", + "signature": "e9203259c5f820618a0c10df0e0e28df87f2469770461540919b23d864b0e2f8", + "affectsGlobalScope": false + }, + "../node_modules/google-auth-library/build/src/auth/googleauth.d.ts": { + "version": "d28ae5ca2e567db94cb2a19070820036bda8713fb1403fbb00bb239d2eb336b3", + "signature": "d28ae5ca2e567db94cb2a19070820036bda8713fb1403fbb00bb239d2eb336b3", + "affectsGlobalScope": false + }, + "../node_modules/google-auth-library/build/src/auth/iam.d.ts": { + "version": "925f03e0d23441ea93e30508a0b0c21ccbb06ed6af0fc19eed66729763849158", + "signature": "925f03e0d23441ea93e30508a0b0c21ccbb06ed6af0fc19eed66729763849158", + "affectsGlobalScope": false + }, + "../node_modules/google-auth-library/build/src/auth/jwtaccess.d.ts": { + "version": "bcea68b9bf0547a5b8ce7faf25cbbd7b7d0918e93c748d8e0e337a264440ad0d", + "signature": "bcea68b9bf0547a5b8ce7faf25cbbd7b7d0918e93c748d8e0e337a264440ad0d", + "affectsGlobalScope": false + }, + "../node_modules/google-auth-library/build/src/index.d.ts": { + "version": "b5f5fb8682b627fabd6173249639436577a19d93a36d09599879dd9dc104ac63", + "signature": "b5f5fb8682b627fabd6173249639436577a19d93a36d09599879dd9dc104ac63", + "affectsGlobalScope": false + }, + "../node_modules/teeny-request/build/src/index.d.ts": { + "version": "1ef82373a19bae276d64e230a668816d96562ffdf266bc67e0a91dd30bb20d0f", + "signature": "1ef82373a19bae276d64e230a668816d96562ffdf266bc67e0a91dd30bb20d0f", + "affectsGlobalScope": false + }, + "../node_modules/@google-cloud/common/build/src/util.d.ts": { + "version": "6e2c8ed913b49e9c93cf0aa682e48c97347d3da54d60d81d2b0e11e09da2f953", + "signature": "6e2c8ed913b49e9c93cf0aa682e48c97347d3da54d60d81d2b0e11e09da2f953", + "affectsGlobalScope": false + }, + "../node_modules/@google-cloud/common/build/src/service-object.d.ts": { + "version": "2931f8609009719078a11c85a1069470ff2a74ca01e19f2a46ff08cdecb22c28", + "signature": "2931f8609009719078a11c85a1069470ff2a74ca01e19f2a46ff08cdecb22c28", + "affectsGlobalScope": false + }, + "../node_modules/@google-cloud/common/build/src/operation.d.ts": { + "version": "7b0dad26d8d3ea293d332162f563f1f60c35dd5b0d44850fa330a901e781ca5d", + "signature": "7b0dad26d8d3ea293d332162f563f1f60c35dd5b0d44850fa330a901e781ca5d", + "affectsGlobalScope": false + }, + "../node_modules/@google-cloud/common/build/src/service.d.ts": { + "version": "27d8eb0ae5461fac42501d2fed4ff876414de3182dea7df38f465409b2ccb039", + "signature": "27d8eb0ae5461fac42501d2fed4ff876414de3182dea7df38f465409b2ccb039", + "affectsGlobalScope": false + }, + "../node_modules/@google-cloud/common/build/src/index.d.ts": { + "version": "a673754b47203b9e1d15d95014c847593f00b3e33fb2ace08f76d9c3dca3f9ab", + "signature": "a673754b47203b9e1d15d95014c847593f00b3e33fb2ace08f76d9c3dca3f9ab", + "affectsGlobalScope": false + }, + "../node_modules/@google-cloud/storage/build/src/acl.d.ts": { + "version": "4a16f63eab2003bf18f93d7c39c2cc582c75ebed56e64d382c427e22bcec509a", + "signature": "4a16f63eab2003bf18f93d7c39c2cc582c75ebed56e64d382c427e22bcec509a", + "affectsGlobalScope": false + }, + "../node_modules/@google-cloud/storage/build/src/signer.d.ts": { + "version": "22977aa28fc8f6c082d2065d6d8b7fa4dcd416f8ae4dff6ed99ec5e1555fe298", + "signature": "22977aa28fc8f6c082d2065d6d8b7fa4dcd416f8ae4dff6ed99ec5e1555fe298", + "affectsGlobalScope": false + }, + "../node_modules/@google-cloud/storage/build/src/file.d.ts": { + "version": "38878788e3023527bd860f1300650e246274cd214ad09eb689bc561fd7831719", + "signature": "38878788e3023527bd860f1300650e246274cd214ad09eb689bc561fd7831719", + "affectsGlobalScope": false + }, + "../node_modules/@google-cloud/storage/build/src/hmacKey.d.ts": { + "version": "bd7f97a3fb993367447f8fbd7dedf0d9b4269a778404fef0ac5f398313b221a2", + "signature": "bd7f97a3fb993367447f8fbd7dedf0d9b4269a778404fef0ac5f398313b221a2", + "affectsGlobalScope": false + }, + "../node_modules/@google-cloud/storage/build/src/storage.d.ts": { + "version": "07098b194f4ad45856fc01df2f3d7ce2f68956ed86e6299254b204eabcf5bac8", + "signature": "07098b194f4ad45856fc01df2f3d7ce2f68956ed86e6299254b204eabcf5bac8", + "affectsGlobalScope": false + }, + "../node_modules/@google-cloud/storage/build/src/channel.d.ts": { + "version": "88e327017e5db7730f40e993f75fe947a8f4fdbf344d6176dba1a85490c824e4", + "signature": "88e327017e5db7730f40e993f75fe947a8f4fdbf344d6176dba1a85490c824e4", + "affectsGlobalScope": false + }, + "../node_modules/@google-cloud/storage/build/src/iam.d.ts": { + "version": "15981245ba7b6481bfab67ef18725554e5031da8d90f5153bfadce62aeedbb9e", + "signature": "15981245ba7b6481bfab67ef18725554e5031da8d90f5153bfadce62aeedbb9e", + "affectsGlobalScope": false + }, + "../node_modules/@google-cloud/storage/build/src/notification.d.ts": { + "version": "d3ff34f8218bcadd383b56f85f6ddf27f54f49abaf17ac05e58a04ff49bdcab7", + "signature": "d3ff34f8218bcadd383b56f85f6ddf27f54f49abaf17ac05e58a04ff49bdcab7", + "affectsGlobalScope": false + }, + "../node_modules/@google-cloud/storage/build/src/bucket.d.ts": { + "version": "8662bdd582ba440517d194716cd779b615f661b9b6f939cc91381363334fb6bd", + "signature": "8662bdd582ba440517d194716cd779b615f661b9b6f939cc91381363334fb6bd", + "affectsGlobalScope": false + }, + "../node_modules/@google-cloud/storage/build/src/index.d.ts": { + "version": "053ea839b135a17a15ad95c3635ab23fc1e12681082a86b91684df20c6e379df", + "signature": "053ea839b135a17a15ad95c3635ab23fc1e12681082a86b91684df20c6e379df", + "affectsGlobalScope": false + }, + "../node_modules/@google-cloud/firestore/types/firestore.d.ts": { + "version": "b6bc9b6873b882ac3fa59e9c46dba66075296a6ff252727ae4bfefddc98e76be", + "signature": "b6bc9b6873b882ac3fa59e9c46dba66075296a6ff252727ae4bfefddc98e76be", + "affectsGlobalScope": true + }, + "../node_modules/firebase-admin/lib/auth.d.ts": { + "version": "1163f9a573b30c6e62fd41a109f344e593c66fcd8c69769d6d9ee898037832d9", + "signature": "1163f9a573b30c6e62fd41a109f344e593c66fcd8c69769d6d9ee898037832d9", + "affectsGlobalScope": false + }, + "../node_modules/firebase-admin/lib/database.d.ts": { + "version": "45cc8779dc91edaa36e3873161b1bcf1147958bfda913e074a5e70524491dc14", + "signature": "45cc8779dc91edaa36e3873161b1bcf1147958bfda913e074a5e70524491dc14", + "affectsGlobalScope": false + }, + "../node_modules/firebase-admin/lib/messaging.d.ts": { + "version": "ff745567658097cfa143772fcc9b6c3f8b9c518260b38032da7ab8bc67de3f30", + "signature": "ff745567658097cfa143772fcc9b6c3f8b9c518260b38032da7ab8bc67de3f30", + "affectsGlobalScope": false + }, + "../node_modules/firebase-admin/lib/instance-id.d.ts": { + "version": "6ff4a3e66bf27360dfa01bbc4dca36531583afdec9a0d0fd3f4cf687f4d9895f", + "signature": "6ff4a3e66bf27360dfa01bbc4dca36531583afdec9a0d0fd3f4cf687f4d9895f", + "affectsGlobalScope": false + }, + "../node_modules/firebase-admin/lib/project-management.d.ts": { + "version": "48b9fdc7e89a788bb45fb37e59c7794051e5a16746a2ab237467c4b3df6f8593", + "signature": "48b9fdc7e89a788bb45fb37e59c7794051e5a16746a2ab237467c4b3df6f8593", + "affectsGlobalScope": false + }, + "../node_modules/firebase-admin/lib/security-rules.d.ts": { + "version": "1d457000b22888ba93c4e891df2b37470d2e15f0d6822fb504066560c23e4b3d", + "signature": "1d457000b22888ba93c4e891df2b37470d2e15f0d6822fb504066560c23e4b3d", + "affectsGlobalScope": false + }, + "../node_modules/firebase-admin/lib/index.d.ts": { + "version": "3779efe6a208b757ad4f02a20b39ed5d3a9181fd90d2fda8c479973ffd5f416d", + "signature": "3779efe6a208b757ad4f02a20b39ed5d3a9181fd90d2fda8c479973ffd5f416d", + "affectsGlobalScope": false + }, + "../src/interfaces/projectProperties.ts": { + "version": "44177f7d98a63cd427e07d250c3d7116ee321716c62362096a8ae8340e34aacf", + "signature": "18a43c234d04ffd95f36e9f5d95b020c80ec051d059cfc4678ae61bfa3addc5f", + "affectsGlobalScope": false + }, + "../src/newsriver.ts": { + "version": "a44bef4b3ef7750ed7d4abe5b7a1ce561b2cc04ac9a97f123e70464771ab0212", + "signature": "e6adb74b0adbeef9ea1da6803d1b5c3af7b9e961971eeb5ce8cf598d8b33c093", + "affectsGlobalScope": false + }, + "../node_modules/firebase-functions-helper/dist/firebase.d.ts": { + "version": "0c6f77926881e6b5444b3577c6d498a450721eaaf84c188b1e1fded3c2c4c415", + "signature": "0c6f77926881e6b5444b3577c6d498a450721eaaf84c188b1e1fded3c2c4c415", + "affectsGlobalScope": false + }, + "../node_modules/firebase-functions-helper/dist/firestore.d.ts": { + "version": "8d87f83b36fb5f55726dcfbb171b0faf85701323bb937ed1853c91298daf6897", + "signature": "8d87f83b36fb5f55726dcfbb171b0faf85701323bb937ed1853c91298daf6897", + "affectsGlobalScope": false + }, + "../node_modules/firebase-functions-helper/dist/realtime.d.ts": { + "version": "e2ade4e2e060488c0dda507dd28e718bcfd748f7f6dfa864e8c8b6d509d8d6bb", + "signature": "e2ade4e2e060488c0dda507dd28e718bcfd748f7f6dfa864e8c8b6d509d8d6bb", + "affectsGlobalScope": false + }, + "../node_modules/firebase-functions-helper/dist/index.d.ts": { + "version": "4f64b8107769278a6dada173dadb40afc975024b5551c3419f208677830a99c0", + "signature": "4f64b8107769278a6dada173dadb40afc975024b5551c3419f208677830a99c0", + "affectsGlobalScope": false + }, + "../src/updater.ts": { + "version": "a95ed0ec52eb5fd158644846bdf7c4b5b97957c394e6d98d8d9d07df2c2c2669", + "signature": "468e09e13fd92c765047426e852c73d7064f2f2968097cc1a379fa52293bd1d4", + "affectsGlobalScope": false + }, + "../node_modules/firebase-functions/lib/function-configuration.d.ts": { + "version": "0853b3b64117e2f64f7489e4484c0fd87767f26e68f0cebeb766feb2d2ae0e49", + "signature": "0853b3b64117e2f64f7489e4484c0fd87767f26e68f0cebeb766feb2d2ae0e49", + "affectsGlobalScope": false + }, + "../node_modules/firebase-functions/lib/cloud-functions.d.ts": { + "version": "2b19546f07ff0901dc3acbe2230de54bff0816bc1182722d3584abbc2d31d825", + "signature": "2b19546f07ff0901dc3acbe2230de54bff0816bc1182722d3584abbc2d31d825", + "affectsGlobalScope": false + }, + "../node_modules/firebase-functions/lib/providers/analytics.d.ts": { + "version": "230f2776b970caf8e470648d09f4bf01d6cf1612d6aadef4d4783343a8b5c7b7", + "signature": "230f2776b970caf8e470648d09f4bf01d6cf1612d6aadef4d4783343a8b5c7b7", + "affectsGlobalScope": false + }, + "../node_modules/firebase-functions/lib/providers/auth.d.ts": { + "version": "8a5cec8e62bd0dcb6e933c76743d57fd5b712663b8fd28c2c21c098e8fe027a4", + "signature": "8a5cec8e62bd0dcb6e933c76743d57fd5b712663b8fd28c2c21c098e8fe027a4", + "affectsGlobalScope": false + }, + "../node_modules/firebase-functions/lib/providers/crashlytics.d.ts": { + "version": "3bdc88b3cd30892523ee658bbde13bc59b6171e4e61095b417be0c6cfa823b6b", + "signature": "3bdc88b3cd30892523ee658bbde13bc59b6171e4e61095b417be0c6cfa823b6b", + "affectsGlobalScope": false + }, + "../node_modules/firebase-functions/lib/apps.d.ts": { + "version": "074ac0dea7b1b926227b2b990a9b38c8a2b36b5b0fb16591fc2c28234e35829e", + "signature": "074ac0dea7b1b926227b2b990a9b38c8a2b36b5b0fb16591fc2c28234e35829e", + "affectsGlobalScope": false + }, + "../node_modules/firebase-functions/lib/providers/database.d.ts": { + "version": "342a94a0c459dac647aac37217a8f3d3276086e5fda5ebc970ef05c4f8d7d76d", + "signature": "342a94a0c459dac647aac37217a8f3d3276086e5fda5ebc970ef05c4f8d7d76d", + "affectsGlobalScope": false + }, + "../node_modules/firebase-functions/lib/providers/firestore.d.ts": { + "version": "7b96e9c9c8e0f799ddbb18968c0f71ba687261f4cac3fe9fc4df03838564aa5d", + "signature": "7b96e9c9c8e0f799ddbb18968c0f71ba687261f4cac3fe9fc4df03838564aa5d", + "affectsGlobalScope": false + }, + "../node_modules/firebase-functions/lib/providers/https.d.ts": { + "version": "ba2a150318f33e7d446842c0af63e548dfa3c8b90cfb378cdf08936523a438e6", + "signature": "ba2a150318f33e7d446842c0af63e548dfa3c8b90cfb378cdf08936523a438e6", + "affectsGlobalScope": false + }, + "../node_modules/firebase-functions/lib/providers/pubsub.d.ts": { + "version": "35663f87496f91f80e16eb8a35a7c7c8e8ee0c6d321c93b4b48648911e225228", + "signature": "35663f87496f91f80e16eb8a35a7c7c8e8ee0c6d321c93b4b48648911e225228", + "affectsGlobalScope": false + }, + "../node_modules/firebase-functions/lib/providers/remoteConfig.d.ts": { + "version": "c4c0261b12524279056993ee8223189a7025f85f29d51317d417623b0fa5c090", + "signature": "c4c0261b12524279056993ee8223189a7025f85f29d51317d417623b0fa5c090", + "affectsGlobalScope": false + }, + "../node_modules/firebase-functions/lib/providers/storage.d.ts": { + "version": "496d7fa3b5a354fbe298366e17ce6bc0ecec576df7fafb63bf12f22fc47d935b", + "signature": "496d7fa3b5a354fbe298366e17ce6bc0ecec576df7fafb63bf12f22fc47d935b", + "affectsGlobalScope": false + }, + "../node_modules/firebase-functions/lib/providers/testLab.d.ts": { + "version": "471805944a5f2b948be9b514ab4f1206bfdf285cf07ea9749820146750ffbfe1", + "signature": "471805944a5f2b948be9b514ab4f1206bfdf285cf07ea9749820146750ffbfe1", + "affectsGlobalScope": false + }, + "../node_modules/firebase-functions/lib/handler-builder.d.ts": { + "version": "720b6958371bbb4683ec48709096e489c51f926e54e884f0c214c66df50e41c6", + "signature": "720b6958371bbb4683ec48709096e489c51f926e54e884f0c214c66df50e41c6", + "affectsGlobalScope": false + }, + "../node_modules/firebase-functions/lib/config.d.ts": { + "version": "e6f78f98b64c3340ad0d49a455132fbc9c50954dd49de5651824962e458d33ed", + "signature": "e6f78f98b64c3340ad0d49a455132fbc9c50954dd49de5651824962e458d33ed", + "affectsGlobalScope": false + }, + "../node_modules/firebase-functions/lib/function-builder.d.ts": { + "version": "82f234207c0d3bc2ff82655f1dc3f4ef13d703c2262d32e4bc80461545923467", + "signature": "82f234207c0d3bc2ff82655f1dc3f4ef13d703c2262d32e4bc80461545923467", + "affectsGlobalScope": false + }, + "../node_modules/firebase-functions/lib/index.d.ts": { + "version": "194b7011603bac1a064e579d5e392a630244b8a64c4853ec30d987997e77a546", + "signature": "194b7011603bac1a064e579d5e392a630244b8a64c4853ec30d987997e77a546", + "affectsGlobalScope": false + }, + "../src/rcdata.ts": { + "version": "bc02021ac0d0562d06c998d41192c133db6258baf68b1eb58f3fdf62a9931a40", + "signature": "9edfbbdc51de383c169c179f775a6f24a8c78c85e57516104f008cec0d1b58e2", + "affectsGlobalScope": false + }, + "../src/models/api.ts": { + "version": "b1820d723a6942e1948ca962f917f78113bfd35e71ab878197ff1dccc4eed78e", + "signature": "968f2998b91af36726206957073a7bbbce81e7503bda7f9b8f0eba947a9cf478", + "affectsGlobalScope": false + }, + "../src/controllers/api.ts": { + "version": "913c7d7b91c0f6a66a0c726cae7ec14d0c394c57afbad098bc76682a71fdabcb", + "signature": "6a7d540939495f5f26982f1978c294c6cb8e8353e3d35b2264f9a9e7dbf51686", + "affectsGlobalScope": false + }, + "../src/routes/api.ts": { + "version": "f926d468e745b7e38a732aa5bcb944f6859ef9db1365630b5381d73068ce7512", + "signature": "df1a2e3e5f95ee7e85d28a0c12bc6a5cc3cef558deb67e90a523199f9f31703b", + "affectsGlobalScope": false + }, + "../src/app.ts": { + "version": "b009d96946058a532521e9c3420f41d5d31160ba0e5f939f76b2a9b17a8b8695", + "signature": "5ab0082578d32976c6616ce01e0b4d85859b6f6749be207b2548bb5516623999", + "affectsGlobalScope": false + }, + "../src/bin/www.ts": { + "version": "43eebc266d8211231172799d79011556a2023f03a2b43ac1f93fbae1f3b9837a", + "signature": "8e609bb71c20b858c77f0e9f90bb1319db8477b13f9f965f1a1e18524bf50881", + "affectsGlobalScope": false + }, + "../node_modules/@types/fs-extra/index.d.ts": { + "version": "5171627120eeb3a7e8afb8ed04ea9be7f0b53ba09bb1fc95172483e0fbb0740c", + "signature": "5171627120eeb3a7e8afb8ed04ea9be7f0b53ba09bb1fc95172483e0fbb0740c", + "affectsGlobalScope": false + }, + "../node_modules/@types/lodash/ts3.1/common/common.d.ts": { + "version": "4025cf62742c5bb3d383c8a62342481622c87e3397ea5e7b7baab18b9efd5798", + "signature": "4025cf62742c5bb3d383c8a62342481622c87e3397ea5e7b7baab18b9efd5798", + "affectsGlobalScope": false + }, + "../node_modules/@types/lodash/ts3.1/common/array.d.ts": { + "version": "f6a75f23e42dc475bdd7fefa8c527a3c5ed502ad371900abe9d138a9010ad80a", + "signature": "f6a75f23e42dc475bdd7fefa8c527a3c5ed502ad371900abe9d138a9010ad80a", + "affectsGlobalScope": false + }, + "../node_modules/@types/lodash/ts3.1/common/collection.d.ts": { + "version": "0c75b204aed9cf6ff1c7b4bed87a3ece0d9d6fc857a6350c0c95ed0c38c814e8", + "signature": "0c75b204aed9cf6ff1c7b4bed87a3ece0d9d6fc857a6350c0c95ed0c38c814e8", + "affectsGlobalScope": false + }, + "../node_modules/@types/lodash/ts3.1/common/date.d.ts": { + "version": "187119ff4f9553676a884e296089e131e8cc01691c546273b1d0089c3533ce42", + "signature": "187119ff4f9553676a884e296089e131e8cc01691c546273b1d0089c3533ce42", + "affectsGlobalScope": false + }, + "../node_modules/@types/lodash/ts3.1/common/function.d.ts": { + "version": "035b95793288bf4457a2b80bfe9b7500a29324ad62adcf9991277198e8833096", + "signature": "035b95793288bf4457a2b80bfe9b7500a29324ad62adcf9991277198e8833096", + "affectsGlobalScope": false + }, + "../node_modules/@types/lodash/ts3.1/common/lang.d.ts": { + "version": "c2d47e5668f89ed8768d306919c42bb88d50d4029d68f58343141360895cfcc0", + "signature": "c2d47e5668f89ed8768d306919c42bb88d50d4029d68f58343141360895cfcc0", + "affectsGlobalScope": false + }, + "../node_modules/@types/lodash/ts3.1/common/math.d.ts": { + "version": "65648639567d214f62c1b21d200c852807e68bdb08311f95ab6f526ef5b98995", + "signature": "65648639567d214f62c1b21d200c852807e68bdb08311f95ab6f526ef5b98995", + "affectsGlobalScope": false + }, + "../node_modules/@types/lodash/ts3.1/common/number.d.ts": { + "version": "00baffbe8a2f2e4875367479489b5d43b5fc1429ecb4a4cc98cfc3009095f52a", + "signature": "00baffbe8a2f2e4875367479489b5d43b5fc1429ecb4a4cc98cfc3009095f52a", + "affectsGlobalScope": false + }, + "../node_modules/@types/lodash/ts3.1/common/object.d.ts": { + "version": "7fc5a3d7cff296cea5c225911726a56283b663328709088fcc912d61f73682fc", + "signature": "7fc5a3d7cff296cea5c225911726a56283b663328709088fcc912d61f73682fc", + "affectsGlobalScope": false + }, + "../node_modules/@types/lodash/ts3.1/common/seq.d.ts": { + "version": "3c92b6dfd43cc1c2485d9eba5ff0b74a19bb8725b692773ef1d66dac48cda4bd", + "signature": "3c92b6dfd43cc1c2485d9eba5ff0b74a19bb8725b692773ef1d66dac48cda4bd", + "affectsGlobalScope": false + }, + "../node_modules/@types/lodash/ts3.1/common/string.d.ts": { + "version": "e34f3f6159b1e23de9bb5521382795aaa5aaed6f53b4702e70a2ec45bc76ddb5", + "signature": "e34f3f6159b1e23de9bb5521382795aaa5aaed6f53b4702e70a2ec45bc76ddb5", + "affectsGlobalScope": false + }, + "../node_modules/@types/lodash/ts3.1/common/util.d.ts": { + "version": "2630a7cbb597e85d713b7ef47f2946d4280d3d4c02733282770741d40672b1a5", + "signature": "2630a7cbb597e85d713b7ef47f2946d4280d3d4c02733282770741d40672b1a5", + "affectsGlobalScope": false + }, + "../node_modules/@types/lodash/ts3.1/index.d.ts": { + "version": "bbf144d4354e2aaa6439f32761f3ee798cc68d1600adab6e2a596f25269f106d", + "signature": "bbf144d4354e2aaa6439f32761f3ee798cc68d1600adab6e2a596f25269f106d", + "affectsGlobalScope": true + }, + "../node_modules/@types/long/index.d.ts": { + "version": "e8465811693dfe4e96ef2b3dffda539d6edfe896961b7af37b44db2c0e48532b", + "signature": "e8465811693dfe4e96ef2b3dffda539d6edfe896961b7af37b44db2c0e48532b", + "affectsGlobalScope": false + } + }, + "options": { + "module": 1, + "noImplicitReturns": true, + "noUnusedLocals": true, + "outDir": "./", + "sourceMap": true, + "strict": false, + "target": 4, + "incremental": true, + "configFilePath": "../tsconfig.json" + }, + "referencedMap": { + "../node_modules/@google-cloud/common/build/src/index.d.ts": [ + "../node_modules/@google-cloud/common/build/src/operation.d.ts", + "../node_modules/@google-cloud/common/build/src/service-object.d.ts", + "../node_modules/@google-cloud/common/build/src/service.d.ts", + "../node_modules/@google-cloud/common/build/src/util.d.ts", + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts", + "../node_modules/google-auth-library/build/src/index.d.ts" + ], + "../node_modules/@google-cloud/common/build/src/operation.d.ts": [ + "../node_modules/@google-cloud/common/build/src/service-object.d.ts", + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts" + ], + "../node_modules/@google-cloud/common/build/src/service-object.d.ts": [ + "../node_modules/@google-cloud/common/build/src/util.d.ts", + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts", + "../node_modules/teeny-request/build/src/index.d.ts" + ], + "../node_modules/@google-cloud/common/build/src/service.d.ts": [ + "../node_modules/@google-cloud/common/build/src/service-object.d.ts", + "../node_modules/@google-cloud/common/build/src/util.d.ts", + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts", + "../node_modules/google-auth-library/build/src/index.d.ts", + "../node_modules/teeny-request/build/src/index.d.ts" + ], + "../node_modules/@google-cloud/common/build/src/util.d.ts": [ + "../node_modules/@google-cloud/common/build/src/service-object.d.ts", + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts", + "../node_modules/google-auth-library/build/src/index.d.ts", + "../node_modules/teeny-request/build/src/index.d.ts" + ], + "../node_modules/@google-cloud/firestore/types/firestore.d.ts": [ + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts" + ], + "../node_modules/@google-cloud/storage/build/src/acl.d.ts": [ + "../node_modules/@google-cloud/common/build/src/index.d.ts", + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts" + ], + "../node_modules/@google-cloud/storage/build/src/bucket.d.ts": [ + "../node_modules/@google-cloud/common/build/src/index.d.ts", + "../node_modules/@google-cloud/storage/build/src/acl.d.ts", + "../node_modules/@google-cloud/storage/build/src/channel.d.ts", + "../node_modules/@google-cloud/storage/build/src/file.d.ts", + "../node_modules/@google-cloud/storage/build/src/iam.d.ts", + "../node_modules/@google-cloud/storage/build/src/notification.d.ts", + "../node_modules/@google-cloud/storage/build/src/signer.d.ts", + "../node_modules/@google-cloud/storage/build/src/storage.d.ts", + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts" + ], + "../node_modules/@google-cloud/storage/build/src/channel.d.ts": [ + "../node_modules/@google-cloud/common/build/src/index.d.ts", + "../node_modules/@google-cloud/storage/build/src/storage.d.ts", + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts" + ], + "../node_modules/@google-cloud/storage/build/src/file.d.ts": [ + "../node_modules/@google-cloud/common/build/src/index.d.ts", + "../node_modules/@google-cloud/common/build/src/util.d.ts", + "../node_modules/@google-cloud/storage/build/src/acl.d.ts", + "../node_modules/@google-cloud/storage/build/src/bucket.d.ts", + "../node_modules/@google-cloud/storage/build/src/signer.d.ts", + "../node_modules/@google-cloud/storage/build/src/storage.d.ts", + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts" + ], + "../node_modules/@google-cloud/storage/build/src/hmacKey.d.ts": [ + "../node_modules/@google-cloud/common/build/src/index.d.ts", + "../node_modules/@google-cloud/storage/build/src/storage.d.ts", + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts" + ], + "../node_modules/@google-cloud/storage/build/src/iam.d.ts": [ + "../node_modules/@google-cloud/common/build/src/index.d.ts", + "../node_modules/@google-cloud/storage/build/src/bucket.d.ts", + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts" + ], + "../node_modules/@google-cloud/storage/build/src/index.d.ts": [ + "../node_modules/@google-cloud/storage/build/src/acl.d.ts", + "../node_modules/@google-cloud/storage/build/src/bucket.d.ts", + "../node_modules/@google-cloud/storage/build/src/channel.d.ts", + "../node_modules/@google-cloud/storage/build/src/file.d.ts", + "../node_modules/@google-cloud/storage/build/src/hmacKey.d.ts", + "../node_modules/@google-cloud/storage/build/src/iam.d.ts", + "../node_modules/@google-cloud/storage/build/src/notification.d.ts", + "../node_modules/@google-cloud/storage/build/src/signer.d.ts", + "../node_modules/@google-cloud/storage/build/src/storage.d.ts", + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts" + ], + "../node_modules/@google-cloud/storage/build/src/notification.d.ts": [ + "../node_modules/@google-cloud/common/build/src/index.d.ts", + "../node_modules/@google-cloud/common/build/src/util.d.ts", + "../node_modules/@google-cloud/storage/build/src/bucket.d.ts", + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts" + ], + "../node_modules/@google-cloud/storage/build/src/signer.d.ts": [ + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts" + ], + "../node_modules/@google-cloud/storage/build/src/storage.d.ts": [ + "../node_modules/@google-cloud/common/build/src/index.d.ts", + "../node_modules/@google-cloud/storage/build/src/bucket.d.ts", + "../node_modules/@google-cloud/storage/build/src/channel.d.ts", + "../node_modules/@google-cloud/storage/build/src/file.d.ts", + "../node_modules/@google-cloud/storage/build/src/hmacKey.d.ts", + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts" + ], + "../node_modules/@types/body-parser/index.d.ts": [ + "../node_modules/@types/connect/index.d.ts", + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts" + ], + "../node_modules/@types/connect/index.d.ts": [ + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts" + ], + "../node_modules/@types/express-serve-static-core/index.d.ts": [ + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts", + "../node_modules/@types/qs/index.d.ts", + "../node_modules/@types/range-parser/index.d.ts" + ], + "../node_modules/@types/express/index.d.ts": [ + "../node_modules/@types/body-parser/index.d.ts", + "../node_modules/@types/express-serve-static-core/index.d.ts", + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts", + "../node_modules/@types/qs/index.d.ts", + "../node_modules/@types/serve-static/index.d.ts" + ], + "../node_modules/@types/fs-extra/index.d.ts": [ + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts" + ], + "../node_modules/@types/lodash/ts3.1/common/array.d.ts": [ + "../node_modules/@types/lodash/ts3.1/common/collection.d.ts", + "../node_modules/@types/lodash/ts3.1/common/common.d.ts", + "../node_modules/@types/lodash/ts3.1/common/date.d.ts", + "../node_modules/@types/lodash/ts3.1/common/function.d.ts", + "../node_modules/@types/lodash/ts3.1/common/lang.d.ts", + "../node_modules/@types/lodash/ts3.1/common/math.d.ts", + "../node_modules/@types/lodash/ts3.1/common/number.d.ts", + "../node_modules/@types/lodash/ts3.1/common/object.d.ts", + "../node_modules/@types/lodash/ts3.1/common/seq.d.ts", + "../node_modules/@types/lodash/ts3.1/common/string.d.ts", + "../node_modules/@types/lodash/ts3.1/common/util.d.ts", + "../node_modules/@types/lodash/ts3.1/index.d.ts", + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts" + ], + "../node_modules/@types/lodash/ts3.1/common/collection.d.ts": [ + "../node_modules/@types/lodash/ts3.1/common/array.d.ts", + "../node_modules/@types/lodash/ts3.1/common/common.d.ts", + "../node_modules/@types/lodash/ts3.1/common/date.d.ts", + "../node_modules/@types/lodash/ts3.1/common/function.d.ts", + "../node_modules/@types/lodash/ts3.1/common/lang.d.ts", + "../node_modules/@types/lodash/ts3.1/common/math.d.ts", + "../node_modules/@types/lodash/ts3.1/common/number.d.ts", + "../node_modules/@types/lodash/ts3.1/common/object.d.ts", + "../node_modules/@types/lodash/ts3.1/common/seq.d.ts", + "../node_modules/@types/lodash/ts3.1/common/string.d.ts", + "../node_modules/@types/lodash/ts3.1/common/util.d.ts", + "../node_modules/@types/lodash/ts3.1/index.d.ts", + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts" + ], + "../node_modules/@types/lodash/ts3.1/common/common.d.ts": [ + "../node_modules/@types/lodash/ts3.1/common/array.d.ts", + "../node_modules/@types/lodash/ts3.1/common/collection.d.ts", + "../node_modules/@types/lodash/ts3.1/common/date.d.ts", + "../node_modules/@types/lodash/ts3.1/common/function.d.ts", + "../node_modules/@types/lodash/ts3.1/common/lang.d.ts", + "../node_modules/@types/lodash/ts3.1/common/math.d.ts", + "../node_modules/@types/lodash/ts3.1/common/number.d.ts", + "../node_modules/@types/lodash/ts3.1/common/object.d.ts", + "../node_modules/@types/lodash/ts3.1/common/seq.d.ts", + "../node_modules/@types/lodash/ts3.1/common/string.d.ts", + "../node_modules/@types/lodash/ts3.1/common/util.d.ts", + "../node_modules/@types/lodash/ts3.1/index.d.ts", + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts" + ], + "../node_modules/@types/lodash/ts3.1/common/date.d.ts": [ + "../node_modules/@types/lodash/ts3.1/common/array.d.ts", + "../node_modules/@types/lodash/ts3.1/common/collection.d.ts", + "../node_modules/@types/lodash/ts3.1/common/common.d.ts", + "../node_modules/@types/lodash/ts3.1/common/function.d.ts", + "../node_modules/@types/lodash/ts3.1/common/lang.d.ts", + "../node_modules/@types/lodash/ts3.1/common/math.d.ts", + "../node_modules/@types/lodash/ts3.1/common/number.d.ts", + "../node_modules/@types/lodash/ts3.1/common/object.d.ts", + "../node_modules/@types/lodash/ts3.1/common/seq.d.ts", + "../node_modules/@types/lodash/ts3.1/common/string.d.ts", + "../node_modules/@types/lodash/ts3.1/common/util.d.ts", + "../node_modules/@types/lodash/ts3.1/index.d.ts", + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts" + ], + "../node_modules/@types/lodash/ts3.1/common/function.d.ts": [ + "../node_modules/@types/lodash/ts3.1/common/array.d.ts", + "../node_modules/@types/lodash/ts3.1/common/collection.d.ts", + "../node_modules/@types/lodash/ts3.1/common/common.d.ts", + "../node_modules/@types/lodash/ts3.1/common/date.d.ts", + "../node_modules/@types/lodash/ts3.1/common/lang.d.ts", + "../node_modules/@types/lodash/ts3.1/common/math.d.ts", + "../node_modules/@types/lodash/ts3.1/common/number.d.ts", + "../node_modules/@types/lodash/ts3.1/common/object.d.ts", + "../node_modules/@types/lodash/ts3.1/common/seq.d.ts", + "../node_modules/@types/lodash/ts3.1/common/string.d.ts", + "../node_modules/@types/lodash/ts3.1/common/util.d.ts", + "../node_modules/@types/lodash/ts3.1/index.d.ts", + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts" + ], + "../node_modules/@types/lodash/ts3.1/common/lang.d.ts": [ + "../node_modules/@types/lodash/ts3.1/common/array.d.ts", + "../node_modules/@types/lodash/ts3.1/common/collection.d.ts", + "../node_modules/@types/lodash/ts3.1/common/common.d.ts", + "../node_modules/@types/lodash/ts3.1/common/date.d.ts", + "../node_modules/@types/lodash/ts3.1/common/function.d.ts", + "../node_modules/@types/lodash/ts3.1/common/math.d.ts", + "../node_modules/@types/lodash/ts3.1/common/number.d.ts", + "../node_modules/@types/lodash/ts3.1/common/object.d.ts", + "../node_modules/@types/lodash/ts3.1/common/seq.d.ts", + "../node_modules/@types/lodash/ts3.1/common/string.d.ts", + "../node_modules/@types/lodash/ts3.1/common/util.d.ts", + "../node_modules/@types/lodash/ts3.1/index.d.ts", + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts" + ], + "../node_modules/@types/lodash/ts3.1/common/math.d.ts": [ + "../node_modules/@types/lodash/ts3.1/common/array.d.ts", + "../node_modules/@types/lodash/ts3.1/common/collection.d.ts", + "../node_modules/@types/lodash/ts3.1/common/common.d.ts", + "../node_modules/@types/lodash/ts3.1/common/date.d.ts", + "../node_modules/@types/lodash/ts3.1/common/function.d.ts", + "../node_modules/@types/lodash/ts3.1/common/lang.d.ts", + "../node_modules/@types/lodash/ts3.1/common/number.d.ts", + "../node_modules/@types/lodash/ts3.1/common/object.d.ts", + "../node_modules/@types/lodash/ts3.1/common/seq.d.ts", + "../node_modules/@types/lodash/ts3.1/common/string.d.ts", + "../node_modules/@types/lodash/ts3.1/common/util.d.ts", + "../node_modules/@types/lodash/ts3.1/index.d.ts", + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts" + ], + "../node_modules/@types/lodash/ts3.1/common/number.d.ts": [ + "../node_modules/@types/lodash/ts3.1/common/array.d.ts", + "../node_modules/@types/lodash/ts3.1/common/collection.d.ts", + "../node_modules/@types/lodash/ts3.1/common/common.d.ts", + "../node_modules/@types/lodash/ts3.1/common/date.d.ts", + "../node_modules/@types/lodash/ts3.1/common/function.d.ts", + "../node_modules/@types/lodash/ts3.1/common/lang.d.ts", + "../node_modules/@types/lodash/ts3.1/common/math.d.ts", + "../node_modules/@types/lodash/ts3.1/common/object.d.ts", + "../node_modules/@types/lodash/ts3.1/common/seq.d.ts", + "../node_modules/@types/lodash/ts3.1/common/string.d.ts", + "../node_modules/@types/lodash/ts3.1/common/util.d.ts", + "../node_modules/@types/lodash/ts3.1/index.d.ts", + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts" + ], + "../node_modules/@types/lodash/ts3.1/common/object.d.ts": [ + "../node_modules/@types/lodash/ts3.1/common/array.d.ts", + "../node_modules/@types/lodash/ts3.1/common/collection.d.ts", + "../node_modules/@types/lodash/ts3.1/common/common.d.ts", + "../node_modules/@types/lodash/ts3.1/common/date.d.ts", + "../node_modules/@types/lodash/ts3.1/common/function.d.ts", + "../node_modules/@types/lodash/ts3.1/common/lang.d.ts", + "../node_modules/@types/lodash/ts3.1/common/math.d.ts", + "../node_modules/@types/lodash/ts3.1/common/number.d.ts", + "../node_modules/@types/lodash/ts3.1/common/seq.d.ts", + "../node_modules/@types/lodash/ts3.1/common/string.d.ts", + "../node_modules/@types/lodash/ts3.1/common/util.d.ts", + "../node_modules/@types/lodash/ts3.1/index.d.ts", + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts" + ], + "../node_modules/@types/lodash/ts3.1/common/seq.d.ts": [ + "../node_modules/@types/lodash/ts3.1/common/array.d.ts", + "../node_modules/@types/lodash/ts3.1/common/collection.d.ts", + "../node_modules/@types/lodash/ts3.1/common/common.d.ts", + "../node_modules/@types/lodash/ts3.1/common/date.d.ts", + "../node_modules/@types/lodash/ts3.1/common/function.d.ts", + "../node_modules/@types/lodash/ts3.1/common/lang.d.ts", + "../node_modules/@types/lodash/ts3.1/common/math.d.ts", + "../node_modules/@types/lodash/ts3.1/common/number.d.ts", + "../node_modules/@types/lodash/ts3.1/common/object.d.ts", + "../node_modules/@types/lodash/ts3.1/common/string.d.ts", + "../node_modules/@types/lodash/ts3.1/common/util.d.ts", + "../node_modules/@types/lodash/ts3.1/index.d.ts", + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts" + ], + "../node_modules/@types/lodash/ts3.1/common/string.d.ts": [ + "../node_modules/@types/lodash/ts3.1/common/array.d.ts", + "../node_modules/@types/lodash/ts3.1/common/collection.d.ts", + "../node_modules/@types/lodash/ts3.1/common/common.d.ts", + "../node_modules/@types/lodash/ts3.1/common/date.d.ts", + "../node_modules/@types/lodash/ts3.1/common/function.d.ts", + "../node_modules/@types/lodash/ts3.1/common/lang.d.ts", + "../node_modules/@types/lodash/ts3.1/common/math.d.ts", + "../node_modules/@types/lodash/ts3.1/common/number.d.ts", + "../node_modules/@types/lodash/ts3.1/common/object.d.ts", + "../node_modules/@types/lodash/ts3.1/common/seq.d.ts", + "../node_modules/@types/lodash/ts3.1/common/util.d.ts", + "../node_modules/@types/lodash/ts3.1/index.d.ts", + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts" + ], + "../node_modules/@types/lodash/ts3.1/common/util.d.ts": [ + "../node_modules/@types/lodash/ts3.1/common/array.d.ts", + "../node_modules/@types/lodash/ts3.1/common/collection.d.ts", + "../node_modules/@types/lodash/ts3.1/common/common.d.ts", + "../node_modules/@types/lodash/ts3.1/common/date.d.ts", + "../node_modules/@types/lodash/ts3.1/common/function.d.ts", + "../node_modules/@types/lodash/ts3.1/common/lang.d.ts", + "../node_modules/@types/lodash/ts3.1/common/math.d.ts", + "../node_modules/@types/lodash/ts3.1/common/number.d.ts", + "../node_modules/@types/lodash/ts3.1/common/object.d.ts", + "../node_modules/@types/lodash/ts3.1/common/seq.d.ts", + "../node_modules/@types/lodash/ts3.1/common/string.d.ts", + "../node_modules/@types/lodash/ts3.1/index.d.ts", + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts" + ], + "../node_modules/@types/lodash/ts3.1/index.d.ts": [ + "../node_modules/@types/lodash/ts3.1/common/array.d.ts", + "../node_modules/@types/lodash/ts3.1/common/collection.d.ts", + "../node_modules/@types/lodash/ts3.1/common/common.d.ts", + "../node_modules/@types/lodash/ts3.1/common/date.d.ts", + "../node_modules/@types/lodash/ts3.1/common/function.d.ts", + "../node_modules/@types/lodash/ts3.1/common/lang.d.ts", + "../node_modules/@types/lodash/ts3.1/common/math.d.ts", + "../node_modules/@types/lodash/ts3.1/common/number.d.ts", + "../node_modules/@types/lodash/ts3.1/common/object.d.ts", + "../node_modules/@types/lodash/ts3.1/common/seq.d.ts", + "../node_modules/@types/lodash/ts3.1/common/string.d.ts", + "../node_modules/@types/lodash/ts3.1/common/util.d.ts", + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts" + ], + "../node_modules/@types/long/index.d.ts": [ + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts" + ], + "../node_modules/@types/mime/index.d.ts": [ + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts" + ], + "../node_modules/@types/node/base.d.ts": [ + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/inspector.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts" + ], + "../node_modules/@types/node/inspector.d.ts": [ + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts" + ], + "../node_modules/@types/node/ts3.2/index.d.ts": [ + "../node_modules/@types/node/base.d.ts" + ], + "../node_modules/@types/qs/index.d.ts": [ + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts" + ], + "../node_modules/@types/range-parser/index.d.ts": [ + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts" + ], + "../node_modules/@types/serve-static/index.d.ts": [ + "../node_modules/@types/express-serve-static-core/index.d.ts", + "../node_modules/@types/mime/index.d.ts", + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts" + ], + "../node_modules/abort-controller/dist/abort-controller.d.ts": [ + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts", + "../node_modules/event-target-shim/index.d.ts" + ], + "../node_modules/event-target-shim/index.d.ts": [ + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts" + ], + "../node_modules/firebase-admin/lib/auth.d.ts": [ + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts", + "../node_modules/firebase-admin/lib/index.d.ts" + ], + "../node_modules/firebase-admin/lib/database.d.ts": [ + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts", + "../node_modules/firebase-admin/lib/index.d.ts" + ], + "../node_modules/firebase-admin/lib/index.d.ts": [ + "../node_modules/@google-cloud/firestore/types/firestore.d.ts", + "../node_modules/@google-cloud/storage/build/src/index.d.ts", + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts", + "../node_modules/firebase-admin/lib/auth.d.ts", + "../node_modules/firebase-admin/lib/database.d.ts", + "../node_modules/firebase-admin/lib/instance-id.d.ts", + "../node_modules/firebase-admin/lib/messaging.d.ts", + "../node_modules/firebase-admin/lib/project-management.d.ts", + "../node_modules/firebase-admin/lib/security-rules.d.ts" + ], + "../node_modules/firebase-admin/lib/instance-id.d.ts": [ + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts", + "../node_modules/firebase-admin/lib/index.d.ts" + ], + "../node_modules/firebase-admin/lib/messaging.d.ts": [ + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts", + "../node_modules/firebase-admin/lib/index.d.ts" + ], + "../node_modules/firebase-admin/lib/project-management.d.ts": [ + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts", + "../node_modules/firebase-admin/lib/index.d.ts" + ], + "../node_modules/firebase-admin/lib/security-rules.d.ts": [ + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts", + "../node_modules/firebase-admin/lib/index.d.ts" + ], + "../node_modules/firebase-functions-helper/dist/firebase.d.ts": [ + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts", + "../node_modules/firebase-admin/lib/index.d.ts" + ], + "../node_modules/firebase-functions-helper/dist/firestore.d.ts": [ + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts" + ], + "../node_modules/firebase-functions-helper/dist/index.d.ts": [ + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts", + "../node_modules/firebase-functions-helper/dist/firebase.d.ts", + "../node_modules/firebase-functions-helper/dist/firestore.d.ts", + "../node_modules/firebase-functions-helper/dist/realtime.d.ts" + ], + "../node_modules/firebase-functions-helper/dist/realtime.d.ts": [ + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts" + ], + "../node_modules/firebase-functions/lib/apps.d.ts": [ + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts", + "../node_modules/firebase-admin/lib/index.d.ts" + ], + "../node_modules/firebase-functions/lib/cloud-functions.d.ts": [ + "../node_modules/@types/express/index.d.ts", + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts", + "../node_modules/firebase-functions/lib/function-configuration.d.ts" + ], + "../node_modules/firebase-functions/lib/config.d.ts": [ + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts", + "../node_modules/firebase-admin/lib/index.d.ts" + ], + "../node_modules/firebase-functions/lib/function-builder.d.ts": [ + "../node_modules/@types/express-serve-static-core/index.d.ts", + "../node_modules/@types/express/index.d.ts", + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts", + "../node_modules/firebase-functions/lib/cloud-functions.d.ts", + "../node_modules/firebase-functions/lib/function-configuration.d.ts", + "../node_modules/firebase-functions/lib/providers/analytics.d.ts", + "../node_modules/firebase-functions/lib/providers/auth.d.ts", + "../node_modules/firebase-functions/lib/providers/crashlytics.d.ts", + "../node_modules/firebase-functions/lib/providers/database.d.ts", + "../node_modules/firebase-functions/lib/providers/firestore.d.ts", + "../node_modules/firebase-functions/lib/providers/https.d.ts", + "../node_modules/firebase-functions/lib/providers/pubsub.d.ts", + "../node_modules/firebase-functions/lib/providers/remoteConfig.d.ts", + "../node_modules/firebase-functions/lib/providers/storage.d.ts", + "../node_modules/firebase-functions/lib/providers/testLab.d.ts" + ], + "../node_modules/firebase-functions/lib/function-configuration.d.ts": [ + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts" + ], + "../node_modules/firebase-functions/lib/handler-builder.d.ts": [ + "../node_modules/@types/express-serve-static-core/index.d.ts", + "../node_modules/@types/express/index.d.ts", + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts", + "../node_modules/firebase-functions/lib/cloud-functions.d.ts", + "../node_modules/firebase-functions/lib/providers/analytics.d.ts", + "../node_modules/firebase-functions/lib/providers/auth.d.ts", + "../node_modules/firebase-functions/lib/providers/crashlytics.d.ts", + "../node_modules/firebase-functions/lib/providers/database.d.ts", + "../node_modules/firebase-functions/lib/providers/firestore.d.ts", + "../node_modules/firebase-functions/lib/providers/https.d.ts", + "../node_modules/firebase-functions/lib/providers/pubsub.d.ts", + "../node_modules/firebase-functions/lib/providers/remoteConfig.d.ts", + "../node_modules/firebase-functions/lib/providers/storage.d.ts", + "../node_modules/firebase-functions/lib/providers/testLab.d.ts" + ], + "../node_modules/firebase-functions/lib/index.d.ts": [ + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts", + "../node_modules/firebase-functions/lib/apps.d.ts", + "../node_modules/firebase-functions/lib/cloud-functions.d.ts", + "../node_modules/firebase-functions/lib/config.d.ts", + "../node_modules/firebase-functions/lib/function-builder.d.ts", + "../node_modules/firebase-functions/lib/function-configuration.d.ts", + "../node_modules/firebase-functions/lib/handler-builder.d.ts", + "../node_modules/firebase-functions/lib/providers/analytics.d.ts", + "../node_modules/firebase-functions/lib/providers/auth.d.ts", + "../node_modules/firebase-functions/lib/providers/crashlytics.d.ts", + "../node_modules/firebase-functions/lib/providers/database.d.ts", + "../node_modules/firebase-functions/lib/providers/firestore.d.ts", + "../node_modules/firebase-functions/lib/providers/https.d.ts", + "../node_modules/firebase-functions/lib/providers/pubsub.d.ts", + "../node_modules/firebase-functions/lib/providers/remoteConfig.d.ts", + "../node_modules/firebase-functions/lib/providers/storage.d.ts", + "../node_modules/firebase-functions/lib/providers/testLab.d.ts" + ], + "../node_modules/firebase-functions/lib/providers/analytics.d.ts": [ + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts", + "../node_modules/firebase-functions/lib/cloud-functions.d.ts", + "../node_modules/firebase-functions/lib/function-configuration.d.ts" + ], + "../node_modules/firebase-functions/lib/providers/auth.d.ts": [ + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts", + "../node_modules/firebase-admin/lib/index.d.ts", + "../node_modules/firebase-functions/lib/cloud-functions.d.ts", + "../node_modules/firebase-functions/lib/function-configuration.d.ts" + ], + "../node_modules/firebase-functions/lib/providers/crashlytics.d.ts": [ + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts", + "../node_modules/firebase-functions/lib/cloud-functions.d.ts", + "../node_modules/firebase-functions/lib/function-configuration.d.ts" + ], + "../node_modules/firebase-functions/lib/providers/database.d.ts": [ + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts", + "../node_modules/firebase-admin/lib/index.d.ts", + "../node_modules/firebase-functions/lib/apps.d.ts", + "../node_modules/firebase-functions/lib/cloud-functions.d.ts", + "../node_modules/firebase-functions/lib/function-configuration.d.ts" + ], + "../node_modules/firebase-functions/lib/providers/firestore.d.ts": [ + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts", + "../node_modules/firebase-admin/lib/index.d.ts", + "../node_modules/firebase-functions/lib/cloud-functions.d.ts", + "../node_modules/firebase-functions/lib/function-configuration.d.ts" + ], + "../node_modules/firebase-functions/lib/providers/https.d.ts": [ + "../node_modules/@types/express/index.d.ts", + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts", + "../node_modules/firebase-admin/lib/index.d.ts", + "../node_modules/firebase-functions/lib/cloud-functions.d.ts", + "../node_modules/firebase-functions/lib/function-configuration.d.ts" + ], + "../node_modules/firebase-functions/lib/providers/pubsub.d.ts": [ + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts", + "../node_modules/firebase-functions/lib/cloud-functions.d.ts", + "../node_modules/firebase-functions/lib/function-configuration.d.ts" + ], + "../node_modules/firebase-functions/lib/providers/remoteConfig.d.ts": [ + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts", + "../node_modules/firebase-functions/lib/cloud-functions.d.ts", + "../node_modules/firebase-functions/lib/function-configuration.d.ts" + ], + "../node_modules/firebase-functions/lib/providers/storage.d.ts": [ + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts", + "../node_modules/firebase-functions/lib/cloud-functions.d.ts", + "../node_modules/firebase-functions/lib/function-configuration.d.ts" + ], + "../node_modules/firebase-functions/lib/providers/testLab.d.ts": [ + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts", + "../node_modules/firebase-functions/lib/cloud-functions.d.ts" + ], + "../node_modules/google-auth-library/build/src/auth/authclient.d.ts": [ + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts", + "../node_modules/google-auth-library/build/src/auth/credentials.d.ts", + "../node_modules/google-auth-library/build/src/auth/oauth2client.d.ts", + "../node_modules/google-auth-library/build/src/transporters.d.ts", + "../node_modules/google-auth-library/node_modules/gaxios/build/src/index.d.ts" + ], + "../node_modules/google-auth-library/build/src/auth/computeclient.d.ts": [ + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts", + "../node_modules/google-auth-library/build/src/auth/oauth2client.d.ts", + "../node_modules/google-auth-library/node_modules/gaxios/build/src/index.d.ts" + ], + "../node_modules/google-auth-library/build/src/auth/credentials.d.ts": [ + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts" + ], + "../node_modules/google-auth-library/build/src/auth/envDetect.d.ts": [ + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts" + ], + "../node_modules/google-auth-library/build/src/auth/googleauth.d.ts": [ + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts", + "../node_modules/google-auth-library/build/src/auth/computeclient.d.ts", + "../node_modules/google-auth-library/build/src/auth/credentials.d.ts", + "../node_modules/google-auth-library/build/src/auth/envDetect.d.ts", + "../node_modules/google-auth-library/build/src/auth/idtokenclient.d.ts", + "../node_modules/google-auth-library/build/src/auth/jwtclient.d.ts", + "../node_modules/google-auth-library/build/src/auth/oauth2client.d.ts", + "../node_modules/google-auth-library/build/src/auth/refreshclient.d.ts", + "../node_modules/google-auth-library/build/src/transporters.d.ts", + "../node_modules/google-auth-library/node_modules/gaxios/build/src/index.d.ts" + ], + "../node_modules/google-auth-library/build/src/auth/iam.d.ts": [ + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts" + ], + "../node_modules/google-auth-library/build/src/auth/idtokenclient.d.ts": [ + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts", + "../node_modules/google-auth-library/build/src/auth/oauth2client.d.ts" + ], + "../node_modules/google-auth-library/build/src/auth/jwtaccess.d.ts": [ + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts", + "../node_modules/google-auth-library/build/src/auth/credentials.d.ts", + "../node_modules/google-auth-library/build/src/auth/oauth2client.d.ts" + ], + "../node_modules/google-auth-library/build/src/auth/jwtclient.d.ts": [ + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts", + "../node_modules/google-auth-library/build/src/auth/credentials.d.ts", + "../node_modules/google-auth-library/build/src/auth/idtokenclient.d.ts", + "../node_modules/google-auth-library/build/src/auth/oauth2client.d.ts", + "../node_modules/google-auth-library/node_modules/gtoken/build/src/index.d.ts" + ], + "../node_modules/google-auth-library/build/src/auth/loginticket.d.ts": [ + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts" + ], + "../node_modules/google-auth-library/build/src/auth/oauth2client.d.ts": [ + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts", + "../node_modules/google-auth-library/build/src/auth/authclient.d.ts", + "../node_modules/google-auth-library/build/src/auth/credentials.d.ts", + "../node_modules/google-auth-library/build/src/auth/loginticket.d.ts", + "../node_modules/google-auth-library/build/src/crypto/crypto.d.ts", + "../node_modules/google-auth-library/build/src/transporters.d.ts", + "../node_modules/google-auth-library/node_modules/gaxios/build/src/index.d.ts" + ], + "../node_modules/google-auth-library/build/src/auth/refreshclient.d.ts": [ + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts", + "../node_modules/google-auth-library/build/src/auth/credentials.d.ts", + "../node_modules/google-auth-library/build/src/auth/oauth2client.d.ts" + ], + "../node_modules/google-auth-library/build/src/crypto/crypto.d.ts": [ + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts" + ], + "../node_modules/google-auth-library/build/src/index.d.ts": [ + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts", + "../node_modules/google-auth-library/build/src/auth/computeclient.d.ts", + "../node_modules/google-auth-library/build/src/auth/credentials.d.ts", + "../node_modules/google-auth-library/build/src/auth/envDetect.d.ts", + "../node_modules/google-auth-library/build/src/auth/googleauth.d.ts", + "../node_modules/google-auth-library/build/src/auth/iam.d.ts", + "../node_modules/google-auth-library/build/src/auth/idtokenclient.d.ts", + "../node_modules/google-auth-library/build/src/auth/jwtaccess.d.ts", + "../node_modules/google-auth-library/build/src/auth/jwtclient.d.ts", + "../node_modules/google-auth-library/build/src/auth/loginticket.d.ts", + "../node_modules/google-auth-library/build/src/auth/oauth2client.d.ts", + "../node_modules/google-auth-library/build/src/auth/refreshclient.d.ts", + "../node_modules/google-auth-library/build/src/transporters.d.ts" + ], + "../node_modules/google-auth-library/build/src/transporters.d.ts": [ + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts", + "../node_modules/google-auth-library/node_modules/gaxios/build/src/index.d.ts" + ], + "../node_modules/google-auth-library/node_modules/gaxios/build/src/common.d.ts": [ + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts", + "../node_modules/abort-controller/dist/abort-controller.d.ts" + ], + "../node_modules/google-auth-library/node_modules/gaxios/build/src/gaxios.d.ts": [ + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts", + "../node_modules/google-auth-library/node_modules/gaxios/build/src/common.d.ts" + ], + "../node_modules/google-auth-library/node_modules/gaxios/build/src/index.d.ts": [ + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts", + "../node_modules/google-auth-library/node_modules/gaxios/build/src/common.d.ts", + "../node_modules/google-auth-library/node_modules/gaxios/build/src/gaxios.d.ts" + ], + "../node_modules/google-auth-library/node_modules/gtoken/build/src/index.d.ts": [ + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts" + ], + "../node_modules/teeny-request/build/src/index.d.ts": [ + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts" + ], + "../node_modules/typescript/lib/lib.dom.d.ts": [ + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts" + ], + "../node_modules/typescript/lib/lib.dom.iterable.d.ts": [ + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts" + ], + "../node_modules/typescript/lib/lib.es2015.collection.d.ts": [ + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts" + ], + "../node_modules/typescript/lib/lib.es2015.core.d.ts": [ + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts" + ], + "../node_modules/typescript/lib/lib.es2015.d.ts": [ + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts" + ], + "../node_modules/typescript/lib/lib.es2015.generator.d.ts": [ + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts" + ], + "../node_modules/typescript/lib/lib.es2015.iterable.d.ts": [ + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts" + ], + "../node_modules/typescript/lib/lib.es2015.promise.d.ts": [ + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts" + ], + "../node_modules/typescript/lib/lib.es2015.proxy.d.ts": [ + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts" + ], + "../node_modules/typescript/lib/lib.es2015.reflect.d.ts": [ + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts" + ], + "../node_modules/typescript/lib/lib.es2015.symbol.d.ts": [ + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts" + ], + "../node_modules/typescript/lib/lib.es2015.symbol.wellknown.d.ts": [ + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts" + ], + "../node_modules/typescript/lib/lib.es2016.array.include.d.ts": [ + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts" + ], + "../node_modules/typescript/lib/lib.es2016.d.ts": [ + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts" + ], + "../node_modules/typescript/lib/lib.es2017.d.ts": [ + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts" + ], + "../node_modules/typescript/lib/lib.es2017.full.d.ts": [ + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts" + ], + "../node_modules/typescript/lib/lib.es2017.intl.d.ts": [ + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts" + ], + "../node_modules/typescript/lib/lib.es2017.object.d.ts": [ + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts" + ], + "../node_modules/typescript/lib/lib.es2017.sharedmemory.d.ts": [ + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts" + ], + "../node_modules/typescript/lib/lib.es2017.string.d.ts": [ + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts" + ], + "../node_modules/typescript/lib/lib.es2017.typedarrays.d.ts": [ + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts" + ], + "../node_modules/typescript/lib/lib.es5.d.ts": [ + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts" + ], + "../node_modules/typescript/lib/lib.scripthost.d.ts": [ + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts" + ], + "../node_modules/typescript/lib/lib.webworker.importscripts.d.ts": [ + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts" + ], + "../src/app.ts": [ + "../node_modules/@types/express/index.d.ts", + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts", + "../node_modules/firebase-admin/lib/index.d.ts", + "../node_modules/firebase-functions/lib/index.d.ts", + "../src/controllers/api.ts", + "../src/interfaces/projectProperties.ts", + "../src/models/api.ts", + "../src/rcdata.ts", + "../src/routes/api.ts" + ], + "../src/bin/www.ts": [ + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts", + "../src/app.ts" + ], + "../src/controllers/api.ts": [ + "../node_modules/@types/express/index.d.ts", + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts", + "../src/models/api.ts" + ], + "../src/interfaces/projectProperties.ts": [ + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts" + ], + "../src/models/api.ts": [ + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts", + "../src/interfaces/projectProperties.ts", + "../src/newsriver.ts", + "../src/rcdata.ts", + "../src/updater.ts" + ], + "../src/newsriver.ts": [ + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts" + ], + "../src/rcdata.ts": [ + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts", + "../node_modules/firebase-admin/lib/index.d.ts", + "../node_modules/firebase-functions/lib/index.d.ts", + "../src/updater.ts" + ], + "../src/routes/api.ts": [ + "../node_modules/@types/express/index.d.ts", + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts", + "../node_modules/firebase-admin/lib/index.d.ts", + "../src/controllers/api.ts", + "../src/models/api.ts" + ], + "../src/updater.ts": [ + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts", + "../node_modules/firebase-functions-helper/dist/index.d.ts", + "../src/newsriver.ts" + ] + }, + "exportedModulesMap": { + "../node_modules/@google-cloud/common/build/src/index.d.ts": [ + "../node_modules/@google-cloud/common/build/src/operation.d.ts", + "../node_modules/@google-cloud/common/build/src/service-object.d.ts", + "../node_modules/@google-cloud/common/build/src/service.d.ts", + "../node_modules/@google-cloud/common/build/src/util.d.ts", + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts", + "../node_modules/google-auth-library/build/src/index.d.ts" + ], + "../node_modules/@google-cloud/common/build/src/operation.d.ts": [ + "../node_modules/@google-cloud/common/build/src/service-object.d.ts", + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts" + ], + "../node_modules/@google-cloud/common/build/src/service-object.d.ts": [ + "../node_modules/@google-cloud/common/build/src/util.d.ts", + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts", + "../node_modules/teeny-request/build/src/index.d.ts" + ], + "../node_modules/@google-cloud/common/build/src/service.d.ts": [ + "../node_modules/@google-cloud/common/build/src/service-object.d.ts", + "../node_modules/@google-cloud/common/build/src/util.d.ts", + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts", + "../node_modules/google-auth-library/build/src/index.d.ts", + "../node_modules/teeny-request/build/src/index.d.ts" + ], + "../node_modules/@google-cloud/common/build/src/util.d.ts": [ + "../node_modules/@google-cloud/common/build/src/service-object.d.ts", + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts", + "../node_modules/google-auth-library/build/src/index.d.ts", + "../node_modules/teeny-request/build/src/index.d.ts" + ], + "../node_modules/@google-cloud/firestore/types/firestore.d.ts": [ + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts" + ], + "../node_modules/@google-cloud/storage/build/src/acl.d.ts": [ + "../node_modules/@google-cloud/common/build/src/index.d.ts", + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts" + ], + "../node_modules/@google-cloud/storage/build/src/bucket.d.ts": [ + "../node_modules/@google-cloud/common/build/src/index.d.ts", + "../node_modules/@google-cloud/storage/build/src/acl.d.ts", + "../node_modules/@google-cloud/storage/build/src/channel.d.ts", + "../node_modules/@google-cloud/storage/build/src/file.d.ts", + "../node_modules/@google-cloud/storage/build/src/iam.d.ts", + "../node_modules/@google-cloud/storage/build/src/notification.d.ts", + "../node_modules/@google-cloud/storage/build/src/signer.d.ts", + "../node_modules/@google-cloud/storage/build/src/storage.d.ts", + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts" + ], + "../node_modules/@google-cloud/storage/build/src/channel.d.ts": [ + "../node_modules/@google-cloud/common/build/src/index.d.ts", + "../node_modules/@google-cloud/storage/build/src/storage.d.ts", + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts" + ], + "../node_modules/@google-cloud/storage/build/src/file.d.ts": [ + "../node_modules/@google-cloud/common/build/src/index.d.ts", + "../node_modules/@google-cloud/common/build/src/util.d.ts", + "../node_modules/@google-cloud/storage/build/src/acl.d.ts", + "../node_modules/@google-cloud/storage/build/src/bucket.d.ts", + "../node_modules/@google-cloud/storage/build/src/signer.d.ts", + "../node_modules/@google-cloud/storage/build/src/storage.d.ts", + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts" + ], + "../node_modules/@google-cloud/storage/build/src/hmacKey.d.ts": [ + "../node_modules/@google-cloud/common/build/src/index.d.ts", + "../node_modules/@google-cloud/storage/build/src/storage.d.ts", + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts" + ], + "../node_modules/@google-cloud/storage/build/src/iam.d.ts": [ + "../node_modules/@google-cloud/common/build/src/index.d.ts", + "../node_modules/@google-cloud/storage/build/src/bucket.d.ts", + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts" + ], + "../node_modules/@google-cloud/storage/build/src/index.d.ts": [ + "../node_modules/@google-cloud/storage/build/src/acl.d.ts", + "../node_modules/@google-cloud/storage/build/src/bucket.d.ts", + "../node_modules/@google-cloud/storage/build/src/channel.d.ts", + "../node_modules/@google-cloud/storage/build/src/file.d.ts", + "../node_modules/@google-cloud/storage/build/src/hmacKey.d.ts", + "../node_modules/@google-cloud/storage/build/src/iam.d.ts", + "../node_modules/@google-cloud/storage/build/src/notification.d.ts", + "../node_modules/@google-cloud/storage/build/src/signer.d.ts", + "../node_modules/@google-cloud/storage/build/src/storage.d.ts", + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts" + ], + "../node_modules/@google-cloud/storage/build/src/notification.d.ts": [ + "../node_modules/@google-cloud/common/build/src/index.d.ts", + "../node_modules/@google-cloud/common/build/src/util.d.ts", + "../node_modules/@google-cloud/storage/build/src/bucket.d.ts", + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts" + ], + "../node_modules/@google-cloud/storage/build/src/signer.d.ts": [ + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts" + ], + "../node_modules/@google-cloud/storage/build/src/storage.d.ts": [ + "../node_modules/@google-cloud/common/build/src/index.d.ts", + "../node_modules/@google-cloud/storage/build/src/bucket.d.ts", + "../node_modules/@google-cloud/storage/build/src/channel.d.ts", + "../node_modules/@google-cloud/storage/build/src/file.d.ts", + "../node_modules/@google-cloud/storage/build/src/hmacKey.d.ts", + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts" + ], + "../node_modules/@types/body-parser/index.d.ts": [ + "../node_modules/@types/connect/index.d.ts", + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts" + ], + "../node_modules/@types/connect/index.d.ts": [ + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts" + ], + "../node_modules/@types/express-serve-static-core/index.d.ts": [ + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts", + "../node_modules/@types/qs/index.d.ts", + "../node_modules/@types/range-parser/index.d.ts" + ], + "../node_modules/@types/express/index.d.ts": [ + "../node_modules/@types/body-parser/index.d.ts", + "../node_modules/@types/express-serve-static-core/index.d.ts", + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts", + "../node_modules/@types/qs/index.d.ts", + "../node_modules/@types/serve-static/index.d.ts" + ], + "../node_modules/@types/fs-extra/index.d.ts": [ + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts" + ], + "../node_modules/@types/lodash/ts3.1/common/array.d.ts": [ + "../node_modules/@types/lodash/ts3.1/common/collection.d.ts", + "../node_modules/@types/lodash/ts3.1/common/common.d.ts", + "../node_modules/@types/lodash/ts3.1/common/date.d.ts", + "../node_modules/@types/lodash/ts3.1/common/function.d.ts", + "../node_modules/@types/lodash/ts3.1/common/lang.d.ts", + "../node_modules/@types/lodash/ts3.1/common/math.d.ts", + "../node_modules/@types/lodash/ts3.1/common/number.d.ts", + "../node_modules/@types/lodash/ts3.1/common/object.d.ts", + "../node_modules/@types/lodash/ts3.1/common/seq.d.ts", + "../node_modules/@types/lodash/ts3.1/common/string.d.ts", + "../node_modules/@types/lodash/ts3.1/common/util.d.ts", + "../node_modules/@types/lodash/ts3.1/index.d.ts", + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts" + ], + "../node_modules/@types/lodash/ts3.1/common/collection.d.ts": [ + "../node_modules/@types/lodash/ts3.1/common/array.d.ts", + "../node_modules/@types/lodash/ts3.1/common/common.d.ts", + "../node_modules/@types/lodash/ts3.1/common/date.d.ts", + "../node_modules/@types/lodash/ts3.1/common/function.d.ts", + "../node_modules/@types/lodash/ts3.1/common/lang.d.ts", + "../node_modules/@types/lodash/ts3.1/common/math.d.ts", + "../node_modules/@types/lodash/ts3.1/common/number.d.ts", + "../node_modules/@types/lodash/ts3.1/common/object.d.ts", + "../node_modules/@types/lodash/ts3.1/common/seq.d.ts", + "../node_modules/@types/lodash/ts3.1/common/string.d.ts", + "../node_modules/@types/lodash/ts3.1/common/util.d.ts", + "../node_modules/@types/lodash/ts3.1/index.d.ts", + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts" + ], + "../node_modules/@types/lodash/ts3.1/common/common.d.ts": [ + "../node_modules/@types/lodash/ts3.1/common/array.d.ts", + "../node_modules/@types/lodash/ts3.1/common/collection.d.ts", + "../node_modules/@types/lodash/ts3.1/common/date.d.ts", + "../node_modules/@types/lodash/ts3.1/common/function.d.ts", + "../node_modules/@types/lodash/ts3.1/common/lang.d.ts", + "../node_modules/@types/lodash/ts3.1/common/math.d.ts", + "../node_modules/@types/lodash/ts3.1/common/number.d.ts", + "../node_modules/@types/lodash/ts3.1/common/object.d.ts", + "../node_modules/@types/lodash/ts3.1/common/seq.d.ts", + "../node_modules/@types/lodash/ts3.1/common/string.d.ts", + "../node_modules/@types/lodash/ts3.1/common/util.d.ts", + "../node_modules/@types/lodash/ts3.1/index.d.ts", + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts" + ], + "../node_modules/@types/lodash/ts3.1/common/date.d.ts": [ + "../node_modules/@types/lodash/ts3.1/common/array.d.ts", + "../node_modules/@types/lodash/ts3.1/common/collection.d.ts", + "../node_modules/@types/lodash/ts3.1/common/common.d.ts", + "../node_modules/@types/lodash/ts3.1/common/function.d.ts", + "../node_modules/@types/lodash/ts3.1/common/lang.d.ts", + "../node_modules/@types/lodash/ts3.1/common/math.d.ts", + "../node_modules/@types/lodash/ts3.1/common/number.d.ts", + "../node_modules/@types/lodash/ts3.1/common/object.d.ts", + "../node_modules/@types/lodash/ts3.1/common/seq.d.ts", + "../node_modules/@types/lodash/ts3.1/common/string.d.ts", + "../node_modules/@types/lodash/ts3.1/common/util.d.ts", + "../node_modules/@types/lodash/ts3.1/index.d.ts", + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts" + ], + "../node_modules/@types/lodash/ts3.1/common/function.d.ts": [ + "../node_modules/@types/lodash/ts3.1/common/array.d.ts", + "../node_modules/@types/lodash/ts3.1/common/collection.d.ts", + "../node_modules/@types/lodash/ts3.1/common/common.d.ts", + "../node_modules/@types/lodash/ts3.1/common/date.d.ts", + "../node_modules/@types/lodash/ts3.1/common/lang.d.ts", + "../node_modules/@types/lodash/ts3.1/common/math.d.ts", + "../node_modules/@types/lodash/ts3.1/common/number.d.ts", + "../node_modules/@types/lodash/ts3.1/common/object.d.ts", + "../node_modules/@types/lodash/ts3.1/common/seq.d.ts", + "../node_modules/@types/lodash/ts3.1/common/string.d.ts", + "../node_modules/@types/lodash/ts3.1/common/util.d.ts", + "../node_modules/@types/lodash/ts3.1/index.d.ts", + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts" + ], + "../node_modules/@types/lodash/ts3.1/common/lang.d.ts": [ + "../node_modules/@types/lodash/ts3.1/common/array.d.ts", + "../node_modules/@types/lodash/ts3.1/common/collection.d.ts", + "../node_modules/@types/lodash/ts3.1/common/common.d.ts", + "../node_modules/@types/lodash/ts3.1/common/date.d.ts", + "../node_modules/@types/lodash/ts3.1/common/function.d.ts", + "../node_modules/@types/lodash/ts3.1/common/math.d.ts", + "../node_modules/@types/lodash/ts3.1/common/number.d.ts", + "../node_modules/@types/lodash/ts3.1/common/object.d.ts", + "../node_modules/@types/lodash/ts3.1/common/seq.d.ts", + "../node_modules/@types/lodash/ts3.1/common/string.d.ts", + "../node_modules/@types/lodash/ts3.1/common/util.d.ts", + "../node_modules/@types/lodash/ts3.1/index.d.ts", + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts" + ], + "../node_modules/@types/lodash/ts3.1/common/math.d.ts": [ + "../node_modules/@types/lodash/ts3.1/common/array.d.ts", + "../node_modules/@types/lodash/ts3.1/common/collection.d.ts", + "../node_modules/@types/lodash/ts3.1/common/common.d.ts", + "../node_modules/@types/lodash/ts3.1/common/date.d.ts", + "../node_modules/@types/lodash/ts3.1/common/function.d.ts", + "../node_modules/@types/lodash/ts3.1/common/lang.d.ts", + "../node_modules/@types/lodash/ts3.1/common/number.d.ts", + "../node_modules/@types/lodash/ts3.1/common/object.d.ts", + "../node_modules/@types/lodash/ts3.1/common/seq.d.ts", + "../node_modules/@types/lodash/ts3.1/common/string.d.ts", + "../node_modules/@types/lodash/ts3.1/common/util.d.ts", + "../node_modules/@types/lodash/ts3.1/index.d.ts", + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts" + ], + "../node_modules/@types/lodash/ts3.1/common/number.d.ts": [ + "../node_modules/@types/lodash/ts3.1/common/array.d.ts", + "../node_modules/@types/lodash/ts3.1/common/collection.d.ts", + "../node_modules/@types/lodash/ts3.1/common/common.d.ts", + "../node_modules/@types/lodash/ts3.1/common/date.d.ts", + "../node_modules/@types/lodash/ts3.1/common/function.d.ts", + "../node_modules/@types/lodash/ts3.1/common/lang.d.ts", + "../node_modules/@types/lodash/ts3.1/common/math.d.ts", + "../node_modules/@types/lodash/ts3.1/common/object.d.ts", + "../node_modules/@types/lodash/ts3.1/common/seq.d.ts", + "../node_modules/@types/lodash/ts3.1/common/string.d.ts", + "../node_modules/@types/lodash/ts3.1/common/util.d.ts", + "../node_modules/@types/lodash/ts3.1/index.d.ts", + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts" + ], + "../node_modules/@types/lodash/ts3.1/common/object.d.ts": [ + "../node_modules/@types/lodash/ts3.1/common/array.d.ts", + "../node_modules/@types/lodash/ts3.1/common/collection.d.ts", + "../node_modules/@types/lodash/ts3.1/common/common.d.ts", + "../node_modules/@types/lodash/ts3.1/common/date.d.ts", + "../node_modules/@types/lodash/ts3.1/common/function.d.ts", + "../node_modules/@types/lodash/ts3.1/common/lang.d.ts", + "../node_modules/@types/lodash/ts3.1/common/math.d.ts", + "../node_modules/@types/lodash/ts3.1/common/number.d.ts", + "../node_modules/@types/lodash/ts3.1/common/seq.d.ts", + "../node_modules/@types/lodash/ts3.1/common/string.d.ts", + "../node_modules/@types/lodash/ts3.1/common/util.d.ts", + "../node_modules/@types/lodash/ts3.1/index.d.ts", + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts" + ], + "../node_modules/@types/lodash/ts3.1/common/seq.d.ts": [ + "../node_modules/@types/lodash/ts3.1/common/array.d.ts", + "../node_modules/@types/lodash/ts3.1/common/collection.d.ts", + "../node_modules/@types/lodash/ts3.1/common/common.d.ts", + "../node_modules/@types/lodash/ts3.1/common/date.d.ts", + "../node_modules/@types/lodash/ts3.1/common/function.d.ts", + "../node_modules/@types/lodash/ts3.1/common/lang.d.ts", + "../node_modules/@types/lodash/ts3.1/common/math.d.ts", + "../node_modules/@types/lodash/ts3.1/common/number.d.ts", + "../node_modules/@types/lodash/ts3.1/common/object.d.ts", + "../node_modules/@types/lodash/ts3.1/common/string.d.ts", + "../node_modules/@types/lodash/ts3.1/common/util.d.ts", + "../node_modules/@types/lodash/ts3.1/index.d.ts", + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts" + ], + "../node_modules/@types/lodash/ts3.1/common/string.d.ts": [ + "../node_modules/@types/lodash/ts3.1/common/array.d.ts", + "../node_modules/@types/lodash/ts3.1/common/collection.d.ts", + "../node_modules/@types/lodash/ts3.1/common/common.d.ts", + "../node_modules/@types/lodash/ts3.1/common/date.d.ts", + "../node_modules/@types/lodash/ts3.1/common/function.d.ts", + "../node_modules/@types/lodash/ts3.1/common/lang.d.ts", + "../node_modules/@types/lodash/ts3.1/common/math.d.ts", + "../node_modules/@types/lodash/ts3.1/common/number.d.ts", + "../node_modules/@types/lodash/ts3.1/common/object.d.ts", + "../node_modules/@types/lodash/ts3.1/common/seq.d.ts", + "../node_modules/@types/lodash/ts3.1/common/util.d.ts", + "../node_modules/@types/lodash/ts3.1/index.d.ts", + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts" + ], + "../node_modules/@types/lodash/ts3.1/common/util.d.ts": [ + "../node_modules/@types/lodash/ts3.1/common/array.d.ts", + "../node_modules/@types/lodash/ts3.1/common/collection.d.ts", + "../node_modules/@types/lodash/ts3.1/common/common.d.ts", + "../node_modules/@types/lodash/ts3.1/common/date.d.ts", + "../node_modules/@types/lodash/ts3.1/common/function.d.ts", + "../node_modules/@types/lodash/ts3.1/common/lang.d.ts", + "../node_modules/@types/lodash/ts3.1/common/math.d.ts", + "../node_modules/@types/lodash/ts3.1/common/number.d.ts", + "../node_modules/@types/lodash/ts3.1/common/object.d.ts", + "../node_modules/@types/lodash/ts3.1/common/seq.d.ts", + "../node_modules/@types/lodash/ts3.1/common/string.d.ts", + "../node_modules/@types/lodash/ts3.1/index.d.ts", + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts" + ], + "../node_modules/@types/lodash/ts3.1/index.d.ts": [ + "../node_modules/@types/lodash/ts3.1/common/array.d.ts", + "../node_modules/@types/lodash/ts3.1/common/collection.d.ts", + "../node_modules/@types/lodash/ts3.1/common/common.d.ts", + "../node_modules/@types/lodash/ts3.1/common/date.d.ts", + "../node_modules/@types/lodash/ts3.1/common/function.d.ts", + "../node_modules/@types/lodash/ts3.1/common/lang.d.ts", + "../node_modules/@types/lodash/ts3.1/common/math.d.ts", + "../node_modules/@types/lodash/ts3.1/common/number.d.ts", + "../node_modules/@types/lodash/ts3.1/common/object.d.ts", + "../node_modules/@types/lodash/ts3.1/common/seq.d.ts", + "../node_modules/@types/lodash/ts3.1/common/string.d.ts", + "../node_modules/@types/lodash/ts3.1/common/util.d.ts", + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts" + ], + "../node_modules/@types/long/index.d.ts": [ + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts" + ], + "../node_modules/@types/mime/index.d.ts": [ + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts" + ], + "../node_modules/@types/node/base.d.ts": [ + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/inspector.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts" + ], + "../node_modules/@types/node/inspector.d.ts": [ + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts" + ], + "../node_modules/@types/node/ts3.2/index.d.ts": [ + "../node_modules/@types/node/base.d.ts" + ], + "../node_modules/@types/qs/index.d.ts": [ + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts" + ], + "../node_modules/@types/range-parser/index.d.ts": [ + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts" + ], + "../node_modules/@types/serve-static/index.d.ts": [ + "../node_modules/@types/express-serve-static-core/index.d.ts", + "../node_modules/@types/mime/index.d.ts", + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts" + ], + "../node_modules/abort-controller/dist/abort-controller.d.ts": [ + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts", + "../node_modules/event-target-shim/index.d.ts" + ], + "../node_modules/event-target-shim/index.d.ts": [ + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts" + ], + "../node_modules/firebase-admin/lib/auth.d.ts": [ + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts", + "../node_modules/firebase-admin/lib/index.d.ts" + ], + "../node_modules/firebase-admin/lib/database.d.ts": [ + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts", + "../node_modules/firebase-admin/lib/index.d.ts" + ], + "../node_modules/firebase-admin/lib/index.d.ts": [ + "../node_modules/@google-cloud/firestore/types/firestore.d.ts", + "../node_modules/@google-cloud/storage/build/src/index.d.ts", + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts", + "../node_modules/firebase-admin/lib/auth.d.ts", + "../node_modules/firebase-admin/lib/database.d.ts", + "../node_modules/firebase-admin/lib/instance-id.d.ts", + "../node_modules/firebase-admin/lib/messaging.d.ts", + "../node_modules/firebase-admin/lib/project-management.d.ts", + "../node_modules/firebase-admin/lib/security-rules.d.ts" + ], + "../node_modules/firebase-admin/lib/instance-id.d.ts": [ + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts", + "../node_modules/firebase-admin/lib/index.d.ts" + ], + "../node_modules/firebase-admin/lib/messaging.d.ts": [ + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts", + "../node_modules/firebase-admin/lib/index.d.ts" + ], + "../node_modules/firebase-admin/lib/project-management.d.ts": [ + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts", + "../node_modules/firebase-admin/lib/index.d.ts" + ], + "../node_modules/firebase-admin/lib/security-rules.d.ts": [ + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts", + "../node_modules/firebase-admin/lib/index.d.ts" + ], + "../node_modules/firebase-functions-helper/dist/firebase.d.ts": [ + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts", + "../node_modules/firebase-admin/lib/index.d.ts" + ], + "../node_modules/firebase-functions-helper/dist/firestore.d.ts": [ + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts" + ], + "../node_modules/firebase-functions-helper/dist/index.d.ts": [ + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts", + "../node_modules/firebase-functions-helper/dist/firebase.d.ts", + "../node_modules/firebase-functions-helper/dist/firestore.d.ts", + "../node_modules/firebase-functions-helper/dist/realtime.d.ts" + ], + "../node_modules/firebase-functions-helper/dist/realtime.d.ts": [ + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts" + ], + "../node_modules/firebase-functions/lib/apps.d.ts": [ + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts", + "../node_modules/firebase-admin/lib/index.d.ts" + ], + "../node_modules/firebase-functions/lib/cloud-functions.d.ts": [ + "../node_modules/@types/express/index.d.ts", + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts", + "../node_modules/firebase-functions/lib/function-configuration.d.ts" + ], + "../node_modules/firebase-functions/lib/config.d.ts": [ + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts", + "../node_modules/firebase-admin/lib/index.d.ts" + ], + "../node_modules/firebase-functions/lib/function-builder.d.ts": [ + "../node_modules/@types/express-serve-static-core/index.d.ts", + "../node_modules/@types/express/index.d.ts", + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts", + "../node_modules/firebase-functions/lib/cloud-functions.d.ts", + "../node_modules/firebase-functions/lib/function-configuration.d.ts", + "../node_modules/firebase-functions/lib/providers/analytics.d.ts", + "../node_modules/firebase-functions/lib/providers/auth.d.ts", + "../node_modules/firebase-functions/lib/providers/crashlytics.d.ts", + "../node_modules/firebase-functions/lib/providers/database.d.ts", + "../node_modules/firebase-functions/lib/providers/firestore.d.ts", + "../node_modules/firebase-functions/lib/providers/https.d.ts", + "../node_modules/firebase-functions/lib/providers/pubsub.d.ts", + "../node_modules/firebase-functions/lib/providers/remoteConfig.d.ts", + "../node_modules/firebase-functions/lib/providers/storage.d.ts", + "../node_modules/firebase-functions/lib/providers/testLab.d.ts" + ], + "../node_modules/firebase-functions/lib/function-configuration.d.ts": [ + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts" + ], + "../node_modules/firebase-functions/lib/handler-builder.d.ts": [ + "../node_modules/@types/express-serve-static-core/index.d.ts", + "../node_modules/@types/express/index.d.ts", + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts", + "../node_modules/firebase-functions/lib/cloud-functions.d.ts", + "../node_modules/firebase-functions/lib/providers/analytics.d.ts", + "../node_modules/firebase-functions/lib/providers/auth.d.ts", + "../node_modules/firebase-functions/lib/providers/crashlytics.d.ts", + "../node_modules/firebase-functions/lib/providers/database.d.ts", + "../node_modules/firebase-functions/lib/providers/firestore.d.ts", + "../node_modules/firebase-functions/lib/providers/https.d.ts", + "../node_modules/firebase-functions/lib/providers/pubsub.d.ts", + "../node_modules/firebase-functions/lib/providers/remoteConfig.d.ts", + "../node_modules/firebase-functions/lib/providers/storage.d.ts", + "../node_modules/firebase-functions/lib/providers/testLab.d.ts" + ], + "../node_modules/firebase-functions/lib/index.d.ts": [ + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts", + "../node_modules/firebase-functions/lib/apps.d.ts", + "../node_modules/firebase-functions/lib/cloud-functions.d.ts", + "../node_modules/firebase-functions/lib/config.d.ts", + "../node_modules/firebase-functions/lib/function-builder.d.ts", + "../node_modules/firebase-functions/lib/function-configuration.d.ts", + "../node_modules/firebase-functions/lib/handler-builder.d.ts", + "../node_modules/firebase-functions/lib/providers/analytics.d.ts", + "../node_modules/firebase-functions/lib/providers/auth.d.ts", + "../node_modules/firebase-functions/lib/providers/crashlytics.d.ts", + "../node_modules/firebase-functions/lib/providers/database.d.ts", + "../node_modules/firebase-functions/lib/providers/firestore.d.ts", + "../node_modules/firebase-functions/lib/providers/https.d.ts", + "../node_modules/firebase-functions/lib/providers/pubsub.d.ts", + "../node_modules/firebase-functions/lib/providers/remoteConfig.d.ts", + "../node_modules/firebase-functions/lib/providers/storage.d.ts", + "../node_modules/firebase-functions/lib/providers/testLab.d.ts" + ], + "../node_modules/firebase-functions/lib/providers/analytics.d.ts": [ + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts", + "../node_modules/firebase-functions/lib/cloud-functions.d.ts", + "../node_modules/firebase-functions/lib/function-configuration.d.ts" + ], + "../node_modules/firebase-functions/lib/providers/auth.d.ts": [ + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts", + "../node_modules/firebase-admin/lib/index.d.ts", + "../node_modules/firebase-functions/lib/cloud-functions.d.ts", + "../node_modules/firebase-functions/lib/function-configuration.d.ts" + ], + "../node_modules/firebase-functions/lib/providers/crashlytics.d.ts": [ + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts", + "../node_modules/firebase-functions/lib/cloud-functions.d.ts", + "../node_modules/firebase-functions/lib/function-configuration.d.ts" + ], + "../node_modules/firebase-functions/lib/providers/database.d.ts": [ + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts", + "../node_modules/firebase-admin/lib/index.d.ts", + "../node_modules/firebase-functions/lib/apps.d.ts", + "../node_modules/firebase-functions/lib/cloud-functions.d.ts", + "../node_modules/firebase-functions/lib/function-configuration.d.ts" + ], + "../node_modules/firebase-functions/lib/providers/firestore.d.ts": [ + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts", + "../node_modules/firebase-admin/lib/index.d.ts", + "../node_modules/firebase-functions/lib/cloud-functions.d.ts", + "../node_modules/firebase-functions/lib/function-configuration.d.ts" + ], + "../node_modules/firebase-functions/lib/providers/https.d.ts": [ + "../node_modules/@types/express/index.d.ts", + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts", + "../node_modules/firebase-admin/lib/index.d.ts", + "../node_modules/firebase-functions/lib/cloud-functions.d.ts", + "../node_modules/firebase-functions/lib/function-configuration.d.ts" + ], + "../node_modules/firebase-functions/lib/providers/pubsub.d.ts": [ + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts", + "../node_modules/firebase-functions/lib/cloud-functions.d.ts", + "../node_modules/firebase-functions/lib/function-configuration.d.ts" + ], + "../node_modules/firebase-functions/lib/providers/remoteConfig.d.ts": [ + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts", + "../node_modules/firebase-functions/lib/cloud-functions.d.ts", + "../node_modules/firebase-functions/lib/function-configuration.d.ts" + ], + "../node_modules/firebase-functions/lib/providers/storage.d.ts": [ + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts", + "../node_modules/firebase-functions/lib/cloud-functions.d.ts", + "../node_modules/firebase-functions/lib/function-configuration.d.ts" + ], + "../node_modules/firebase-functions/lib/providers/testLab.d.ts": [ + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts", + "../node_modules/firebase-functions/lib/cloud-functions.d.ts" + ], + "../node_modules/google-auth-library/build/src/auth/authclient.d.ts": [ + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts", + "../node_modules/google-auth-library/build/src/auth/credentials.d.ts", + "../node_modules/google-auth-library/build/src/auth/oauth2client.d.ts", + "../node_modules/google-auth-library/build/src/transporters.d.ts", + "../node_modules/google-auth-library/node_modules/gaxios/build/src/index.d.ts" + ], + "../node_modules/google-auth-library/build/src/auth/computeclient.d.ts": [ + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts", + "../node_modules/google-auth-library/build/src/auth/oauth2client.d.ts", + "../node_modules/google-auth-library/node_modules/gaxios/build/src/index.d.ts" + ], + "../node_modules/google-auth-library/build/src/auth/credentials.d.ts": [ + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts" + ], + "../node_modules/google-auth-library/build/src/auth/envDetect.d.ts": [ + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts" + ], + "../node_modules/google-auth-library/build/src/auth/googleauth.d.ts": [ + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts", + "../node_modules/google-auth-library/build/src/auth/computeclient.d.ts", + "../node_modules/google-auth-library/build/src/auth/credentials.d.ts", + "../node_modules/google-auth-library/build/src/auth/envDetect.d.ts", + "../node_modules/google-auth-library/build/src/auth/idtokenclient.d.ts", + "../node_modules/google-auth-library/build/src/auth/jwtclient.d.ts", + "../node_modules/google-auth-library/build/src/auth/oauth2client.d.ts", + "../node_modules/google-auth-library/build/src/auth/refreshclient.d.ts", + "../node_modules/google-auth-library/build/src/transporters.d.ts", + "../node_modules/google-auth-library/node_modules/gaxios/build/src/index.d.ts" + ], + "../node_modules/google-auth-library/build/src/auth/iam.d.ts": [ + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts" + ], + "../node_modules/google-auth-library/build/src/auth/idtokenclient.d.ts": [ + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts", + "../node_modules/google-auth-library/build/src/auth/oauth2client.d.ts" + ], + "../node_modules/google-auth-library/build/src/auth/jwtaccess.d.ts": [ + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts", + "../node_modules/google-auth-library/build/src/auth/credentials.d.ts", + "../node_modules/google-auth-library/build/src/auth/oauth2client.d.ts" + ], + "../node_modules/google-auth-library/build/src/auth/jwtclient.d.ts": [ + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts", + "../node_modules/google-auth-library/build/src/auth/credentials.d.ts", + "../node_modules/google-auth-library/build/src/auth/idtokenclient.d.ts", + "../node_modules/google-auth-library/build/src/auth/oauth2client.d.ts", + "../node_modules/google-auth-library/node_modules/gtoken/build/src/index.d.ts" + ], + "../node_modules/google-auth-library/build/src/auth/loginticket.d.ts": [ + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts" + ], + "../node_modules/google-auth-library/build/src/auth/oauth2client.d.ts": [ + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts", + "../node_modules/google-auth-library/build/src/auth/authclient.d.ts", + "../node_modules/google-auth-library/build/src/auth/credentials.d.ts", + "../node_modules/google-auth-library/build/src/auth/loginticket.d.ts", + "../node_modules/google-auth-library/build/src/crypto/crypto.d.ts", + "../node_modules/google-auth-library/build/src/transporters.d.ts", + "../node_modules/google-auth-library/node_modules/gaxios/build/src/index.d.ts" + ], + "../node_modules/google-auth-library/build/src/auth/refreshclient.d.ts": [ + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts", + "../node_modules/google-auth-library/build/src/auth/credentials.d.ts", + "../node_modules/google-auth-library/build/src/auth/oauth2client.d.ts" + ], + "../node_modules/google-auth-library/build/src/crypto/crypto.d.ts": [ + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts" + ], + "../node_modules/google-auth-library/build/src/index.d.ts": [ + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts", + "../node_modules/google-auth-library/build/src/auth/computeclient.d.ts", + "../node_modules/google-auth-library/build/src/auth/credentials.d.ts", + "../node_modules/google-auth-library/build/src/auth/envDetect.d.ts", + "../node_modules/google-auth-library/build/src/auth/googleauth.d.ts", + "../node_modules/google-auth-library/build/src/auth/iam.d.ts", + "../node_modules/google-auth-library/build/src/auth/idtokenclient.d.ts", + "../node_modules/google-auth-library/build/src/auth/jwtaccess.d.ts", + "../node_modules/google-auth-library/build/src/auth/jwtclient.d.ts", + "../node_modules/google-auth-library/build/src/auth/loginticket.d.ts", + "../node_modules/google-auth-library/build/src/auth/oauth2client.d.ts", + "../node_modules/google-auth-library/build/src/auth/refreshclient.d.ts", + "../node_modules/google-auth-library/build/src/transporters.d.ts" + ], + "../node_modules/google-auth-library/build/src/transporters.d.ts": [ + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts", + "../node_modules/google-auth-library/node_modules/gaxios/build/src/index.d.ts" + ], + "../node_modules/google-auth-library/node_modules/gaxios/build/src/common.d.ts": [ + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts", + "../node_modules/abort-controller/dist/abort-controller.d.ts" + ], + "../node_modules/google-auth-library/node_modules/gaxios/build/src/gaxios.d.ts": [ + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts", + "../node_modules/google-auth-library/node_modules/gaxios/build/src/common.d.ts" + ], + "../node_modules/google-auth-library/node_modules/gaxios/build/src/index.d.ts": [ + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts", + "../node_modules/google-auth-library/node_modules/gaxios/build/src/common.d.ts", + "../node_modules/google-auth-library/node_modules/gaxios/build/src/gaxios.d.ts" + ], + "../node_modules/google-auth-library/node_modules/gtoken/build/src/index.d.ts": [ + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts" + ], + "../node_modules/teeny-request/build/src/index.d.ts": [ + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts" + ], + "../node_modules/typescript/lib/lib.dom.d.ts": [ + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts" + ], + "../node_modules/typescript/lib/lib.dom.iterable.d.ts": [ + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts" + ], + "../node_modules/typescript/lib/lib.es2015.collection.d.ts": [ + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts" + ], + "../node_modules/typescript/lib/lib.es2015.core.d.ts": [ + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts" + ], + "../node_modules/typescript/lib/lib.es2015.d.ts": [ + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts" + ], + "../node_modules/typescript/lib/lib.es2015.generator.d.ts": [ + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts" + ], + "../node_modules/typescript/lib/lib.es2015.iterable.d.ts": [ + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts" + ], + "../node_modules/typescript/lib/lib.es2015.promise.d.ts": [ + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts" + ], + "../node_modules/typescript/lib/lib.es2015.proxy.d.ts": [ + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts" + ], + "../node_modules/typescript/lib/lib.es2015.reflect.d.ts": [ + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts" + ], + "../node_modules/typescript/lib/lib.es2015.symbol.d.ts": [ + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts" + ], + "../node_modules/typescript/lib/lib.es2015.symbol.wellknown.d.ts": [ + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts" + ], + "../node_modules/typescript/lib/lib.es2016.array.include.d.ts": [ + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts" + ], + "../node_modules/typescript/lib/lib.es2016.d.ts": [ + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts" + ], + "../node_modules/typescript/lib/lib.es2017.d.ts": [ + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts" + ], + "../node_modules/typescript/lib/lib.es2017.full.d.ts": [ + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts" + ], + "../node_modules/typescript/lib/lib.es2017.intl.d.ts": [ + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts" + ], + "../node_modules/typescript/lib/lib.es2017.object.d.ts": [ + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts" + ], + "../node_modules/typescript/lib/lib.es2017.sharedmemory.d.ts": [ + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts" + ], + "../node_modules/typescript/lib/lib.es2017.string.d.ts": [ + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts" + ], + "../node_modules/typescript/lib/lib.es2017.typedarrays.d.ts": [ + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts" + ], + "../node_modules/typescript/lib/lib.es5.d.ts": [ + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts" + ], + "../node_modules/typescript/lib/lib.scripthost.d.ts": [ + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts" + ], + "../node_modules/typescript/lib/lib.webworker.importscripts.d.ts": [ + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts" + ], + "../src/app.ts": [ + "../node_modules/@types/express-serve-static-core/index.d.ts" + ], + "../src/controllers/api.ts": [ + "../node_modules/@types/express/index.d.ts", + "../src/models/api.ts" + ], + "../src/models/api.ts": [ + "../src/interfaces/projectProperties.ts", + "../src/newsriver.ts", + "../src/rcdata.ts", + "../src/updater.ts" + ], + "../src/rcdata.ts": [ + "../node_modules/firebase-admin/lib/index.d.ts", + "../src/updater.ts" + ], + "../src/routes/api.ts": [ + "../node_modules/@types/express/index.d.ts", + "../node_modules/firebase-admin/lib/index.d.ts", + "../src/controllers/api.ts", + "../src/models/api.ts" + ], + "../src/updater.ts": [ + "../src/newsriver.ts" + ] + }, + "semanticDiagnosticsPerFile": [ + "../node_modules/@google-cloud/common/build/src/index.d.ts", + "../node_modules/@google-cloud/common/build/src/operation.d.ts", + "../node_modules/@google-cloud/common/build/src/service-object.d.ts", + "../node_modules/@google-cloud/common/build/src/service.d.ts", + "../node_modules/@google-cloud/common/build/src/util.d.ts", + "../node_modules/@google-cloud/firestore/types/firestore.d.ts", + "../node_modules/@google-cloud/storage/build/src/acl.d.ts", + "../node_modules/@google-cloud/storage/build/src/bucket.d.ts", + "../node_modules/@google-cloud/storage/build/src/channel.d.ts", + "../node_modules/@google-cloud/storage/build/src/file.d.ts", + "../node_modules/@google-cloud/storage/build/src/hmacKey.d.ts", + "../node_modules/@google-cloud/storage/build/src/iam.d.ts", + "../node_modules/@google-cloud/storage/build/src/index.d.ts", + "../node_modules/@google-cloud/storage/build/src/notification.d.ts", + "../node_modules/@google-cloud/storage/build/src/signer.d.ts", + "../node_modules/@google-cloud/storage/build/src/storage.d.ts", + "../node_modules/@types/body-parser/index.d.ts", + "../node_modules/@types/connect/index.d.ts", + "../node_modules/@types/express-serve-static-core/index.d.ts", + "../node_modules/@types/express/index.d.ts", + "../node_modules/@types/fs-extra/index.d.ts", + "../node_modules/@types/lodash/ts3.1/common/array.d.ts", + "../node_modules/@types/lodash/ts3.1/common/collection.d.ts", + "../node_modules/@types/lodash/ts3.1/common/common.d.ts", + "../node_modules/@types/lodash/ts3.1/common/date.d.ts", + "../node_modules/@types/lodash/ts3.1/common/function.d.ts", + "../node_modules/@types/lodash/ts3.1/common/lang.d.ts", + "../node_modules/@types/lodash/ts3.1/common/math.d.ts", + "../node_modules/@types/lodash/ts3.1/common/number.d.ts", + "../node_modules/@types/lodash/ts3.1/common/object.d.ts", + "../node_modules/@types/lodash/ts3.1/common/seq.d.ts", + "../node_modules/@types/lodash/ts3.1/common/string.d.ts", + "../node_modules/@types/lodash/ts3.1/common/util.d.ts", + "../node_modules/@types/lodash/ts3.1/index.d.ts", + "../node_modules/@types/long/index.d.ts", + "../node_modules/@types/mime/index.d.ts", + "../node_modules/@types/node/base.d.ts", + "../node_modules/@types/node/inspector.d.ts", + "../node_modules/@types/node/ts3.2/index.d.ts", + "../node_modules/@types/qs/index.d.ts", + "../node_modules/@types/range-parser/index.d.ts", + "../node_modules/@types/serve-static/index.d.ts", + "../node_modules/abort-controller/dist/abort-controller.d.ts", + "../node_modules/event-target-shim/index.d.ts", + "../node_modules/firebase-admin/lib/auth.d.ts", + "../node_modules/firebase-admin/lib/database.d.ts", + "../node_modules/firebase-admin/lib/index.d.ts", + "../node_modules/firebase-admin/lib/instance-id.d.ts", + "../node_modules/firebase-admin/lib/messaging.d.ts", + "../node_modules/firebase-admin/lib/project-management.d.ts", + "../node_modules/firebase-admin/lib/security-rules.d.ts", + "../node_modules/firebase-functions-helper/dist/firebase.d.ts", + "../node_modules/firebase-functions-helper/dist/firestore.d.ts", + "../node_modules/firebase-functions-helper/dist/index.d.ts", + "../node_modules/firebase-functions-helper/dist/realtime.d.ts", + "../node_modules/firebase-functions/lib/apps.d.ts", + "../node_modules/firebase-functions/lib/cloud-functions.d.ts", + "../node_modules/firebase-functions/lib/config.d.ts", + "../node_modules/firebase-functions/lib/function-builder.d.ts", + "../node_modules/firebase-functions/lib/function-configuration.d.ts", + "../node_modules/firebase-functions/lib/handler-builder.d.ts", + "../node_modules/firebase-functions/lib/index.d.ts", + "../node_modules/firebase-functions/lib/providers/analytics.d.ts", + "../node_modules/firebase-functions/lib/providers/auth.d.ts", + "../node_modules/firebase-functions/lib/providers/crashlytics.d.ts", + "../node_modules/firebase-functions/lib/providers/database.d.ts", + "../node_modules/firebase-functions/lib/providers/firestore.d.ts", + "../node_modules/firebase-functions/lib/providers/https.d.ts", + "../node_modules/firebase-functions/lib/providers/pubsub.d.ts", + "../node_modules/firebase-functions/lib/providers/remoteConfig.d.ts", + "../node_modules/firebase-functions/lib/providers/storage.d.ts", + "../node_modules/firebase-functions/lib/providers/testLab.d.ts", + "../node_modules/google-auth-library/build/src/auth/authclient.d.ts", + "../node_modules/google-auth-library/build/src/auth/computeclient.d.ts", + "../node_modules/google-auth-library/build/src/auth/credentials.d.ts", + "../node_modules/google-auth-library/build/src/auth/envDetect.d.ts", + "../node_modules/google-auth-library/build/src/auth/googleauth.d.ts", + "../node_modules/google-auth-library/build/src/auth/iam.d.ts", + "../node_modules/google-auth-library/build/src/auth/idtokenclient.d.ts", + "../node_modules/google-auth-library/build/src/auth/jwtaccess.d.ts", + "../node_modules/google-auth-library/build/src/auth/jwtclient.d.ts", + "../node_modules/google-auth-library/build/src/auth/loginticket.d.ts", + "../node_modules/google-auth-library/build/src/auth/oauth2client.d.ts", + "../node_modules/google-auth-library/build/src/auth/refreshclient.d.ts", + "../node_modules/google-auth-library/build/src/crypto/crypto.d.ts", + "../node_modules/google-auth-library/build/src/index.d.ts", + "../node_modules/google-auth-library/build/src/transporters.d.ts", + "../node_modules/google-auth-library/node_modules/gaxios/build/src/common.d.ts", + "../node_modules/google-auth-library/node_modules/gaxios/build/src/gaxios.d.ts", + "../node_modules/google-auth-library/node_modules/gaxios/build/src/index.d.ts", + "../node_modules/google-auth-library/node_modules/gtoken/build/src/index.d.ts", + "../node_modules/teeny-request/build/src/index.d.ts", + "../node_modules/typescript/lib/lib.dom.d.ts", + "../node_modules/typescript/lib/lib.dom.iterable.d.ts", + "../node_modules/typescript/lib/lib.es2015.collection.d.ts", + "../node_modules/typescript/lib/lib.es2015.core.d.ts", + "../node_modules/typescript/lib/lib.es2015.d.ts", + "../node_modules/typescript/lib/lib.es2015.generator.d.ts", + "../node_modules/typescript/lib/lib.es2015.iterable.d.ts", + "../node_modules/typescript/lib/lib.es2015.promise.d.ts", + "../node_modules/typescript/lib/lib.es2015.proxy.d.ts", + "../node_modules/typescript/lib/lib.es2015.reflect.d.ts", + "../node_modules/typescript/lib/lib.es2015.symbol.d.ts", + "../node_modules/typescript/lib/lib.es2015.symbol.wellknown.d.ts", + "../node_modules/typescript/lib/lib.es2016.array.include.d.ts", + "../node_modules/typescript/lib/lib.es2016.d.ts", + "../node_modules/typescript/lib/lib.es2017.d.ts", + "../node_modules/typescript/lib/lib.es2017.full.d.ts", + "../node_modules/typescript/lib/lib.es2017.intl.d.ts", + "../node_modules/typescript/lib/lib.es2017.object.d.ts", + "../node_modules/typescript/lib/lib.es2017.sharedmemory.d.ts", + "../node_modules/typescript/lib/lib.es2017.string.d.ts", + "../node_modules/typescript/lib/lib.es2017.typedarrays.d.ts", + "../node_modules/typescript/lib/lib.es5.d.ts", + "../node_modules/typescript/lib/lib.scripthost.d.ts", + "../node_modules/typescript/lib/lib.webworker.importscripts.d.ts", + "../src/app.ts", + "../src/bin/www.ts", + "../src/controllers/api.ts", + "../src/interfaces/projectProperties.ts", + "../src/models/api.ts", + "../src/newsriver.ts", + "../src/rcdata.ts", + "../src/routes/api.ts", + "../src/updater.ts" + ] + }, + "version": "3.9.3" +} \ No newline at end of file diff --git a/functions/RApi/lib/views/error.pug b/functions/RApi/lib/views/error.pug new file mode 100644 index 0000000..3b25cfa --- /dev/null +++ b/functions/RApi/lib/views/error.pug @@ -0,0 +1,6 @@ +extends layout + +block content + h1= message + h2= error.status + pre #{error.stack} diff --git a/functions/RApi/lib/views/layout.pug b/functions/RApi/lib/views/layout.pug new file mode 100644 index 0000000..6dc17d8 --- /dev/null +++ b/functions/RApi/lib/views/layout.pug @@ -0,0 +1,7 @@ +doctype html +html + head + title= title + link(rel='stylesheet', href='/stylesheets/style.css') + body + block content diff --git a/functions/RApi/package-lock.json b/functions/RApi/package-lock.json new file mode 100644 index 0000000..833ca3e --- /dev/null +++ b/functions/RApi/package-lock.json @@ -0,0 +1,3340 @@ +{ + "name": "functions", + "requires": true, + "lockfileVersion": 1, + "dependencies": { + "@babel/code-frame": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.8.3.tgz", + "integrity": "sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g==", + "dev": true, + "requires": { + "@babel/highlight": "^7.8.3" + } + }, + "@babel/helper-validator-identifier": { + "version": "7.9.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.9.5.tgz", + "integrity": "sha512-/8arLKUFq882w4tWGj9JYzRpAlZgiWUJ+dtteNTDqrRBz9Iguck9Rn3ykuBDoUwh2TO4tSAJlrxDUOXWklJe4g==", + "dev": true + }, + "@babel/highlight": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.9.0.tgz", + "integrity": "sha512-lJZPilxX7Op3Nv/2cvFdnlepPXDxi29wxteT57Q965oc5R9v86ztx0jfxVrTcBk8C2kcPkkDa2Z4T3ZsPPVWsQ==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.9.0", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, + "@babel/parser": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.10.1.tgz", + "integrity": "sha512-AUTksaz3FqugBkbTZ1i+lDLG5qy8hIzCaAxEtttU6C0BtZZU9pkNZtWSVAht4EW9kl46YBiyTGMp9xTTGqViNg==" + }, + "@babel/types": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.10.1.tgz", + "integrity": "sha512-L2yqUOpf3tzlW9GVuipgLEcZxnO+96SzR6fjXMuxxNkIgFJ5+07mHCZ+HkHqaeZu8+3LKnNJJ1bKbjBETQAsrA==", + "requires": { + "@babel/helper-validator-identifier": "^7.10.1", + "lodash": "^4.17.13", + "to-fast-properties": "^2.0.0" + }, + "dependencies": { + "@babel/helper-validator-identifier": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.1.tgz", + "integrity": "sha512-5vW/JXLALhczRCWP0PnFDMCJAchlBvM7f4uk/jXritBnIa6E1KmqmtrS3yn1LAnxFBypQ3eneLuXjsnfQsgILw==" + } + } + }, + "@firebase/analytics": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/@firebase/analytics/-/analytics-0.3.6.tgz", + "integrity": "sha512-OgBPLsLcYSLBCBLMkuOPnW2YmGo0ruVBtnZ1Yhk0y54oCtrAcm0ijuI98h0evAdA7XfHPgmfKRpke9rU2X9OQQ==", + "requires": { + "@firebase/analytics-types": "0.3.1", + "@firebase/component": "0.1.13", + "@firebase/installations": "0.4.11", + "@firebase/logger": "0.2.5", + "@firebase/util": "0.2.48", + "tslib": "1.11.1" + }, + "dependencies": { + "@firebase/component": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.13.tgz", + "integrity": "sha512-DuSIM96NQkE3Yo77IOa5BWw8VBdvCR5cbMLNiFT4X3dTU15Dm0zHjncQHt/6rQpABGNYWAfOCJmSU1v6vc3DFA==", + "requires": { + "@firebase/util": "0.2.48", + "tslib": "1.11.1" + } + }, + "@firebase/logger": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.2.5.tgz", + "integrity": "sha512-qqw3m0tWs/qrg7axTZG/QZq24DIMdSY6dGoWuBn08ddq7+GLF5HiqkRj71XznYeUUbfRq5W9C/PSFnN4JxX+WA==" + }, + "@firebase/util": { + "version": "0.2.48", + "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.2.48.tgz", + "integrity": "sha512-6Wzq6IBF//3mrMTmTQ+JmceM0PMQpxV2GVfXhZn/4sMMkkhB0MA908nPDnatoHwUKyWE3BMw+uTLkyBnkuTu5A==", + "requires": { + "tslib": "1.11.1" + } + } + } + }, + "@firebase/analytics-types": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@firebase/analytics-types/-/analytics-types-0.3.1.tgz", + "integrity": "sha512-63vVJ5NIBh/JF8l9LuPrQYSzFimk7zYHySQB4Dk9rVdJ8kV/vGQoVTvRu1UW05sEc2Ug5PqtEChtTHU+9hvPcA==" + }, + "@firebase/app": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/@firebase/app/-/app-0.6.5.tgz", + "integrity": "sha512-rhId5P4egyaVp4HaLsqxV1dJYWbjAyyTnoW8r6t2uXyUGBKUiv+tdK97abkHCo4UQwq/GvUu0Drd96Cm7nEIeA==", + "requires": { + "@firebase/app-types": "0.6.1", + "@firebase/component": "0.1.13", + "@firebase/logger": "0.2.5", + "@firebase/util": "0.2.48", + "dom-storage": "2.1.0", + "tslib": "1.11.1", + "xmlhttprequest": "1.8.0" + }, + "dependencies": { + "@firebase/component": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.13.tgz", + "integrity": "sha512-DuSIM96NQkE3Yo77IOa5BWw8VBdvCR5cbMLNiFT4X3dTU15Dm0zHjncQHt/6rQpABGNYWAfOCJmSU1v6vc3DFA==", + "requires": { + "@firebase/util": "0.2.48", + "tslib": "1.11.1" + } + }, + "@firebase/logger": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.2.5.tgz", + "integrity": "sha512-qqw3m0tWs/qrg7axTZG/QZq24DIMdSY6dGoWuBn08ddq7+GLF5HiqkRj71XznYeUUbfRq5W9C/PSFnN4JxX+WA==" + }, + "@firebase/util": { + "version": "0.2.48", + "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.2.48.tgz", + "integrity": "sha512-6Wzq6IBF//3mrMTmTQ+JmceM0PMQpxV2GVfXhZn/4sMMkkhB0MA908nPDnatoHwUKyWE3BMw+uTLkyBnkuTu5A==", + "requires": { + "tslib": "1.11.1" + } + } + } + }, + "@firebase/app-types": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.6.1.tgz", + "integrity": "sha512-L/ZnJRAq7F++utfuoTKX4CLBG5YR7tFO3PLzG1/oXXKEezJ0kRL3CMRoueBEmTCzVb/6SIs2Qlaw++uDgi5Xyg==" + }, + "@firebase/auth": { + "version": "0.14.6", + "resolved": "https://registry.npmjs.org/@firebase/auth/-/auth-0.14.6.tgz", + "integrity": "sha512-7gaEUWhUubWBGfOXAZvpTpJqBJT9KyG83RXC6VnjSQIfNUaarHZ485WkzERil43A6KvIl+f4kHxfZShE6ZCK3A==", + "requires": { + "@firebase/auth-types": "0.10.1" + } + }, + "@firebase/auth-interop-types": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/@firebase/auth-interop-types/-/auth-interop-types-0.1.5.tgz", + "integrity": "sha512-88h74TMQ6wXChPA6h9Q3E1Jg6TkTHep2+k63OWg3s0ozyGVMeY+TTOti7PFPzq5RhszQPQOoCi59es4MaRvgCw==" + }, + "@firebase/auth-types": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/@firebase/auth-types/-/auth-types-0.10.1.tgz", + "integrity": "sha512-/+gBHb1O9x/YlG7inXfxff/6X3BPZt4zgBv4kql6HEmdzNQCodIRlEYnI+/da+lN+dha7PjaFH7C7ewMmfV7rw==" + }, + "@firebase/component": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.12.tgz", + "integrity": "sha512-03w800MxR/EW1m7N0Q46WNcngwdDIHDWpFPHTdbZEI6U/HuLks5RJQlBxWqb1P73nYPkN8YP3U8gTdqrDpqY3Q==", + "requires": { + "@firebase/util": "0.2.47", + "tslib": "1.11.1" + } + }, + "@firebase/database": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/@firebase/database/-/database-0.6.3.tgz", + "integrity": "sha512-gHoCISHQVLoq+rGu+PorYxMkhsjhXov3ocBxz/0uVdznNhrbKkAZaEKF+dIAsUPDlwSYeZuwWuik7xcV3DtRaw==", + "requires": { + "@firebase/auth-interop-types": "0.1.5", + "@firebase/component": "0.1.12", + "@firebase/database-types": "0.5.1", + "@firebase/logger": "0.2.4", + "@firebase/util": "0.2.47", + "faye-websocket": "0.11.3", + "tslib": "1.11.1" + } + }, + "@firebase/database-types": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-0.5.1.tgz", + "integrity": "sha512-onQxom1ZBYBJ648w/VNRzUewovEDAH7lvnrrpCd69ukkyrMk6rGEO/PQ9BcNEbhlNtukpsqRS0oNOFlHs0FaSA==", + "requires": { + "@firebase/app-types": "0.6.1" + } + }, + "@firebase/firestore": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/@firebase/firestore/-/firestore-1.15.0.tgz", + "integrity": "sha512-4eSBDY2hb/og8OEFZSjjzlb0y5+cWpridtQHLYsM4IPcOwxnnlE6RjGWN+UUxgKtvlkvOBdUIlTjL/YYoF+QcA==", + "requires": { + "@firebase/component": "0.1.13", + "@firebase/firestore-types": "1.11.0", + "@firebase/logger": "0.2.5", + "@firebase/util": "0.2.48", + "@firebase/webchannel-wrapper": "0.2.41", + "@grpc/grpc-js": "0.8.1", + "@grpc/proto-loader": "^0.5.0", + "tslib": "1.11.1" + }, + "dependencies": { + "@firebase/component": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.13.tgz", + "integrity": "sha512-DuSIM96NQkE3Yo77IOa5BWw8VBdvCR5cbMLNiFT4X3dTU15Dm0zHjncQHt/6rQpABGNYWAfOCJmSU1v6vc3DFA==", + "requires": { + "@firebase/util": "0.2.48", + "tslib": "1.11.1" + } + }, + "@firebase/logger": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.2.5.tgz", + "integrity": "sha512-qqw3m0tWs/qrg7axTZG/QZq24DIMdSY6dGoWuBn08ddq7+GLF5HiqkRj71XznYeUUbfRq5W9C/PSFnN4JxX+WA==" + }, + "@firebase/util": { + "version": "0.2.48", + "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.2.48.tgz", + "integrity": "sha512-6Wzq6IBF//3mrMTmTQ+JmceM0PMQpxV2GVfXhZn/4sMMkkhB0MA908nPDnatoHwUKyWE3BMw+uTLkyBnkuTu5A==", + "requires": { + "tslib": "1.11.1" + } + }, + "@grpc/grpc-js": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-0.8.1.tgz", + "integrity": "sha512-e8gSjRZnOUefsR3obOgxG9RtYW2Mw83hh7ogE2ByCdgRhoX0mdnJwBcZOami3E0l643KCTZvORFwfSEi48KFIQ==", + "requires": { + "semver": "^6.2.0" + } + } + } + }, + "@firebase/firestore-types": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/@firebase/firestore-types/-/firestore-types-1.11.0.tgz", + "integrity": "sha512-hD7+cmMUvT5OJeWVrcRkE87PPuj/0/Wic6bntCopJE1WIX/Dm117AUkHgKd3S7Ici6DLp4bdlx1MjjwWL5942w==" + }, + "@firebase/functions": { + "version": "0.4.45", + "resolved": "https://registry.npmjs.org/@firebase/functions/-/functions-0.4.45.tgz", + "integrity": "sha512-Sy0D+52bkabdapTGxPlX+1b2FH+0BEJBmboLfM7EySZV/32oI277pDYKhyp9Pm//eOLspMOpEDvJz1WK8xmQcw==", + "requires": { + "@firebase/component": "0.1.13", + "@firebase/functions-types": "0.3.17", + "@firebase/messaging-types": "0.4.5", + "isomorphic-fetch": "2.2.1", + "tslib": "1.11.1" + }, + "dependencies": { + "@firebase/component": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.13.tgz", + "integrity": "sha512-DuSIM96NQkE3Yo77IOa5BWw8VBdvCR5cbMLNiFT4X3dTU15Dm0zHjncQHt/6rQpABGNYWAfOCJmSU1v6vc3DFA==", + "requires": { + "@firebase/util": "0.2.48", + "tslib": "1.11.1" + } + }, + "@firebase/util": { + "version": "0.2.48", + "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.2.48.tgz", + "integrity": "sha512-6Wzq6IBF//3mrMTmTQ+JmceM0PMQpxV2GVfXhZn/4sMMkkhB0MA908nPDnatoHwUKyWE3BMw+uTLkyBnkuTu5A==", + "requires": { + "tslib": "1.11.1" + } + } + } + }, + "@firebase/functions-types": { + "version": "0.3.17", + "resolved": "https://registry.npmjs.org/@firebase/functions-types/-/functions-types-0.3.17.tgz", + "integrity": "sha512-DGR4i3VI55KnYk4IxrIw7+VG7Q3gA65azHnZxo98Il8IvYLr2UTBlSh72dTLlDf25NW51HqvJgYJDKvSaAeyHQ==" + }, + "@firebase/installations": { + "version": "0.4.11", + "resolved": "https://registry.npmjs.org/@firebase/installations/-/installations-0.4.11.tgz", + "integrity": "sha512-ri+8O6vZPF0JKXboMzYFAbN7rn0OeUKLeMuCWdKZGJZD8NS8NRk/YvGRBa+IrkrwBeNNA/bMBUTEnhjN4CdVgQ==", + "requires": { + "@firebase/component": "0.1.13", + "@firebase/installations-types": "0.3.4", + "@firebase/util": "0.2.48", + "idb": "3.0.2", + "tslib": "1.11.1" + }, + "dependencies": { + "@firebase/component": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.13.tgz", + "integrity": "sha512-DuSIM96NQkE3Yo77IOa5BWw8VBdvCR5cbMLNiFT4X3dTU15Dm0zHjncQHt/6rQpABGNYWAfOCJmSU1v6vc3DFA==", + "requires": { + "@firebase/util": "0.2.48", + "tslib": "1.11.1" + } + }, + "@firebase/util": { + "version": "0.2.48", + "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.2.48.tgz", + "integrity": "sha512-6Wzq6IBF//3mrMTmTQ+JmceM0PMQpxV2GVfXhZn/4sMMkkhB0MA908nPDnatoHwUKyWE3BMw+uTLkyBnkuTu5A==", + "requires": { + "tslib": "1.11.1" + } + } + } + }, + "@firebase/installations-types": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@firebase/installations-types/-/installations-types-0.3.4.tgz", + "integrity": "sha512-RfePJFovmdIXb6rYwtngyxuEcWnOrzdZd9m7xAW0gRxDIjBT20n3BOhjpmgRWXo/DAxRmS7bRjWAyTHY9cqN7Q==" + }, + "@firebase/logger": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.2.4.tgz", + "integrity": "sha512-akHkOU7izYB1okp/B5sxClGjjw6KvZdSHyjNM5pKd67Zg5W6PsbkI/GFNv21+y6LkUkJwDRbdeDgJoYXWT3mMA==" + }, + "@firebase/messaging": { + "version": "0.6.17", + "resolved": "https://registry.npmjs.org/@firebase/messaging/-/messaging-0.6.17.tgz", + "integrity": "sha512-wwAn2HrklhBxHpk5UpudJ0wCrlUC9ovFJ88cSOALf82po423IOwR5ijq1z2zKzZiz4D1dLv7rJIqZ0N1MZ/Giw==", + "requires": { + "@firebase/component": "0.1.13", + "@firebase/installations": "0.4.11", + "@firebase/messaging-types": "0.4.5", + "@firebase/util": "0.2.48", + "idb": "3.0.2", + "tslib": "1.11.1" + }, + "dependencies": { + "@firebase/component": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.13.tgz", + "integrity": "sha512-DuSIM96NQkE3Yo77IOa5BWw8VBdvCR5cbMLNiFT4X3dTU15Dm0zHjncQHt/6rQpABGNYWAfOCJmSU1v6vc3DFA==", + "requires": { + "@firebase/util": "0.2.48", + "tslib": "1.11.1" + } + }, + "@firebase/util": { + "version": "0.2.48", + "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.2.48.tgz", + "integrity": "sha512-6Wzq6IBF//3mrMTmTQ+JmceM0PMQpxV2GVfXhZn/4sMMkkhB0MA908nPDnatoHwUKyWE3BMw+uTLkyBnkuTu5A==", + "requires": { + "tslib": "1.11.1" + } + } + } + }, + "@firebase/messaging-types": { + "version": "0.4.5", + "resolved": "https://registry.npmjs.org/@firebase/messaging-types/-/messaging-types-0.4.5.tgz", + "integrity": "sha512-sux4fgqr/0KyIxqzHlatI04Ajs5rc3WM+WmtCpxrKP1E5Bke8xu/0M+2oy4lK/sQ7nov9z15n3iltAHCgTRU3Q==" + }, + "@firebase/performance": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/@firebase/performance/-/performance-0.3.6.tgz", + "integrity": "sha512-AB+ohBYgF8fe9FacDAcwJaBLRrXgt93no6Pj14xzQ4oX31dQpPrWZdFfNYEUZRU1Gnb/fqWlCaBTObzUXD5cag==", + "requires": { + "@firebase/component": "0.1.13", + "@firebase/installations": "0.4.11", + "@firebase/logger": "0.2.5", + "@firebase/performance-types": "0.0.13", + "@firebase/util": "0.2.48", + "tslib": "1.11.1" + }, + "dependencies": { + "@firebase/component": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.13.tgz", + "integrity": "sha512-DuSIM96NQkE3Yo77IOa5BWw8VBdvCR5cbMLNiFT4X3dTU15Dm0zHjncQHt/6rQpABGNYWAfOCJmSU1v6vc3DFA==", + "requires": { + "@firebase/util": "0.2.48", + "tslib": "1.11.1" + } + }, + "@firebase/logger": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.2.5.tgz", + "integrity": "sha512-qqw3m0tWs/qrg7axTZG/QZq24DIMdSY6dGoWuBn08ddq7+GLF5HiqkRj71XznYeUUbfRq5W9C/PSFnN4JxX+WA==" + }, + "@firebase/util": { + "version": "0.2.48", + "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.2.48.tgz", + "integrity": "sha512-6Wzq6IBF//3mrMTmTQ+JmceM0PMQpxV2GVfXhZn/4sMMkkhB0MA908nPDnatoHwUKyWE3BMw+uTLkyBnkuTu5A==", + "requires": { + "tslib": "1.11.1" + } + } + } + }, + "@firebase/performance-types": { + "version": "0.0.13", + "resolved": "https://registry.npmjs.org/@firebase/performance-types/-/performance-types-0.0.13.tgz", + "integrity": "sha512-6fZfIGjQpwo9S5OzMpPyqgYAUZcFzZxHFqOyNtorDIgNXq33nlldTL/vtaUZA8iT9TT5cJlCrF/jthKU7X21EA==" + }, + "@firebase/polyfill": { + "version": "0.3.36", + "resolved": "https://registry.npmjs.org/@firebase/polyfill/-/polyfill-0.3.36.tgz", + "integrity": "sha512-zMM9oSJgY6cT2jx3Ce9LYqb0eIpDE52meIzd/oe/y70F+v9u1LDqk5kUF5mf16zovGBWMNFmgzlsh6Wj0OsFtg==", + "requires": { + "core-js": "3.6.5", + "promise-polyfill": "8.1.3", + "whatwg-fetch": "2.0.4" + }, + "dependencies": { + "whatwg-fetch": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-2.0.4.tgz", + "integrity": "sha512-dcQ1GWpOD/eEQ97k66aiEVpNnapVj90/+R+SXTPYGHpYBBypfKJEQjLrvMZ7YXbKm21gXd4NcuxUTjiv1YtLng==" + } + } + }, + "@firebase/remote-config": { + "version": "0.1.22", + "resolved": "https://registry.npmjs.org/@firebase/remote-config/-/remote-config-0.1.22.tgz", + "integrity": "sha512-xX/b+euI/RP1qAWqNI5YkZ4VFNL00CI7jBJmPzoBbSl1vVglR1ya7u1fbC5SeowxnD1/0QIOYbe5sdnqjmLsyg==", + "requires": { + "@firebase/component": "0.1.13", + "@firebase/installations": "0.4.11", + "@firebase/logger": "0.2.5", + "@firebase/remote-config-types": "0.1.9", + "@firebase/util": "0.2.48", + "tslib": "1.11.1" + }, + "dependencies": { + "@firebase/component": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.13.tgz", + "integrity": "sha512-DuSIM96NQkE3Yo77IOa5BWw8VBdvCR5cbMLNiFT4X3dTU15Dm0zHjncQHt/6rQpABGNYWAfOCJmSU1v6vc3DFA==", + "requires": { + "@firebase/util": "0.2.48", + "tslib": "1.11.1" + } + }, + "@firebase/logger": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.2.5.tgz", + "integrity": "sha512-qqw3m0tWs/qrg7axTZG/QZq24DIMdSY6dGoWuBn08ddq7+GLF5HiqkRj71XznYeUUbfRq5W9C/PSFnN4JxX+WA==" + }, + "@firebase/util": { + "version": "0.2.48", + "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.2.48.tgz", + "integrity": "sha512-6Wzq6IBF//3mrMTmTQ+JmceM0PMQpxV2GVfXhZn/4sMMkkhB0MA908nPDnatoHwUKyWE3BMw+uTLkyBnkuTu5A==", + "requires": { + "tslib": "1.11.1" + } + } + } + }, + "@firebase/remote-config-types": { + "version": "0.1.9", + "resolved": "https://registry.npmjs.org/@firebase/remote-config-types/-/remote-config-types-0.1.9.tgz", + "integrity": "sha512-G96qnF3RYGbZsTRut7NBX0sxyczxt1uyCgXQuH/eAfUCngxjEGcZQnBdy6mvSdqdJh5mC31rWPO4v9/s7HwtzA==" + }, + "@firebase/storage": { + "version": "0.3.35", + "resolved": "https://registry.npmjs.org/@firebase/storage/-/storage-0.3.35.tgz", + "integrity": "sha512-kS+P5X3lla9bpeWVwmRzJ5atMDPlhLPa8jgutN9vWXWfVnlj82U8VqeAqWc8ndHumHiV0TYLDk9DdGfs6rFL3w==", + "requires": { + "@firebase/component": "0.1.13", + "@firebase/storage-types": "0.3.12", + "@firebase/util": "0.2.48", + "tslib": "1.11.1" + }, + "dependencies": { + "@firebase/component": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.13.tgz", + "integrity": "sha512-DuSIM96NQkE3Yo77IOa5BWw8VBdvCR5cbMLNiFT4X3dTU15Dm0zHjncQHt/6rQpABGNYWAfOCJmSU1v6vc3DFA==", + "requires": { + "@firebase/util": "0.2.48", + "tslib": "1.11.1" + } + }, + "@firebase/util": { + "version": "0.2.48", + "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.2.48.tgz", + "integrity": "sha512-6Wzq6IBF//3mrMTmTQ+JmceM0PMQpxV2GVfXhZn/4sMMkkhB0MA908nPDnatoHwUKyWE3BMw+uTLkyBnkuTu5A==", + "requires": { + "tslib": "1.11.1" + } + } + } + }, + "@firebase/storage-types": { + "version": "0.3.12", + "resolved": "https://registry.npmjs.org/@firebase/storage-types/-/storage-types-0.3.12.tgz", + "integrity": "sha512-DDV6Fs6aYoGw3w/zZZTkqiipxihnsvHf6znbeZYjIIHit3tr1uLJdGPDPiCTfZcTGPpg2ux6ZmvNDvVgJdHALw==" + }, + "@firebase/util": { + "version": "0.2.47", + "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.2.47.tgz", + "integrity": "sha512-RjcIvcfswyxYhf0OMXod+qeI/933wl9FGLIszf0/O1yMZ/s8moXcse7xnOpMjmQPRLB9vHzCMoxW5X90kKg/bQ==", + "requires": { + "tslib": "1.11.1" + } + }, + "@firebase/webchannel-wrapper": { + "version": "0.2.41", + "resolved": "https://registry.npmjs.org/@firebase/webchannel-wrapper/-/webchannel-wrapper-0.2.41.tgz", + "integrity": "sha512-XcdMT5PSZHiuf7LJIhzKIe+RyYa25S3LHRRvLnZc6iFjwXkrSDJ8J/HWO6VT8d2ZTbawp3VcLEjRF/VN8glCrA==" + }, + "@google-cloud/common": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@google-cloud/common/-/common-2.4.0.tgz", + "integrity": "sha512-zWFjBS35eI9leAHhjfeOYlK5Plcuj/77EzstnrJIZbKgF/nkqjcQuGiMCpzCwOfPyUbz8ZaEOYgbHa759AKbjg==", + "optional": true, + "requires": { + "@google-cloud/projectify": "^1.0.0", + "@google-cloud/promisify": "^1.0.0", + "arrify": "^2.0.0", + "duplexify": "^3.6.0", + "ent": "^2.2.0", + "extend": "^3.0.2", + "google-auth-library": "^5.5.0", + "retry-request": "^4.0.0", + "teeny-request": "^6.0.0" + } + }, + "@google-cloud/firestore": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/@google-cloud/firestore/-/firestore-3.8.0.tgz", + "integrity": "sha512-FEv52jhsRAV/0nHaMy0PAmExYkYRLDoOXRB+85euaC72HZU8aV/bgOi1OvAeiD+ogoO39ilxiHQh5PaRHIolug==", + "optional": true, + "requires": { + "deep-equal": "^2.0.0", + "functional-red-black-tree": "^1.0.1", + "google-gax": "^1.13.0", + "readable-stream": "^3.4.0", + "through2": "^3.0.0" + } + }, + "@google-cloud/paginator": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@google-cloud/paginator/-/paginator-2.0.3.tgz", + "integrity": "sha512-kp/pkb2p/p0d8/SKUu4mOq8+HGwF8NPzHWkj+VKrIPQPyMRw8deZtrO/OcSiy9C/7bpfU5Txah5ltUNfPkgEXg==", + "optional": true, + "requires": { + "arrify": "^2.0.0", + "extend": "^3.0.2" + } + }, + "@google-cloud/projectify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@google-cloud/projectify/-/projectify-1.0.4.tgz", + "integrity": "sha512-ZdzQUN02eRsmTKfBj9FDL0KNDIFNjBn/d6tHQmA/+FImH5DO6ZV8E7FzxMgAUiVAUq41RFAkb25p1oHOZ8psfg==", + "optional": true + }, + "@google-cloud/promisify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@google-cloud/promisify/-/promisify-1.0.4.tgz", + "integrity": "sha512-VccZDcOql77obTnFh0TbNED/6ZbbmHDf8UMNnzO1d5g9V0Htfm4k5cllY8P1tJsRKC3zWYGRLaViiupcgVjBoQ==", + "optional": true + }, + "@google-cloud/storage": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@google-cloud/storage/-/storage-4.7.0.tgz", + "integrity": "sha512-f0guAlbeg7Z0m3gKjCfBCu7FG9qS3M3oL5OQQxlvGoPtK7/qg3+W+KQV73O2/sbuS54n0Kh2mvT5K2FWzF5vVQ==", + "optional": true, + "requires": { + "@google-cloud/common": "^2.1.1", + "@google-cloud/paginator": "^2.0.0", + "@google-cloud/promisify": "^1.0.0", + "arrify": "^2.0.0", + "compressible": "^2.0.12", + "concat-stream": "^2.0.0", + "date-and-time": "^0.13.0", + "duplexify": "^3.5.0", + "extend": "^3.0.2", + "gaxios": "^3.0.0", + "gcs-resumable-upload": "^2.2.4", + "hash-stream-validation": "^0.2.2", + "mime": "^2.2.0", + "mime-types": "^2.0.8", + "onetime": "^5.1.0", + "p-limit": "^2.2.0", + "pumpify": "^2.0.0", + "readable-stream": "^3.4.0", + "snakeize": "^0.1.0", + "stream-events": "^1.0.1", + "through2": "^3.0.0", + "xdg-basedir": "^4.0.0" + } + }, + "@grpc/grpc-js": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.0.4.tgz", + "integrity": "sha512-Qawt6HUrEmljQMPWnLnIXpcjelmtIAydi3M9awiG02WWJ1CmIvFEx4IOC1EsWUWUlabOGksRbpfvoIeZKFTNXw==", + "optional": true, + "requires": { + "google-auth-library": "^6.0.0", + "semver": "^6.2.0" + }, + "dependencies": { + "google-auth-library": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-6.0.0.tgz", + "integrity": "sha512-uLydy1t6SHN/EvYUJrtN3GCHFrnJ0c8HJjOxXiGjoTuYHIoCUT3jVxnzmjHwVnSdkfE9Akasm2rM6qG1COTXfQ==", + "optional": true, + "requires": { + "arrify": "^2.0.0", + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "fast-text-encoding": "^1.0.0", + "gaxios": "^3.0.0", + "gcp-metadata": "^4.0.0", + "gtoken": "^5.0.0", + "jws": "^4.0.0", + "lru-cache": "^5.0.0" + } + } + } + }, + "@grpc/proto-loader": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.5.4.tgz", + "integrity": "sha512-HTM4QpI9B2XFkPz7pjwMyMgZchJ93TVkL3kWPW8GDMDKYxsMnmf4w2TNMJK7+KNiYHS5cJrCEAFlF+AwtXWVPA==", + "requires": { + "lodash.camelcase": "^4.3.0", + "protobufjs": "^6.8.6" + } + }, + "@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha1-m4sMxmPWaafY9vXQiToU00jzD78=" + }, + "@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==" + }, + "@protobufjs/codegen": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", + "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==" + }, + "@protobufjs/eventemitter": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", + "integrity": "sha1-NVy8mLr61ZePntCV85diHx0Ga3A=" + }, + "@protobufjs/fetch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", + "integrity": "sha1-upn7WYYUr2VwDBYZ/wbUVLDYTEU=", + "requires": { + "@protobufjs/aspromise": "^1.1.1", + "@protobufjs/inquire": "^1.1.0" + } + }, + "@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha1-Xp4avctz/Ap8uLKR33jIy9l7h9E=" + }, + "@protobufjs/inquire": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", + "integrity": "sha1-/yAOPnzyQp4tyvwRQIKOjMY48Ik=" + }, + "@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha1-bMKyDFya1q0NzP0hynZz2Nf79o0=" + }, + "@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha1-Cf0V8tbTq/qbZbw2ZQbWrXhG/1Q=" + }, + "@protobufjs/utf8": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", + "integrity": "sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA=" + }, + "@tootallnate/once": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", + "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==", + "optional": true + }, + "@types/body-parser": { + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.0.tgz", + "integrity": "sha512-W98JrE0j2K78swW4ukqMleo8R7h/pFETjM2DQ90MF6XK2i4LO4W3gQ71Lt4w3bfm2EvVSyWHplECvB5sK22yFQ==", + "requires": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "@types/connect": { + "version": "3.4.33", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.33.tgz", + "integrity": "sha512-2+FrkXY4zllzTNfJth7jOqEHC+enpLeGslEhpnTAkg21GkRrWV4SsAtqchtT4YS9/nODBU2/ZfsBY2X4J/dX7A==", + "requires": { + "@types/node": "*" + } + }, + "@types/express": { + "version": "4.17.6", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.6.tgz", + "integrity": "sha512-n/mr9tZI83kd4azlPG5y997C/M4DNABK9yErhFM6hKdym4kkmd9j0vtsJyjFIwfRBxtrxZtAfGZCNRIBMFLK5w==", + "requires": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "*", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "@types/express-serve-static-core": { + "version": "4.17.7", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.7.tgz", + "integrity": "sha512-EMgTj/DF9qpgLXyc+Btimg+XoH7A2liE8uKul8qSmMTHCeNYzydDKFdsJskDvw42UsesCnhO63dO0Grbj8J4Dw==", + "requires": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*" + } + }, + "@types/fs-extra": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-8.1.1.tgz", + "integrity": "sha512-TcUlBem321DFQzBNuz8p0CLLKp0VvF/XH9E4KHNmgwyp4E3AfgI5cjiIVZWlbfThBop2qxFIh4+LeY6hVWWZ2w==", + "optional": true, + "requires": { + "@types/node": "*" + } + }, + "@types/lodash": { + "version": "4.14.152", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.152.tgz", + "integrity": "sha512-Vwf9YF2x1GE3WNeUMjT5bTHa2DqgUo87ocdgTScupY2JclZ5Nn7W2RLM/N0+oreexUk8uaVugR81NnTY/jNNXg==", + "dev": true + }, + "@types/long": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.1.tgz", + "integrity": "sha512-5tXH6Bx/kNGd3MgffdmP4dy2Z+G4eaXw0SE81Tq3BNadtnMR5/ySMzX4SLEzHJzSmPNn4HIdpQsBvXMUykr58w==" + }, + "@types/mime": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-2.0.2.tgz", + "integrity": "sha512-4kPlzbljFcsttWEq6aBW0OZe6BDajAmyvr2xknBG92tejQnvdGtT9+kXSZ580DqpxY9qG2xeQVF9Dq0ymUTo5Q==" + }, + "@types/node": { + "version": "8.10.61", + "resolved": "https://registry.npmjs.org/@types/node/-/node-8.10.61.tgz", + "integrity": "sha512-l+zSbvT8TPRaCxL1l9cwHCb0tSqGAGcjPJFItGGYat5oCTiq1uQQKYg5m7AF1mgnEBzFXGLJ2LRmNjtreRX76Q==" + }, + "@types/qs": { + "version": "6.9.3", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.3.tgz", + "integrity": "sha512-7s9EQWupR1fTc2pSMtXRQ9w9gLOcrJn+h7HOXw4evxyvVqMi4f+q7d2tnFe3ng3SNHjtK+0EzGMGFUQX4/AQRA==" + }, + "@types/range-parser": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.3.tgz", + "integrity": "sha512-ewFXqrQHlFsgc09MK5jP5iR7vumV/BYayNC6PgJO2LPe8vrnNFyjQjSppfEngITi0qvfKtzFvgKymGheFM9UOA==" + }, + "@types/serve-static": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.4.tgz", + "integrity": "sha512-jTDt0o/YbpNwZbQmE/+2e+lfjJEJJR0I3OFaKQKPWkASkCoW3i6fsUnqudSMcNAfbtmADGu8f4MV4q+GqULmug==", + "requires": { + "@types/express-serve-static-core": "*", + "@types/mime": "*" + } + }, + "abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "optional": true, + "requires": { + "event-target-shim": "^5.0.0" + } + }, + "accepts": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", + "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", + "requires": { + "mime-types": "~2.1.24", + "negotiator": "0.6.2" + } + }, + "acorn": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.2.0.tgz", + "integrity": "sha512-apwXVmYVpQ34m/i71vrApRrRKCWQnZZF1+npOD0WV5xZFfwWOmKGQ2RWlfdy9vWITsenisM8M0Qeq8agcFHNiQ==" + }, + "agent-base": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.0.tgz", + "integrity": "sha512-j1Q7cSCqN+AwrmDd+pzgqc0/NpC655x2bUf5ZjRIO77DcNBFmh+OgRNzF6OKdCC9RSCb19fGd99+bhXFdkRNqw==", + "optional": true, + "requires": { + "debug": "4" + } + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "array-filter": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-filter/-/array-filter-1.0.0.tgz", + "integrity": "sha1-uveeYubvTCpMC4MSMtr/7CUfnYM=", + "optional": true + }, + "array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" + }, + "arrify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz", + "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==", + "optional": true + }, + "asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=" + }, + "assert-never": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/assert-never/-/assert-never-1.2.1.tgz", + "integrity": "sha512-TaTivMB6pYI1kXwrFlEhLeGfOqoDNdTxjCdwRfFFkEA30Eu+k48W34nlok2EYWJfFFzqaEmichdNM7th6M5HNw==" + }, + "assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==" + }, + "available-typed-arrays": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.2.tgz", + "integrity": "sha512-XWX3OX8Onv97LMk/ftVyBibpGwY5a8SmuxZPzeOxqmuEqUCOM9ZE+uIaD1VNJ5QnvU2UQusvmKbuM1FR8QWGfQ==", + "optional": true, + "requires": { + "array-filter": "^1.0.0" + } + }, + "babel-walk": { + "version": "3.0.0-canary-5", + "resolved": "https://registry.npmjs.org/babel-walk/-/babel-walk-3.0.0-canary-5.tgz", + "integrity": "sha512-GAwkz0AihzY5bkwIY5QDR+LvsRQgB/B+1foMPvi0FZPMl5fjD7ICiznUiBdLYMH1QYe6vqu4gWYytZOccLouFw==", + "requires": { + "@babel/types": "^7.9.6" + } + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "base64-js": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz", + "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==", + "optional": true + }, + "basic-auth": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", + "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", + "requires": { + "safe-buffer": "5.1.2" + }, + "dependencies": { + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + } + } + }, + "bignumber.js": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-7.2.1.tgz", + "integrity": "sha512-S4XzBk5sMB+Rcb/LNcpzXr57VRTxgAvaAEDAl1AwRx27j00hT84O6OkteE7u8UB3NuaaygCRrEpqox4uDOrbdQ==", + "optional": true + }, + "body-parser": { + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", + "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", + "requires": { + "bytes": "3.1.0", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "~1.1.2", + "http-errors": "1.7.2", + "iconv-lite": "0.4.24", + "on-finished": "~2.3.0", + "qs": "6.7.0", + "raw-body": "2.4.0", + "type-is": "~1.6.17" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "http-errors": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", + "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.1", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.0" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + } + } + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=" + }, + "buffer-from": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", + "optional": true + }, + "builtin-modules": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", + "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=", + "dev": true + }, + "bytes": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", + "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==" + }, + "chai": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.2.0.tgz", + "integrity": "sha512-XQU3bhBukrOsQCuwZndwGcCVQHyZi53fQ6Ys1Fym7E4olpIqqZZhhoFJoaKVvV17lWQoXYwgWN2nF5crA8J2jw==", + "requires": { + "assertion-error": "^1.1.0", + "check-error": "^1.0.2", + "deep-eql": "^3.0.1", + "get-func-name": "^2.0.0", + "pathval": "^1.1.0", + "type-detect": "^4.0.5" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "character-parser": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/character-parser/-/character-parser-2.2.0.tgz", + "integrity": "sha1-x84o821LzZdE5f/CxfzeHHMmH8A=", + "requires": { + "is-regex": "^1.0.3" + } + }, + "check-error": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", + "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=" + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true + }, + "compressible": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", + "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", + "optional": true, + "requires": { + "mime-db": ">= 1.43.0 < 2" + } + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "concat-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz", + "integrity": "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==", + "optional": true, + "requires": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.0.2", + "typedarray": "^0.0.6" + } + }, + "configstore": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/configstore/-/configstore-5.0.1.tgz", + "integrity": "sha512-aMKprgk5YhBNyH25hj8wGt2+D52Sw1DRRIzqBwLp2Ya9mFmY8KPvvtvmna8SxVR9JMZ4kzMD68N22vlaRpkeFA==", + "optional": true, + "requires": { + "dot-prop": "^5.2.0", + "graceful-fs": "^4.1.2", + "make-dir": "^3.0.0", + "unique-string": "^2.0.0", + "write-file-atomic": "^3.0.0", + "xdg-basedir": "^4.0.0" + } + }, + "constantinople": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/constantinople/-/constantinople-4.0.1.tgz", + "integrity": "sha512-vCrqcSIq4//Gx74TXXCGnHpulY1dskqLTFGDmhrGxzeXL8lF8kvXv6mpNWlJj1uD4DW23D4ljAqbY4RRaaUZIw==", + "requires": { + "@babel/parser": "^7.6.0", + "@babel/types": "^7.6.1" + } + }, + "content-disposition": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", + "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", + "requires": { + "safe-buffer": "5.1.2" + }, + "dependencies": { + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + } + } + }, + "content-type": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" + }, + "cookie": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", + "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==" + }, + "cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" + }, + "core-js": { + "version": "3.6.5", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.6.5.tgz", + "integrity": "sha512-vZVEEwZoIsI+vPEuoF9Iqf5H7/M3eeQqWlQnYa8FSKKePuYTf5MWnxb5SDAzCa60b3JBRS5g9b+Dq7b1y/RCrA==" + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", + "optional": true + }, + "cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "requires": { + "object-assign": "^4", + "vary": "^1" + } + }, + "crypto-random-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz", + "integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==", + "optional": true + }, + "date-and-time": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/date-and-time/-/date-and-time-0.13.1.tgz", + "integrity": "sha512-/Uge9DJAT+s+oAcDxtBhyR8+sKjUnZbYmyhbmWjTHNtX7B7oWD8YyYdeXcBRbwSj6hVvj+IQegJam7m7czhbFw==", + "optional": true + }, + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "optional": true, + "requires": { + "ms": "^2.1.1" + } + }, + "deep-eql": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", + "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==", + "requires": { + "type-detect": "^4.0.0" + } + }, + "deep-equal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.0.3.tgz", + "integrity": "sha512-Spqdl4H+ky45I9ByyJtXteOm9CaIrPmnIPmOhrkKGNYWeDgCvJ8jNYVCTjChxW4FqGuZnLHADc8EKRMX6+CgvA==", + "optional": true, + "requires": { + "es-abstract": "^1.17.5", + "es-get-iterator": "^1.1.0", + "is-arguments": "^1.0.4", + "is-date-object": "^1.0.2", + "is-regex": "^1.0.5", + "isarray": "^2.0.5", + "object-is": "^1.1.2", + "object-keys": "^1.1.1", + "object.assign": "^4.1.0", + "regexp.prototype.flags": "^1.3.0", + "side-channel": "^1.0.2", + "which-boxed-primitive": "^1.0.1", + "which-collection": "^1.0.1", + "which-typed-array": "^1.1.2" + } + }, + "define-properties": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", + "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "requires": { + "object-keys": "^1.0.12" + } + }, + "depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" + }, + "destroy": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", + "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" + }, + "dicer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/dicer/-/dicer-0.3.0.tgz", + "integrity": "sha512-MdceRRWqltEG2dZqO769g27N/3PXfcKl04VhYnBlo2YhH7zPi88VebsjTKclaOyiuMaGU72hTfw3VkUitGcVCA==", + "requires": { + "streamsearch": "0.1.2" + } + }, + "diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true + }, + "doctypes": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/doctypes/-/doctypes-1.1.0.tgz", + "integrity": "sha1-6oCxBqh1OHdOijpKWv4pPeSJ4Kk=" + }, + "dom-storage": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/dom-storage/-/dom-storage-2.1.0.tgz", + "integrity": "sha512-g6RpyWXzl0RR6OTElHKBl7nwnK87GUyZMYC7JWsB/IA73vpqK2K6LT39x4VepLxlSsWBFrPVLnsSR5Jyty0+2Q==" + }, + "dot-prop": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.2.0.tgz", + "integrity": "sha512-uEUyaDKoSQ1M4Oq8l45hSE26SnTxL6snNnqvK/VWx5wJhmff5z0FUVJDKDanor/6w3kzE3i7XZOk+7wC0EXr1A==", + "optional": true, + "requires": { + "is-obj": "^2.0.0" + } + }, + "duplexify": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz", + "integrity": "sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==", + "optional": true, + "requires": { + "end-of-stream": "^1.0.0", + "inherits": "^2.0.1", + "readable-stream": "^2.0.0", + "stream-shift": "^1.0.0" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "optional": true + }, + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "optional": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "optional": true + } + } + }, + "ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" + }, + "encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" + }, + "encoding": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.12.tgz", + "integrity": "sha1-U4tm8+5izRq1HsMjgp0flIDHS+s=", + "requires": { + "iconv-lite": "~0.4.13" + } + }, + "end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "optional": true, + "requires": { + "once": "^1.4.0" + } + }, + "ent": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/ent/-/ent-2.2.0.tgz", + "integrity": "sha1-6WQhkyWiHQX0RGai9obtbOX13R0=", + "optional": true + }, + "es-abstract": { + "version": "1.17.5", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.5.tgz", + "integrity": "sha512-BR9auzDbySxOcfog0tLECW8l28eRGpDpU3Dm3Hp4q/N+VtLTmyj4EUN088XZWQDW/hzj6sYRDXeOFsaAODKvpg==", + "requires": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.1.5", + "is-regex": "^1.0.5", + "object-inspect": "^1.7.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.0", + "string.prototype.trimleft": "^2.1.1", + "string.prototype.trimright": "^2.1.1" + } + }, + "es-get-iterator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.0.tgz", + "integrity": "sha512-UfrmHuWQlNMTs35e1ypnvikg6jCz3SK8v8ImvmDsh36fCVUR1MqoFDiyn0/k52C8NqO3YsO8Oe0azeesNuqSsQ==", + "optional": true, + "requires": { + "es-abstract": "^1.17.4", + "has-symbols": "^1.0.1", + "is-arguments": "^1.0.4", + "is-map": "^2.0.1", + "is-set": "^2.0.1", + "is-string": "^1.0.5", + "isarray": "^2.0.5" + } + }, + "es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, + "escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true + }, + "etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" + }, + "event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "optional": true + }, + "express": { + "version": "4.17.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", + "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==", + "requires": { + "accepts": "~1.3.7", + "array-flatten": "1.1.1", + "body-parser": "1.19.0", + "content-disposition": "0.5.3", + "content-type": "~1.0.4", + "cookie": "0.4.0", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "~1.1.2", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "~1.1.2", + "fresh": "0.5.2", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.5", + "qs": "6.7.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.1.2", + "send": "0.17.1", + "serve-static": "1.14.1", + "setprototypeof": "1.1.1", + "statuses": "~1.5.0", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + } + } + }, + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "optional": true + }, + "fast-text-encoding": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/fast-text-encoding/-/fast-text-encoding-1.0.2.tgz", + "integrity": "sha512-5rQdinSsycpzvAoHga2EDn+LRX1d5xLFsuNG0Kg61JrAT/tASXcLL0nf/33v+sAxlQcfYmWbTURa1mmAf55jGw==", + "optional": true + }, + "faye-websocket": { + "version": "0.11.3", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.3.tgz", + "integrity": "sha512-D2y4bovYpzziGgbHYtGCMjlJM36vAl/y+xUyn1C+FVx8szd1E+86KwVw6XvYSzOP8iMpm1X0I4xJD+QtUb36OA==", + "requires": { + "websocket-driver": ">=0.5.1" + } + }, + "finalhandler": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", + "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", + "requires": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "statuses": "~1.5.0", + "unpipe": "~1.0.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + } + } + }, + "firebase": { + "version": "7.15.0", + "resolved": "https://registry.npmjs.org/firebase/-/firebase-7.15.0.tgz", + "integrity": "sha512-0t8w/TT+230/n/XWBw9jGMApCkIEb5K1b+6t4R+SAMOZHMJZvoAwMcwgQXoYeUBFOJOQpgDhIZK8PzApq4iUXw==", + "requires": { + "@firebase/analytics": "0.3.6", + "@firebase/app": "0.6.5", + "@firebase/app-types": "0.6.1", + "@firebase/auth": "0.14.6", + "@firebase/database": "0.6.4", + "@firebase/firestore": "1.15.0", + "@firebase/functions": "0.4.45", + "@firebase/installations": "0.4.11", + "@firebase/messaging": "0.6.17", + "@firebase/performance": "0.3.6", + "@firebase/polyfill": "0.3.36", + "@firebase/remote-config": "0.1.22", + "@firebase/storage": "0.3.35", + "@firebase/util": "0.2.48" + }, + "dependencies": { + "@firebase/component": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.13.tgz", + "integrity": "sha512-DuSIM96NQkE3Yo77IOa5BWw8VBdvCR5cbMLNiFT4X3dTU15Dm0zHjncQHt/6rQpABGNYWAfOCJmSU1v6vc3DFA==", + "requires": { + "@firebase/util": "0.2.48", + "tslib": "1.11.1" + } + }, + "@firebase/database": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/@firebase/database/-/database-0.6.4.tgz", + "integrity": "sha512-m3jaElEEXhr3a9D+M/kbDuRCQG5EmrnSqyEq7iNk3s5ankIrALid0AYm2RZF764F/DIeMFtAzng4EyyEqsaQlQ==", + "requires": { + "@firebase/auth-interop-types": "0.1.5", + "@firebase/component": "0.1.13", + "@firebase/database-types": "0.5.1", + "@firebase/logger": "0.2.5", + "@firebase/util": "0.2.48", + "faye-websocket": "0.11.3", + "tslib": "1.11.1" + } + }, + "@firebase/logger": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.2.5.tgz", + "integrity": "sha512-qqw3m0tWs/qrg7axTZG/QZq24DIMdSY6dGoWuBn08ddq7+GLF5HiqkRj71XznYeUUbfRq5W9C/PSFnN4JxX+WA==" + }, + "@firebase/util": { + "version": "0.2.48", + "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.2.48.tgz", + "integrity": "sha512-6Wzq6IBF//3mrMTmTQ+JmceM0PMQpxV2GVfXhZn/4sMMkkhB0MA908nPDnatoHwUKyWE3BMw+uTLkyBnkuTu5A==", + "requires": { + "tslib": "1.11.1" + } + } + } + }, + "firebase-admin": { + "version": "8.12.1", + "resolved": "https://registry.npmjs.org/firebase-admin/-/firebase-admin-8.12.1.tgz", + "integrity": "sha512-DZ4Q7QQJYaO2BhnhZLrhL+mGRTCLS5WrxjbJtuKGmbKRBepwMhx++EQA5yhnGnIXgDHnp5SrZnVKygNdXtH8BQ==", + "requires": { + "@firebase/database": "^0.6.0", + "@google-cloud/firestore": "^3.0.0", + "@google-cloud/storage": "^4.1.2", + "@types/node": "^8.10.59", + "dicer": "^0.3.0", + "jsonwebtoken": "8.1.0", + "node-forge": "0.7.4" + } + }, + "firebase-functions": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/firebase-functions/-/firebase-functions-3.6.1.tgz", + "integrity": "sha512-CBvlDEoFgsdm10PTHs7gRd5xBmhp+eqCqgsyqKbzmdbU3J8RYqtBWoHm2O31gjtZv6MyOWvS3oFITShzBulylQ==", + "requires": { + "@types/express": "^4.17.3", + "cors": "^2.8.5", + "express": "^4.17.1", + "jsonwebtoken": "^8.5.1", + "lodash": "^4.17.14" + }, + "dependencies": { + "jsonwebtoken": { + "version": "8.5.1", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz", + "integrity": "sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==", + "requires": { + "jws": "^3.2.2", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^5.6.0" + } + }, + "jwa": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", + "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "requires": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "requires": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" + } + } + }, + "firebase-functions-helper": { + "version": "0.7.5", + "resolved": "https://registry.npmjs.org/firebase-functions-helper/-/firebase-functions-helper-0.7.5.tgz", + "integrity": "sha512-iZripWqrsbBeFq/UP8ycuYl7J7fuQVsjMAnvV3tahBTEPleSk44nUJDo6XRxzQos3gNvPupw2+slAXbrHVtBkg==", + "requires": { + "chai": "^4.2.0", + "firebase-admin": "^8.9.0" + } + }, + "firebase-functions-test": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/firebase-functions-test/-/firebase-functions-test-0.2.1.tgz", + "integrity": "sha512-+ZaNrDoRVy0ar4NGtrYbqVTsnitL3/Ud5yC7ElZUkX3956j+AzPCcrsCfa+5GJnpnVODXkMKpw9AySFJ/12nvA==", + "dev": true, + "requires": { + "@types/lodash": "^4.14.104", + "lodash": "^4.17.5" + } + }, + "foreach": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.5.tgz", + "integrity": "sha1-C+4AUBiusmDQo6865ljdATbsG5k=", + "optional": true + }, + "forwarded": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", + "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=" + }, + "fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + }, + "functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", + "optional": true + }, + "gaxios": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-3.0.3.tgz", + "integrity": "sha512-PkzQludeIFhd535/yucALT/Wxyj/y2zLyrMwPcJmnLHDugmV49NvAi/vb+VUq/eWztATZCNcb8ue+ywPG+oLuw==", + "optional": true, + "requires": { + "abort-controller": "^3.0.0", + "extend": "^3.0.2", + "https-proxy-agent": "^5.0.0", + "is-stream": "^2.0.0", + "node-fetch": "^2.3.0" + } + }, + "gcp-metadata": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-4.1.0.tgz", + "integrity": "sha512-r57SV28+olVsflPlKyVig3Muo/VDlcsObMtvDGOEtEJXj+DDE8bEl0coIkXh//hbkSDTvo+f5lbihZOndYXQQQ==", + "optional": true, + "requires": { + "gaxios": "^3.0.0", + "json-bigint": "^0.3.0" + } + }, + "gcs-resumable-upload": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/gcs-resumable-upload/-/gcs-resumable-upload-2.3.3.tgz", + "integrity": "sha512-sf896I5CC/1AxeaGfSFg3vKMjUq/r+A3bscmVzZm10CElyRanN0XwPu/MxeIO4LSP+9uF6yKzXvNsaTsMXUG6Q==", + "optional": true, + "requires": { + "abort-controller": "^3.0.0", + "configstore": "^5.0.0", + "gaxios": "^2.0.0", + "google-auth-library": "^5.0.0", + "pumpify": "^2.0.0", + "stream-events": "^1.0.4" + }, + "dependencies": { + "gaxios": { + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-2.3.4.tgz", + "integrity": "sha512-US8UMj8C5pRnao3Zykc4AAVr+cffoNKRTg9Rsf2GiuZCW69vgJj38VK2PzlPuQU73FZ/nTk9/Av6/JGcE1N9vA==", + "optional": true, + "requires": { + "abort-controller": "^3.0.0", + "extend": "^3.0.2", + "https-proxy-agent": "^5.0.0", + "is-stream": "^2.0.0", + "node-fetch": "^2.3.0" + } + } + } + }, + "get-func-name": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", + "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=" + }, + "glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "google-auth-library": { + "version": "5.10.1", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-5.10.1.tgz", + "integrity": "sha512-rOlaok5vlpV9rSiUu5EpR0vVpc+PhN62oF4RyX/6++DG1VsaulAFEMlDYBLjJDDPI6OcNOCGAKy9UVB/3NIDXg==", + "optional": true, + "requires": { + "arrify": "^2.0.0", + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "fast-text-encoding": "^1.0.0", + "gaxios": "^2.1.0", + "gcp-metadata": "^3.4.0", + "gtoken": "^4.1.0", + "jws": "^4.0.0", + "lru-cache": "^5.0.0" + }, + "dependencies": { + "gaxios": { + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-2.3.4.tgz", + "integrity": "sha512-US8UMj8C5pRnao3Zykc4AAVr+cffoNKRTg9Rsf2GiuZCW69vgJj38VK2PzlPuQU73FZ/nTk9/Av6/JGcE1N9vA==", + "optional": true, + "requires": { + "abort-controller": "^3.0.0", + "extend": "^3.0.2", + "https-proxy-agent": "^5.0.0", + "is-stream": "^2.0.0", + "node-fetch": "^2.3.0" + } + }, + "gcp-metadata": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-3.5.0.tgz", + "integrity": "sha512-ZQf+DLZ5aKcRpLzYUyBS3yo3N0JSa82lNDO8rj3nMSlovLcz2riKFBsYgDzeXcv75oo5eqB2lx+B14UvPoCRnA==", + "optional": true, + "requires": { + "gaxios": "^2.1.0", + "json-bigint": "^0.3.0" + } + }, + "google-p12-pem": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-2.0.4.tgz", + "integrity": "sha512-S4blHBQWZRnEW44OcR7TL9WR+QCqByRvhNDZ/uuQfpxywfupikf/miba8js1jZi6ZOGv5slgSuoshCWh6EMDzg==", + "optional": true, + "requires": { + "node-forge": "^0.9.0" + } + }, + "gtoken": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-4.1.4.tgz", + "integrity": "sha512-VxirzD0SWoFUo5p8RDP8Jt2AGyOmyYcT/pOUgDKJCK+iSw0TMqwrVfY37RXTNmoKwrzmDHSk0GMT9FsgVmnVSA==", + "optional": true, + "requires": { + "gaxios": "^2.1.0", + "google-p12-pem": "^2.0.0", + "jws": "^4.0.0", + "mime": "^2.2.0" + } + }, + "node-forge": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.9.1.tgz", + "integrity": "sha512-G6RlQt5Sb4GMBzXvhfkeFmbqR6MzhtnT7VTHuLadjkii3rdYHNdw0m8zA4BTxVIh68FicCQ2NSUANpsqkr9jvQ==", + "optional": true + } + } + }, + "google-gax": { + "version": "1.15.3", + "resolved": "https://registry.npmjs.org/google-gax/-/google-gax-1.15.3.tgz", + "integrity": "sha512-3JKJCRumNm3x2EksUTw4P1Rad43FTpqrtW9jzpf3xSMYXx+ogaqTM1vGo7VixHB4xkAyATXVIa3OcNSh8H9zsQ==", + "optional": true, + "requires": { + "@grpc/grpc-js": "~1.0.3", + "@grpc/proto-loader": "^0.5.1", + "@types/fs-extra": "^8.0.1", + "@types/long": "^4.0.0", + "abort-controller": "^3.0.0", + "duplexify": "^3.6.0", + "google-auth-library": "^5.0.0", + "is-stream-ended": "^0.1.4", + "lodash.at": "^4.6.0", + "lodash.has": "^4.5.2", + "node-fetch": "^2.6.0", + "protobufjs": "^6.8.9", + "retry-request": "^4.0.0", + "semver": "^6.0.0", + "walkdir": "^0.4.0" + } + }, + "google-p12-pem": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-3.0.1.tgz", + "integrity": "sha512-VlQgtozgNVVVcYTXS36eQz4PXPt9gIPqLOhHN0QiV6W6h4qSCNVKPtKC5INtJsaHHF2r7+nOIa26MJeJMTaZEQ==", + "optional": true, + "requires": { + "node-forge": "^0.9.0" + }, + "dependencies": { + "node-forge": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.9.1.tgz", + "integrity": "sha512-G6RlQt5Sb4GMBzXvhfkeFmbqR6MzhtnT7VTHuLadjkii3rdYHNdw0m8zA4BTxVIh68FicCQ2NSUANpsqkr9jvQ==", + "optional": true + } + } + }, + "graceful-fs": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", + "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", + "optional": true + }, + "gtoken": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-5.0.1.tgz", + "integrity": "sha512-33w4FNDkUcyIOq/TqyC+drnKdI4PdXmWp9lZzssyEQKuvu9ZFN3KttaSnDKo52U3E51oujVGop93mKxmqO8HHg==", + "optional": true, + "requires": { + "gaxios": "^3.0.0", + "google-p12-pem": "^3.0.0", + "jws": "^4.0.0", + "mime": "^2.2.0" + } + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "has-symbols": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", + "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==" + }, + "hash-stream-validation": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/hash-stream-validation/-/hash-stream-validation-0.2.3.tgz", + "integrity": "sha512-OEohGLoUOh+bwsIpHpdvhIXFyRGjeLqJbT8Yc5QTZPbRM7LKywagTQxnX/6mghLDOrD9YGz88hy5mLN2eKflYQ==", + "optional": true, + "requires": { + "through2": "^2.0.0" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "optional": true + }, + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "optional": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "optional": true + }, + "through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "optional": true, + "requires": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + } + } + }, + "http-errors": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.3.tgz", + "integrity": "sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw==", + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.4", + "setprototypeof": "1.1.1", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.0" + } + }, + "http-parser-js": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.2.tgz", + "integrity": "sha512-opCO9ASqg5Wy2FNo7A0sxy71yGbbkJJXLdgMK04Tcypw9jr2MgWbyubb0+WdmDmGnFflO7fRbqbaihh/ENDlRQ==" + }, + "http-proxy-agent": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", + "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==", + "optional": true, + "requires": { + "@tootallnate/once": "1", + "agent-base": "6", + "debug": "4" + } + }, + "https-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", + "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==", + "optional": true, + "requires": { + "agent-base": "6", + "debug": "4" + } + }, + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "idb": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/idb/-/idb-3.0.2.tgz", + "integrity": "sha512-+FLa/0sTXqyux0o6C+i2lOR0VoS60LU/jzUo5xjfY6+7sEEgy4Gz1O7yFBXvjd7N0NyIGWIRg8DcQSLEG+VSPw==" + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "optional": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" + }, + "is-arguments": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.0.4.tgz", + "integrity": "sha512-xPh0Rmt8NE65sNzvyUmWgI1tz3mKq74lGA0mL8LYZcoIzKOzDh6HmrYm3d18k60nHerC8A9Km8kYu87zfSFnLA==", + "optional": true + }, + "is-bigint": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.0.tgz", + "integrity": "sha512-t5mGUXC/xRheCK431ylNiSkGGpBp8bHENBcENTkDT6ppwPzEVxNGZRvgvmOEfbWkFhA7D2GEuE2mmQTr78sl2g==", + "optional": true + }, + "is-boolean-object": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.0.1.tgz", + "integrity": "sha512-TqZuVwa/sppcrhUCAYkGBk7w0yxfQQnxq28fjkO53tnK9FQXmdwz2JS5+GjsWQ6RByES1K40nI+yDic5c9/aAQ==", + "optional": true + }, + "is-callable": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.5.tgz", + "integrity": "sha512-ESKv5sMCJB2jnHTWZ3O5itG+O128Hsus4K4Qh1h2/cgn2vbgnLSVqfV46AeJA9D5EeeLa9w81KUXMtn34zhX+Q==" + }, + "is-date-object": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz", + "integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==" + }, + "is-expression": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-expression/-/is-expression-4.0.0.tgz", + "integrity": "sha512-zMIXX63sxzG3XrkHkrAPvm/OVZVSCPNkwMHU8oTX7/U3AL78I0QXCEICXUM13BIa8TYGZ68PiTKfQz3yaTNr4A==", + "requires": { + "acorn": "^7.1.1", + "object-assign": "^4.1.1" + } + }, + "is-map": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.1.tgz", + "integrity": "sha512-T/S49scO8plUiAOA2DBTBG3JHpn1yiw0kRp6dgiZ0v2/6twi5eiB0rHtHFH9ZIrvlWc6+4O+m4zg5+Z833aXgw==", + "optional": true + }, + "is-number-object": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.4.tgz", + "integrity": "sha512-zohwelOAur+5uXtk8O3GPQ1eAcu4ZX3UwxQhUlfFFMNpUd83gXgjbhJh6HmB6LUNV/ieOLQuDwJO3dWJosUeMw==", + "optional": true + }, + "is-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", + "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", + "optional": true + }, + "is-promise": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz", + "integrity": "sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==" + }, + "is-regex": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.5.tgz", + "integrity": "sha512-vlKW17SNq44owv5AQR3Cq0bQPEb8+kF3UKZ2fiZNOWtztYE5i0CzCZxFDwO58qAOWtxdBRVO/V5Qin1wjCqFYQ==", + "requires": { + "has": "^1.0.3" + } + }, + "is-set": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.1.tgz", + "integrity": "sha512-eJEzOtVyenDs1TMzSQ3kU3K+E0GUS9sno+F0OBT97xsgcJsF9nXMBtkT9/kut5JEpM7oL7X/0qxR17K3mcwIAA==", + "optional": true + }, + "is-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", + "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==", + "optional": true + }, + "is-stream-ended": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-stream-ended/-/is-stream-ended-0.1.4.tgz", + "integrity": "sha512-xj0XPvmr7bQFTvirqnFr50o0hQIh6ZItDqloxt5aJrR4NQsYeSsyFQERYGCAzfindAcnKjINnwEEgLx4IqVzQw==", + "optional": true + }, + "is-string": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.5.tgz", + "integrity": "sha512-buY6VNRjhQMiF1qWDouloZlQbRhDPCebwxSjxMjxgemYT46YMd2NR0/H+fBhEfWX4A/w9TBJ+ol+okqJKFE6vQ==", + "optional": true + }, + "is-symbol": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz", + "integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==", + "requires": { + "has-symbols": "^1.0.1" + } + }, + "is-typed-array": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.3.tgz", + "integrity": "sha512-BSYUBOK/HJibQ30wWkWold5txYwMUXQct9YHAQJr8fSwvZoiglcqB0pd7vEN23+Tsi9IUEjztdOSzl4qLVYGTQ==", + "optional": true, + "requires": { + "available-typed-arrays": "^1.0.0", + "es-abstract": "^1.17.4", + "foreach": "^2.0.5", + "has-symbols": "^1.0.1" + } + }, + "is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", + "optional": true + }, + "is-weakmap": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.1.tgz", + "integrity": "sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA==", + "optional": true + }, + "is-weakset": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.1.tgz", + "integrity": "sha512-pi4vhbhVHGLxohUw7PhGsueT4vRGFoXhP7+RGN0jKIv9+8PWYCQTqtADngrxOm2g46hoH0+g8uZZBzMrvVGDmw==", + "optional": true + }, + "isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "optional": true + }, + "isomorphic-fetch": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz", + "integrity": "sha1-YRrhrPFPXoH3KVB0coGf6XM1WKk=", + "requires": { + "node-fetch": "^1.0.1", + "whatwg-fetch": ">=0.10.0" + }, + "dependencies": { + "is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=" + }, + "node-fetch": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-1.7.3.tgz", + "integrity": "sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ==", + "requires": { + "encoding": "^0.1.11", + "is-stream": "^1.0.1" + } + } + } + }, + "js-stringify": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/js-stringify/-/js-stringify-1.0.2.tgz", + "integrity": "sha1-Fzb939lyTyijaCrcYjCufk6Weds=" + }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "js-yaml": { + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.0.tgz", + "integrity": "sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "json-bigint": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-0.3.0.tgz", + "integrity": "sha1-DM2RLEuCcNBfBW+9E4FLU9OCWx4=", + "optional": true, + "requires": { + "bignumber.js": "^7.0.0" + } + }, + "jsonwebtoken": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.1.0.tgz", + "integrity": "sha1-xjl80uX9WD1lwAeoPce7eOaYK4M=", + "requires": { + "jws": "^3.1.4", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.0.0", + "xtend": "^4.0.1" + }, + "dependencies": { + "jwa": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", + "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "requires": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "requires": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + } + } + }, + "jstransformer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/jstransformer/-/jstransformer-1.0.0.tgz", + "integrity": "sha1-7Yvwkh4vPx7U1cGkT2hwntJHIsM=", + "requires": { + "is-promise": "^2.0.0", + "promise": "^7.0.1" + } + }, + "jwa": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", + "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", + "optional": true, + "requires": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "jws": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz", + "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==", + "optional": true, + "requires": { + "jwa": "^2.0.0", + "safe-buffer": "^5.0.1" + } + }, + "lodash": { + "version": "4.17.15", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", + "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==" + }, + "lodash.at": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.at/-/lodash.at-4.6.0.tgz", + "integrity": "sha1-k83OZk8KGZTqM9181A4jr9EbD/g=", + "optional": true + }, + "lodash.camelcase": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha1-soqmKIorn8ZRA1x3EfZathkDMaY=" + }, + "lodash.has": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/lodash.has/-/lodash.has-4.5.2.tgz", + "integrity": "sha1-0Z9NwQlQWMzL4rDN9O4P5Ko3yGI=", + "optional": true + }, + "lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8=" + }, + "lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY=" + }, + "lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha1-YZwK89A/iwTDH1iChAt3sRzWg0M=" + }, + "lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w=" + }, + "lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=" + }, + "lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=" + }, + "lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=" + }, + "long": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", + "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" + }, + "lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "optional": true, + "requires": { + "yallist": "^3.0.2" + } + }, + "make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "optional": true, + "requires": { + "semver": "^6.0.0" + } + }, + "media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" + }, + "merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" + }, + "methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" + }, + "mime": { + "version": "2.4.5", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.5.tgz", + "integrity": "sha512-3hQhEUF027BuxZjQA3s7rIv/7VCQPa27hN9u9g87sEkWaKwQPuXOkVKtOeiyUrnWqTDiOs8Ed2rwg733mB0R5w==", + "optional": true + }, + "mime-db": { + "version": "1.44.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz", + "integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==" + }, + "mime-types": { + "version": "2.1.27", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz", + "integrity": "sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==", + "requires": { + "mime-db": "1.44.0" + } + }, + "mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "optional": true + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", + "dev": true + }, + "mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "dev": true, + "requires": { + "minimist": "^1.2.5" + } + }, + "morgan": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.0.tgz", + "integrity": "sha512-AbegBVI4sh6El+1gNwvD5YIck7nSA36weD7xvIxG4in80j/UoK8AEGaWnnz8v1GxonMCltmlNs5ZKbGvl9b1XQ==", + "requires": { + "basic-auth": "~2.0.1", + "debug": "2.6.9", + "depd": "~2.0.0", + "on-finished": "~2.3.0", + "on-headers": "~1.0.2" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + } + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "negotiator": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", + "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==" + }, + "node-fetch": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.0.tgz", + "integrity": "sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA==" + }, + "node-forge": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.7.4.tgz", + "integrity": "sha512-8Df0906+tq/omxuCZD6PqhPaQDYuyJ1d+VITgxoIA8zvQd1ru+nMJcDChHH324MWitIgbVkAkQoGEEVJNpn/PA==" + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" + }, + "object-inspect": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.7.0.tgz", + "integrity": "sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw==" + }, + "object-is": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.2.tgz", + "integrity": "sha512-5lHCz+0uufF6wZ7CRFWJN3hp8Jqblpgve06U5CMQ3f//6iDjPr2PEo9MWCjEssDsa+UZEL4PkFpr+BMop6aKzQ==", + "optional": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + } + }, + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==" + }, + "object.assign": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", + "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", + "requires": { + "define-properties": "^1.1.2", + "function-bind": "^1.1.1", + "has-symbols": "^1.0.0", + "object-keys": "^1.0.11" + } + }, + "on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "requires": { + "ee-first": "1.1.1" + } + }, + "on-headers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==" + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "requires": { + "wrappy": "1" + } + }, + "onetime": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.0.tgz", + "integrity": "sha512-5NcSkPHhwTVFIQN+TUqXoS5+dlElHXdpAWu9I0HP20YOtIi+aZ0Ct82jdlILDxjLEAWwvm+qj1m6aEtsDVmm6Q==", + "optional": true, + "requires": { + "mimic-fn": "^2.1.0" + } + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "optional": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "optional": true + }, + "parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "path-parse": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", + "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==" + }, + "path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" + }, + "pathval": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.0.tgz", + "integrity": "sha1-uULm1L3mUwBe9rcTYd74cn0GReA=" + }, + "process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "optional": true + }, + "promise": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", + "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==", + "requires": { + "asap": "~2.0.3" + } + }, + "promise-polyfill": { + "version": "8.1.3", + "resolved": "https://registry.npmjs.org/promise-polyfill/-/promise-polyfill-8.1.3.tgz", + "integrity": "sha512-MG5r82wBzh7pSKDRa9y+vllNHz3e3d4CNj1PQE4BQYxLme0gKYYBm9YENq+UkEikyZ0XbiGWxYlVw3Rl9O/U8g==" + }, + "protobufjs": { + "version": "6.9.0", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.9.0.tgz", + "integrity": "sha512-LlGVfEWDXoI/STstRDdZZKb/qusoAWUnmLg9R8OLSO473mBLWHowx8clbX5/+mKDEI+v7GzjoK9tRPZMMcoTrg==", + "requires": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/long": "^4.0.1", + "@types/node": "^13.7.0", + "long": "^4.0.0" + }, + "dependencies": { + "@types/node": { + "version": "13.13.9", + "resolved": "https://registry.npmjs.org/@types/node/-/node-13.13.9.tgz", + "integrity": "sha512-EPZBIGed5gNnfWCiwEIwTE2Jdg4813odnG8iNPMQGrqVxrI+wL68SPtPeCX+ZxGBaA6pKAVc6jaKgP/Q0QzfdQ==" + } + } + }, + "proxy-addr": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.6.tgz", + "integrity": "sha512-dh/frvCBVmSsDYzw6n926jv974gddhkFPfiN8hPOi30Wax25QZyZEGveluCgliBnqmuM+UJmBErbAUFIoDbjOw==", + "requires": { + "forwarded": "~0.1.2", + "ipaddr.js": "1.9.1" + } + }, + "pug": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pug/-/pug-3.0.0.tgz", + "integrity": "sha512-inmsJyFBSHZaiGLaguoFgJGViX0If6AcfcElimvwj9perqjDpUpw79UIEDZbWFmoGVidh08aoE+e8tVkjVJPCw==", + "requires": { + "pug-code-gen": "^3.0.0", + "pug-filters": "^4.0.0", + "pug-lexer": "^5.0.0", + "pug-linker": "^4.0.0", + "pug-load": "^3.0.0", + "pug-parser": "^6.0.0", + "pug-runtime": "^3.0.0", + "pug-strip-comments": "^2.0.0" + } + }, + "pug-attrs": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pug-attrs/-/pug-attrs-3.0.0.tgz", + "integrity": "sha512-azINV9dUtzPMFQktvTXciNAfAuVh/L/JCl0vtPCwvOA21uZrC08K/UnmrL+SXGEVc1FwzjW62+xw5S/uaLj6cA==", + "requires": { + "constantinople": "^4.0.1", + "js-stringify": "^1.0.2", + "pug-runtime": "^3.0.0" + } + }, + "pug-code-gen": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/pug-code-gen/-/pug-code-gen-3.0.1.tgz", + "integrity": "sha512-xJIGvmXTQlkJllq6hqxxjRWcay2F9CU69TuAuiVZgHK0afOhG5txrQOcZyaPHBvSWCU/QQOqEp5XCH94rRZpBQ==", + "requires": { + "constantinople": "^4.0.1", + "doctypes": "^1.1.0", + "js-stringify": "^1.0.2", + "pug-attrs": "^3.0.0", + "pug-error": "^2.0.0", + "pug-runtime": "^3.0.0", + "void-elements": "^3.1.0", + "with": "^7.0.0" + } + }, + "pug-error": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pug-error/-/pug-error-2.0.0.tgz", + "integrity": "sha512-sjiUsi9M4RAGHktC1drQfCr5C5eriu24Lfbt4s+7SykztEOwVZtbFk1RRq0tzLxcMxMYTBR+zMQaG07J/btayQ==" + }, + "pug-filters": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/pug-filters/-/pug-filters-4.0.0.tgz", + "integrity": "sha512-yeNFtq5Yxmfz0f9z2rMXGw/8/4i1cCFecw/Q7+D0V2DdtII5UvqE12VaZ2AY7ri6o5RNXiweGH79OCq+2RQU4A==", + "requires": { + "constantinople": "^4.0.1", + "jstransformer": "1.0.0", + "pug-error": "^2.0.0", + "pug-walk": "^2.0.0", + "resolve": "^1.15.1" + } + }, + "pug-lexer": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/pug-lexer/-/pug-lexer-5.0.0.tgz", + "integrity": "sha512-52xMk8nNpuyQ/M2wjZBN5gXQLIylaGkAoTk5Y1pBhVqaopaoj8Z0iVzpbFZAqitL4RHNVDZRnJDsqEYe99Ti0A==", + "requires": { + "character-parser": "^2.2.0", + "is-expression": "^4.0.0", + "pug-error": "^2.0.0" + } + }, + "pug-linker": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/pug-linker/-/pug-linker-4.0.0.tgz", + "integrity": "sha512-gjD1yzp0yxbQqnzBAdlhbgoJL5qIFJw78juN1NpTLt/mfPJ5VgC4BvkoD3G23qKzJtIIXBbcCt6FioLSFLOHdw==", + "requires": { + "pug-error": "^2.0.0", + "pug-walk": "^2.0.0" + } + }, + "pug-load": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pug-load/-/pug-load-3.0.0.tgz", + "integrity": "sha512-OCjTEnhLWZBvS4zni/WUMjH2YSUosnsmjGBB1An7CsKQarYSWQ0GCVyd4eQPMFJqZ8w9xgs01QdiZXKVjk92EQ==", + "requires": { + "object-assign": "^4.1.1", + "pug-walk": "^2.0.0" + } + }, + "pug-parser": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/pug-parser/-/pug-parser-6.0.0.tgz", + "integrity": "sha512-ukiYM/9cH6Cml+AOl5kETtM9NR3WulyVP2y4HOU45DyMim1IeP/OOiyEWRr6qk5I5klpsBnbuHpwKmTx6WURnw==", + "requires": { + "pug-error": "^2.0.0", + "token-stream": "1.0.0" + } + }, + "pug-runtime": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pug-runtime/-/pug-runtime-3.0.0.tgz", + "integrity": "sha512-GoEPcmQNnaTsePEdVA05bDpY+Op5VLHKayg08AQiqJBWU/yIaywEYv7TetC5dEQS3fzBBoyb2InDcZEg3mPTIA==" + }, + "pug-strip-comments": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pug-strip-comments/-/pug-strip-comments-2.0.0.tgz", + "integrity": "sha512-zo8DsDpH7eTkPHCXFeAk1xZXJbyoTfdPlNR0bK7rpOMuhBYb0f5qUVCO1xlsitYd3w5FQTK7zpNVKb3rZoUrrQ==", + "requires": { + "pug-error": "^2.0.0" + } + }, + "pug-walk": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pug-walk/-/pug-walk-2.0.0.tgz", + "integrity": "sha512-yYELe9Q5q9IQhuvqsZNwA5hfPkMJ8u92bQLIMcsMxf/VADjNtEYptU+inlufAFYcWdHlwNfZOEnOOQrZrcyJCQ==" + }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "optional": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "pumpify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-2.0.1.tgz", + "integrity": "sha512-m7KOje7jZxrmutanlkS1daj1dS6z6BgslzOXmcSEpIlCxM3VJH7lG5QLeck/6hgF6F4crFf01UtQmNsJfweTAw==", + "optional": true, + "requires": { + "duplexify": "^4.1.1", + "inherits": "^2.0.3", + "pump": "^3.0.0" + }, + "dependencies": { + "duplexify": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.1.tgz", + "integrity": "sha512-DY3xVEmVHTv1wSzKNbwoU6nVjzI369Y6sPoqfYr0/xlx3IdX2n94xIszTcjPO8W8ZIv0Wb0PXNcjuZyT4wiICA==", + "optional": true, + "requires": { + "end-of-stream": "^1.4.1", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1", + "stream-shift": "^1.0.0" + } + } + } + }, + "qs": { + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", + "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==" + }, + "range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" + }, + "raw-body": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", + "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", + "requires": { + "bytes": "3.1.0", + "http-errors": "1.7.2", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "dependencies": { + "http-errors": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", + "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.1", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.0" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + } + } + }, + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "optional": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "regexp.prototype.flags": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.3.0.tgz", + "integrity": "sha512-2+Q0C5g951OlYlJz6yu5/M33IcsESLlLfsyIaLJaG4FA2r4yP8MvVMJUUP/fVBkSpbbbZlS5gynbEWLipiiXiQ==", + "optional": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0-next.1" + } + }, + "resolve": { + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz", + "integrity": "sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==", + "requires": { + "path-parse": "^1.0.6" + } + }, + "retry-request": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/retry-request/-/retry-request-4.1.1.tgz", + "integrity": "sha512-BINDzVtLI2BDukjWmjAIRZ0oglnCAkpP2vQjM3jdLhmT62h0xnQgciPwBRDAvHqpkPT2Wo1XuUyLyn6nbGrZQQ==", + "optional": true, + "requires": { + "debug": "^4.1.1", + "through2": "^3.0.1" + } + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" + }, + "send": { + "version": "0.17.1", + "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", + "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==", + "requires": { + "debug": "2.6.9", + "depd": "~1.1.2", + "destroy": "~1.0.4", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "~1.7.2", + "mime": "1.6.0", + "ms": "2.1.1", + "on-finished": "~2.3.0", + "range-parser": "~1.2.1", + "statuses": "~1.5.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + }, + "dependencies": { + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + } + } + }, + "mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" + } + } + }, + "serve-static": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz", + "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==", + "requires": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.17.1" + } + }, + "setprototypeof": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", + "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" + }, + "side-channel": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.2.tgz", + "integrity": "sha512-7rL9YlPHg7Ancea1S96Pa8/QWb4BtXL/TZvS6B8XFetGBeuhAsfmUspK6DokBeZ64+Kj9TCNRD/30pVz1BvQNA==", + "optional": true, + "requires": { + "es-abstract": "^1.17.0-next.1", + "object-inspect": "^1.7.0" + } + }, + "signal-exit": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", + "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==", + "optional": true + }, + "snakeize": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/snakeize/-/snakeize-0.1.0.tgz", + "integrity": "sha1-EMCI2LWOsHazIpu1oE4jLOEmQi0=", + "optional": true + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, + "statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" + }, + "stream-events": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/stream-events/-/stream-events-1.0.5.tgz", + "integrity": "sha512-E1GUzBSgvct8Jsb3v2X15pjzN1tYebtbLaMg+eBOUOAxgbLoSbT2NS91ckc5lJD1KfLjId+jXJRgo0qnV5Nerg==", + "optional": true, + "requires": { + "stubs": "^3.0.0" + } + }, + "stream-shift": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz", + "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==", + "optional": true + }, + "streamsearch": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-0.1.2.tgz", + "integrity": "sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo=" + }, + "string.prototype.trimend": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.1.tgz", + "integrity": "sha512-LRPxFUaTtpqYsTeNKaFOw3R4bxIzWOnbQ837QfBylo8jIxtcbK/A/sMV7Q+OAV/vWo+7s25pOE10KYSjaSO06g==", + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + } + }, + "string.prototype.trimleft": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/string.prototype.trimleft/-/string.prototype.trimleft-2.1.2.tgz", + "integrity": "sha512-gCA0tza1JBvqr3bfAIFJGqfdRTyPae82+KTnm3coDXkZN9wnuW3HjGgN386D7hfv5CHQYCI022/rJPVlqXyHSw==", + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5", + "string.prototype.trimstart": "^1.0.0" + } + }, + "string.prototype.trimright": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/string.prototype.trimright/-/string.prototype.trimright-2.1.2.tgz", + "integrity": "sha512-ZNRQ7sY3KroTaYjRS6EbNiiHrOkjihL9aQE/8gfQ4DtAC/aEBRHFJa44OmoWxGGqXuJlfKkZW4WcXErGr+9ZFg==", + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5", + "string.prototype.trimend": "^1.0.0" + } + }, + "string.prototype.trimstart": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.1.tgz", + "integrity": "sha512-XxZn+QpvrBI1FOcg6dIpxUPgWCPuNXvMD72aaRaUQv1eD4e/Qy8i/hFTe0BUmD60p/QA6bh1avmuPTfNjqVWRw==", + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "optional": true, + "requires": { + "safe-buffer": "~5.1.0" + }, + "dependencies": { + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "optional": true + } + } + }, + "stubs": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/stubs/-/stubs-3.0.0.tgz", + "integrity": "sha1-6NK6H6nJBXAwPAMLaQD31fiavls=", + "optional": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + }, + "teeny-request": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-6.0.3.tgz", + "integrity": "sha512-TZG/dfd2r6yeji19es1cUIwAlVD8y+/svB1kAC2Y0bjEyysrfbO8EZvJBRwIE6WkwmUoB7uvWLwTIhJbMXZ1Dw==", + "optional": true, + "requires": { + "http-proxy-agent": "^4.0.0", + "https-proxy-agent": "^5.0.0", + "node-fetch": "^2.2.0", + "stream-events": "^1.0.5", + "uuid": "^7.0.0" + } + }, + "through2": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/through2/-/through2-3.0.1.tgz", + "integrity": "sha512-M96dvTalPT3YbYLaKaCuwu+j06D/8Jfib0o/PxbVt6Amhv3dUAtW6rTV1jPgJSBG83I/e04Y6xkVdVhSRhi0ww==", + "optional": true, + "requires": { + "readable-stream": "2 || 3" + } + }, + "to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=" + }, + "toidentifier": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", + "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==" + }, + "token-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/token-stream/-/token-stream-1.0.0.tgz", + "integrity": "sha1-zCAOqyYT9BZtJ/+a/HylbUnfbrQ=" + }, + "tslib": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.11.1.tgz", + "integrity": "sha512-aZW88SY8kQbU7gpV19lN24LtXh/yD4ZZg6qieAJDDg+YBsJcSmLGK9QpnUjAKVG/xefmvJGd1WUmfpT/g6AJGA==" + }, + "tslint": { + "version": "5.20.1", + "resolved": "https://registry.npmjs.org/tslint/-/tslint-5.20.1.tgz", + "integrity": "sha512-EcMxhzCFt8k+/UP5r8waCf/lzmeSyVlqxqMEDQE7rWYiQky8KpIBz1JAoYXfROHrPZ1XXd43q8yQnULOLiBRQg==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "builtin-modules": "^1.1.1", + "chalk": "^2.3.0", + "commander": "^2.12.1", + "diff": "^4.0.1", + "glob": "^7.1.1", + "js-yaml": "^3.13.1", + "minimatch": "^3.0.4", + "mkdirp": "^0.5.1", + "resolve": "^1.3.2", + "semver": "^5.3.0", + "tslib": "^1.8.0", + "tsutils": "^2.29.0" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + } + } + }, + "tsutils": { + "version": "2.29.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.29.0.tgz", + "integrity": "sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA==", + "dev": true, + "requires": { + "tslib": "^1.8.1" + } + }, + "type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==" + }, + "type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "requires": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + } + }, + "typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", + "optional": true + }, + "typedarray-to-buffer": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", + "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", + "optional": true, + "requires": { + "is-typedarray": "^1.0.0" + } + }, + "typescript": { + "version": "3.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.3.tgz", + "integrity": "sha512-D/wqnB2xzNFIcoBG9FG8cXRDjiqSTbG2wd8DMZeQyJlP1vfTkIxH4GKveWaEBYySKIg+USu+E+EDIR47SqnaMQ==", + "dev": true + }, + "unique-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz", + "integrity": "sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==", + "optional": true, + "requires": { + "crypto-random-string": "^2.0.0" + } + }, + "unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "optional": true + }, + "utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" + }, + "uuid": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-7.0.3.tgz", + "integrity": "sha512-DPSke0pXhTZgoF/d+WSt2QaKMCFSfx7QegxEWT+JOuHF5aWrKEn0G+ztjuJg/gG8/ItK+rbPCD/yNv8yyih6Cg==", + "optional": true + }, + "vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" + }, + "void-elements": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz", + "integrity": "sha1-YU9/v42AHwu18GYfWy9XhXUOTwk=" + }, + "walkdir": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/walkdir/-/walkdir-0.4.1.tgz", + "integrity": "sha512-3eBwRyEln6E1MSzcxcVpQIhRG8Q1jLvEqRmCZqS3dsfXEDR/AhOF4d+jHg1qvDCpYaVRZjENPQyrVxAkQqxPgQ==", + "optional": true + }, + "websocket-driver": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", + "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", + "requires": { + "http-parser-js": ">=0.5.1", + "safe-buffer": ">=5.1.0", + "websocket-extensions": ">=0.1.1" + } + }, + "websocket-extensions": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.3.tgz", + "integrity": "sha512-nqHUnMXmBzT0w570r2JpJxfiSD1IzoI+HGVdd3aZ0yNi3ngvQ4jv1dtHt5VGxfI2yj5yqImPhOK4vmIh2xMbGg==" + }, + "whatwg-fetch": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.0.0.tgz", + "integrity": "sha512-9GSJUgz1D4MfyKU7KRqwOjXCXTqWdFNvEr7eUBYchQiVc744mqK/MzXPNR2WsPkmkOa4ywfg8C2n8h+13Bey1Q==" + }, + "which-boxed-primitive": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.1.tgz", + "integrity": "sha512-7BT4TwISdDGBgaemWU0N0OU7FeAEJ9Oo2P1PHRm/FCWoEi2VLWC9b6xvxAA3C/NMpxg3HXVgi0sMmGbNUbNepQ==", + "optional": true, + "requires": { + "is-bigint": "^1.0.0", + "is-boolean-object": "^1.0.0", + "is-number-object": "^1.0.3", + "is-string": "^1.0.4", + "is-symbol": "^1.0.2" + } + }, + "which-collection": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.1.tgz", + "integrity": "sha512-W8xeTUwaln8i3K/cY1nGXzdnVZlidBcagyNFtBdD5kxnb4TvGKR7FfSIS3mYpwWS1QUCutfKz8IY8RjftB0+1A==", + "optional": true, + "requires": { + "is-map": "^2.0.1", + "is-set": "^2.0.1", + "is-weakmap": "^2.0.1", + "is-weakset": "^2.0.1" + } + }, + "which-typed-array": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.2.tgz", + "integrity": "sha512-KT6okrd1tE6JdZAy3o2VhMoYPh3+J6EMZLyrxBQsZflI1QCZIxMrIYLkosd8Twf+YfknVIHmYQPgJt238p8dnQ==", + "optional": true, + "requires": { + "available-typed-arrays": "^1.0.2", + "es-abstract": "^1.17.5", + "foreach": "^2.0.5", + "function-bind": "^1.1.1", + "has-symbols": "^1.0.1", + "is-typed-array": "^1.1.3" + } + }, + "with": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/with/-/with-7.0.1.tgz", + "integrity": "sha512-TpHxhlaRS5mNJbCDXqbDJB4qhyV8zQUPytY3o3cCb6t2m13Qw+vsWFvJCBBIkWILRjNlmlnvd/0AW0dPaO7n/w==", + "requires": { + "@babel/parser": "^7.9.6", + "@babel/types": "^7.9.6", + "assert-never": "^1.2.1", + "babel-walk": "3.0.0-canary-5" + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + }, + "write-file-atomic": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", + "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", + "optional": true, + "requires": { + "imurmurhash": "^0.1.4", + "is-typedarray": "^1.0.0", + "signal-exit": "^3.0.2", + "typedarray-to-buffer": "^3.1.5" + } + }, + "xdg-basedir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-4.0.0.tgz", + "integrity": "sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==", + "optional": true + }, + "xmlhttprequest": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/xmlhttprequest/-/xmlhttprequest-1.8.0.tgz", + "integrity": "sha1-Z/4HXFwk/vOfnWX197f+dRcZaPw=" + }, + "xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==" + }, + "yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "optional": true + } + } +} diff --git a/functions/RApi/package.json b/functions/RApi/package.json new file mode 100644 index 0000000..20489d1 --- /dev/null +++ b/functions/RApi/package.json @@ -0,0 +1,35 @@ +{ + "name": "functions", + "scripts": { + "lint": "tslint --project tsconfig.json", + "build": "tsc", + "serve": "npm run build && firebase emulators:start --only functions", + "shell": "npm run build && firebase functions:shell", + "start": "npm run shell", + "deploy": "firebase deploy --only functions", + "logs": "firebase functions:log", + "server": "npm run build && node lib/bin/www.js", + "updater": "npm run build && firebase emulators:start --only functions" + }, + "engines": { + "node": "8" + }, + "main": "lib/bin/www.js", + "dependencies": { + "body-parser": "^1.19.0", + "express": "^4.17.1", + "firebase-admin": "^8.10.0", + "firebase-functions": "^3.6.1", + "firebase-functions-helper": "^0.7.5", + "http-errors": "^1.7.3", + "morgan": "^1.10.0", + "node-fetch": "^2.6.0", + "pug": "^3.0.0" + }, + "devDependencies": { + "tslint": "^5.12.0", + "typescript": "^3.8.0", + "firebase-functions-test": "^0.2.0" + }, + "private": true +} diff --git a/functions/RApi/src/app.ts b/functions/RApi/src/app.ts new file mode 100644 index 0000000..748ecb9 --- /dev/null +++ b/functions/RApi/src/app.ts @@ -0,0 +1,43 @@ +import * as path from 'path'; +import * as logger from 'morgan'; +import * as express from 'express'; +import * as createError from 'http-errors'; +import router = require("./routes/newsriver"); + + +const app = express(); + +// view engine setup +app.set('views', path.join(__dirname, 'views')); +app.set('view engine', 'pug'); + +app.use(logger('dev')); +app.use(express.json()); +app.use(express.urlencoded({extended: false})); +app.use(express.static(path.join(__dirname, 'public'))); + +/** + * ------------- + * Test purposes + * ------------- + const testUpdater = new updater.Updater(null, null, ['covid-19', 'enfermedad'], functions.config().newsriver.key, 'es'); + app.get('/api', (req, res) => + testUpdater.request() + .then(it => res.json(it)) + ); + * --------------- + */ +app.use(router); +// catch 404 and forward to error handler +app.use((req, res, next) => next(createError(404))); + +// error handler +app.use((err, req, res, next) => { + res.locals.message = err.message; + res.locals.error = res.app.get('env') === 'development' ? err : {}; + + res.status(err.status || 500); + res.render('error'); +}); + +export = app; diff --git a/functions/RApi/src/bin/www.ts b/functions/RApi/src/bin/www.ts new file mode 100644 index 0000000..4055b63 --- /dev/null +++ b/functions/RApi/src/bin/www.ts @@ -0,0 +1,81 @@ +const app = require('../app'); +import * as http from 'http'; +import functions = require("firebase-functions"); + + +/** + * Get port from environment and store in Express + */ +const port = normalizePort(process.env.PORT || '3000'); + +/** + * Create the http server + */ +const server = http.createServer(app); + +/** + * Listen on a provided port, on all network interfaces + */ +server.on('error', onError); +server.on('listening', onListening); +export const webApi = functions.https.onRequest(app); + +/** + * Normalize a port into a number, string or false + * @param val the port + */ +function normalizePort(val) { + const parsedPort = parseInt(val, 10); + + if (isNaN(parsedPort)) { + // named pipe + return val; + } + + if (parsedPort >= 0) { + // port number + return parsedPort; + } + + return false; +} + +/** + * Event listener for HTTP server "error" event. + */ + +function onError(error) { + if (error.syscall !== 'listen') { + throw error; + } + + const bind = typeof port === 'string' + ? 'Pipe ' + port + : 'Port ' + port; + + // handle specific listen errors with friendly messages + switch (error.code) { + case 'EACCES': + console.error(bind + ' requires elevated privileges'); + process.exit(1); + break; + case 'EADDRINUSE': + console.error(bind + ' is already in use'); + process.exit(1); + break; + default: + throw error; + } +} + +/** + * Event listener for HTTP server "listening" event. + */ + +function onListening() { + const addr = server.address(); + const bind = typeof addr === 'string' + ? 'pipe ' + addr + : 'port ' + addr.port; + console.log('Listening on ' + bind); +} diff --git a/functions/RApi/src/controllers/api.ts b/functions/RApi/src/controllers/api.ts new file mode 100644 index 0000000..fc2d383 --- /dev/null +++ b/functions/RApi/src/controllers/api.ts @@ -0,0 +1,50 @@ +// import {Api} from '../models/api'; +// import api = require('../models/api'); +// import {api} from '../models/api'; +// import apiModels = require("../models/api"); + +import {Request, Response} from 'express'; +import {Api} from "../models/api"; + +/*export function getNewsByLanguage(req: Request, res: Response) { + console.log('Getting news by language'); + const language = req.query.lang as string; + apiModels.api.newsForLanguage(language) + .then(data => res.json(data)) + .catch(err => { + console.error(err); + res.sendStatus(304); + }); +}*/ + +export function ApiController(api: Api) { + return { + getNewsByLanguage(req: Request, res: Response) { + const language = req.query.lang as string; + api.newsForLanguage(language) + .then(data => res.json(data)) + .catch(err => { + console.error(err); + res.sendStatus(500); + }); + } + } +} + +/*export class ApiController { + api: Api + + constructor(api: Api) { + this.api = api; + } + + getNewsByLanguage(req: Request, res: Response) { + const language = req.query.lang as string; + this.api.newsForLanguage(language) + .then(data => res.json(data)) + .catch(err => { + console.error(err); + res.sendStatus(500); + }); + } +}*/ diff --git a/functions/RApi/src/controllers/newsriver.ts b/functions/RApi/src/controllers/newsriver.ts new file mode 100644 index 0000000..538845b --- /dev/null +++ b/functions/RApi/src/controllers/newsriver.ts @@ -0,0 +1,17 @@ +export import apiModel = require("../models/newsriver"); +import {Request, Response} from "express"; + + +export async function queryNewsForLanguage(req: Request, res: Response) { + const language = req.query.lang as string; + apiModel.newsForLanguage(language) + .then(newsData => res.json(newsData)) + .catch(err => { + if (err !instanceof RangeError) { + console.error(`Error while getting news data: ${err}`); + res.sendStatus(500); + } + else + res.status(403).send(err); + }) +} \ No newline at end of file diff --git a/functions/RApi/src/interfaces/projectProperties.ts b/functions/RApi/src/interfaces/projectProperties.ts new file mode 100644 index 0000000..2bdee61 --- /dev/null +++ b/functions/RApi/src/interfaces/projectProperties.ts @@ -0,0 +1,5 @@ +export interface ProjectProperties { + collection: string, + database: FirebaseFirestore.Firestore, + authToken: string +} \ No newline at end of file diff --git a/functions/RApi/src/models/api.ts b/functions/RApi/src/models/api.ts new file mode 100644 index 0000000..44ddc4e --- /dev/null +++ b/functions/RApi/src/models/api.ts @@ -0,0 +1,75 @@ +// import * as functions from 'firebase-functions'; +// import * as admin from 'firebase-admin'; + +import {ProjectProperties} from '../interfaces/projectProperties'; +import {RemoteConfigData} from '../rcdata'; +import {Updater} from '../updater'; +import {NewsriverData} from '../newsriver'; + +export class Api { + properties: ProjectProperties; + remoteConfig: RemoteConfigData; + languages: Array; + updaters: Record; + timers: Set; + + constructor(properties: ProjectProperties, + remoteConfig: RemoteConfigData, + languages: Array) { + this.properties = properties; + this.remoteConfig = remoteConfig; + this.languages = languages; + this.updaters = {}; + for (const language in languages) { + this.updaters[language] = undefined; + } + this.timers = new Set(); + } + + async init() { + this.languages.forEach(language => { + this.remoteConfig.getSearchTermsForLanguage(language) + .then(terms => { + const updater = new Updater( + this.properties.database, + `${this.properties.collection}_${language}`, + terms, + this.properties.authToken, + language + ); + this.updaters[language] = updater; + this.timers.add(updater.schedule()); + }); + }); + this.remoteConfig.subscribeUpdaters(this.updaters); + } + + async newsForLanguage(language: string): Promise> { + if (language ! in this.languages) + language = 'en'; + const collection = this.updaters[language].collection; + const snapshot = await collection.get(); + const data = Array(snapshot.size); + snapshot.forEach(item => { + data.push(item.data() as NewsriverData); + }); + return data; + } + + finish() { + for (const timer of this.timers) { + clearInterval(timer); + } + } +} +/*const serviceAccount = require('../../handwashing-firebase-adminsdk.json'); +const firebaseApp = admin.initializeApp({ + credential: admin.credential.cert(serviceAccount), + databaseURL: 'https://handwashing.firebaseio.com' +});*/ +/*const firebaseApp = admin.initializeApp(); +const projectProperties: ProjectProperties = { + collection: 'news', + database: admin.firestore(), + authToken: functions.config().newsriver.key +};*/ \ No newline at end of file diff --git a/functions/RApi/src/models/newsriver.ts b/functions/RApi/src/models/newsriver.ts new file mode 100644 index 0000000..a6c42b8 --- /dev/null +++ b/functions/RApi/src/models/newsriver.ts @@ -0,0 +1,70 @@ +import admin = require('firebase-admin'); +import functions = require("firebase-functions"); +import {ProjectProperties} from "../interfaces/projectProperties"; +import {Updater} from "../updater"; +import {RemoteConfigData} from "../rcdata"; +import {NewsriverData} from "../newsriver"; + + +const serviceAccount = require('../../handwashing-firebase-adminsdk.json'); +export const firebaseApp = admin.initializeApp({ + credential: admin.credential.cert(serviceAccount), + databaseURL: 'https://handwashing.firebaseio.com' +}); +const languages = new Set(['es', 'en']); +const projectProperties: ProjectProperties = { + collection: 'news', + database: admin.firestore(), + authToken: functions.config().newsriver.key +} +const updaters: Record = {}; +const remoteConfig = new RemoteConfigData(firebaseApp); +const timers = new Set(); +let initCalled = false; + +export async function initialize() { + initCalled = true; + for (const language of languages) { + const terms = await remoteConfig.getSearchTermsForLanguage(language); + const updater = new Updater( + projectProperties.database, + `${projectProperties.collection}_${language}`, + terms, + projectProperties.authToken, + language + ); + console.log('Created updater object'); + updaters[language] = updater; + } + remoteConfig.subscribeUpdaters(updaters); +} + +export async function scheduleUpdates() { + if (!initCalled) + throw new Error('`initialize` not called'); + for (const language of languages) { + timers.add(updaters[language].schedule()); + } +} + +export async function newsForLanguage(language: string) { + if (!initCalled) + throw new Error('`initialize` not called'); + if (language !in languages) + throw new RangeError(`invalid language "${language}"`); + const collection = updaters[language].collection; + const snapshot = await collection.get(); + const data = new Array(); + snapshot.forEach(item => { + if (item.data() !== null) + data.push(item.data() as NewsriverData) + }); + return data; +} + +export async function stopScheduling() { + if (!initCalled) + throw new Error('`initialize` not called'); + for (const timer of timers) + clearInterval(timer); +} diff --git a/functions/RApi/src/newsriver.ts b/functions/RApi/src/newsriver.ts new file mode 100644 index 0000000..1fd8258 --- /dev/null +++ b/functions/RApi/src/newsriver.ts @@ -0,0 +1,48 @@ +interface NewsriverElements { + type: string, + primary: boolean, + url: string, + width: number | null, + height: number | null, + title: string | null, + alternative: string | null + } + + interface NewsriverWebsite { + name: string, + hostName: string, + domainName: string, + iconURL: string, + countryName: string | null, + countryCode: string | null, + region: null + } + + interface Sentiment { + type: string, + sentiment: number + } + + interface ReadTime { + type: string, + seconds: number + } + + interface NewsriverMetadata { + finSentiment: Sentiment, + readTime: ReadTime + } + + export interface NewsriverData { + id: string, + discoverDate: Date, + title: string, + language: string, + text: string, + structuredText: string, + elements: Array, + website: NewsriverWebsite, + metadata: NewsriverMetadata, + highlight: string, + score: number + } \ No newline at end of file diff --git a/functions/RApi/src/public/stylesheets/style.css b/functions/RApi/src/public/stylesheets/style.css new file mode 100644 index 0000000..b993201 --- /dev/null +++ b/functions/RApi/src/public/stylesheets/style.css @@ -0,0 +1,8 @@ +body { + padding: 50px; + font: 14px "Lucida Grande", Helvetica, Arial, sans-serif; +} + +a { + color: #00B7FF; +} diff --git a/functions/RApi/src/rcdata.ts b/functions/RApi/src/rcdata.ts new file mode 100644 index 0000000..ccbe159 --- /dev/null +++ b/functions/RApi/src/rcdata.ts @@ -0,0 +1,61 @@ +import {Updater} from './updater'; +import * as admin from 'firebase-admin'; +import * as functions from 'firebase-functions' + +export class RemoteConfigData { + remoteConfig: admin.remoteConfig.RemoteConfig; + updaters: Record; + + constructor(app: admin.app.App) { + this.remoteConfig = admin.remoteConfig(app); + this.updaters = {}; + this.listenToRCChanges(); + } + + getSearchTermsForLanguage(language: string): Promise> { + return new Promise>(resolve => { + this.remoteConfig.getTemplate() + .then(template => { + let condition: string; + switch (language) { + case 'es': + condition = 'Spanish users'; + break; + default: + condition = 'Default language users'; + break; + } + const values = JSON.parse(template.parameters['search_terms'].conditionalValues[condition]['value']); + resolve(values); + }); + }); + } + + subscribeUpdaters(updaters: Record) { + this.updaters = updaters; + } + + listenToRCChanges() { + functions.remoteConfig.onUpdate(_ => { + return admin.credential.applicationDefault().getAccessToken() + .then(_ => { + this.remoteConfig.getTemplate() + .then(template => { + const languages = Object.keys(template.parameters['search_terms'].conditionalValues); + for (const language of languages) { + const terms = JSON.parse( + template.parameters['search_terms'].conditionalValues[language]['value'] + ); + try { + if (this.updaters[language].searchTerms.length !== terms.lenght) + this.updaters[language].searchTerms = terms; + } catch (e) { + console.warn(`Updaters are not set yet - ${e}`); + } + } + }); + }) + .catch(err => console.error(`Error while obtaining data from RC: ${err}`)); + }); + } +} diff --git a/functions/RApi/src/routes/api.ts b/functions/RApi/src/routes/api.ts new file mode 100644 index 0000000..362378d --- /dev/null +++ b/functions/RApi/src/routes/api.ts @@ -0,0 +1,178 @@ +import * as admin from 'firebase-admin'; +// import controller = require("../controllers/api"); +import {Router} from "express"; +// import get = Reflect.get; + +/*const router = Router(); + +router.get('/api/v1', (req, res, next) => { + try { + const language = req.query.lang; + const tokenId = req.get('Authorization').split('Bearer')[0]; + admin.auth().verifyIdToken(tokenId) + .then(_ => { + if (language === undefined) + res.status(403).send('lang must be given [?lang=...]'); + else next(); + }) + .catch(e => { + console.log('User unauthorized'); + next(); + // res.status(401).send(e) + }) + } catch (e) { + console.error(e); + res.status(401).send(e); + } +}); +router.get('/api/v1', controller.getNewsByLanguage);*/ +// app: admin.app.App, apiController: { getNewsByLanguage: (req, res) => any } +export function ApiRoutes(apiController: { getNewsByLanguage: (req, res) => any }) { + const router = Router(); + router.get('/api/v1', (req, res, next) => { + try { + const language = req.query.lang; + const tokenId = req.get('Authorization').split('Bearer')[0]; + admin.auth().verifyIdToken(tokenId) + .then(_ => { + if (language === undefined) + res.status(403).send('lang must be given [?lang=...]'); + else next(); + }) + .catch(e => { + console.log('User unauthorized'); + next(); + // res.status(401).send(e) + }) + } catch (e) { + console.error(e); + res.status(401).send(e); + } + }); + router.get('/api/v1', apiController.getNewsByLanguage); + + return {router}; +} + +/*export class ApiRouter { + router: Router + // firebaseApp: admin.app.App + + constructor(app: admin.app.App, apiController: ApiController) { + // this.firebaseApp = app; + this.router = Router(); + + this.router.get('/api/v1', (req, res, next) => { + try { + const language = req.query.lang; + const tokenId = req.get('Authorization').split('Bearer')[0]; + admin.auth(app).verifyIdToken(tokenId) + .then(_ => { + if (language === undefined) + res.status(403).send('lang must be given [?lang=...]'); + else next(); + }) + .catch(e => { + console.log('User unauthorized'); + next(); + // res.status(401).send(e) + }) + } catch (e) { + console.error(e); + res.status(401).send(e); + } + }); + // this.router.get('/api/v1', apiController.getNewsByLanguage); + } +}*/ + +// export = router; + +/*export class ApiRouter { + router: Router; + firebaseApp: admin.app.App; + + constructor(firebaseApp: admin.app.App, apiController: ApiController) { + this.router = Router(); + this.firebaseApp = firebaseApp; + // this.router.use('/api/v1', apiController.getNewsByLanguage); + this.router.use('/update', (req: Request, res: Response) => { + try { + apiController.api.updaters['en'].request() + .then(apiData => { + apiController.api.updaters['en'].updateData(apiData) + .then(_ => { + console.log("Data updated"); + res.json(apiData); + }) + .catch(err => { + console.error(err); + res.sendStatus(500); + }) + }) + .catch(e => { + console.error(e); + res.sendStatus(500); + }); + } catch (e) { + console.error(e); + res.sendStatus(500); + } + }); + } + + async init(): Promise { + this.router.use('/api/v1', (req: Request, res: Response, next: NextFunction) => { + try { + const language = req.query.lang; + const tokenId = req.get('Authorization').split('Bearer')[1]; + admin.auth(this.firebaseApp).verifyIdToken(tokenId) + .then(_ => { + if (language === undefined) + res.status(403).send('lang must be given [?lang=...]'); + else next(); + }) + .catch(err => res.status(401).send(err)); + } catch (e) { + res.status(401).send(e); + } + }); + return this.router; + } +}*/ + +// export const router = express.Router(); + +/*router.use('/api/v1', (req, res, next) => { + try { + const language = req.query.lang; + const tokenId = req.get('Authorization').split('Bearer ')[1]; + admin.auth().verifyIdToken(tokenId) + .then(_ => { + if (language === undefined) { + res.status(403).send('lang must be given [?lang=...]'); + } else next(); + }) + .catch(err => res.status(401).send(err)); + } catch (e) { + res.status(401).send(e); + } +});*/ +// router.use('/api/v1', getNewsByLanguage); +// router.use('/update', (req, res) => { +// api.updaters['es'].request() +// .then(apiData => { +// api.updaters['es'].updateData(apiData) +// .then(() => { +// console.log("Data was updated"); +// res.json(apiData); +// }) +// .catch((err) => { +// console.error(err); +// res.status(304).send(err); +// }); +// }) +// .catch((err) => console.error(err)); +// }); + +// module.exports = router; diff --git a/functions/RApi/src/routes/newsriver.ts b/functions/RApi/src/routes/newsriver.ts new file mode 100644 index 0000000..2ab181b --- /dev/null +++ b/functions/RApi/src/routes/newsriver.ts @@ -0,0 +1,51 @@ +import apiController = require("../controllers/newsriver"); +import express = require("express"); +// import admin = require("firebase-admin"); + + +const router = express.Router(); + +apiController.apiModel.initialize() + .then(_ => apiController.apiModel.scheduleUpdates()) + .catch(e => { + console.error(e); + throw e; + }); + +router.get('/api/v1', (req, res, next) => { + try { + const language = req.query.lang; + // const tokenId = req.get('Authorization').split('Bearer')[0]; + if (language === undefined) + res.status(403).send('lang must be given [?lang=...]'); + else next(); + /*admin.auth(apiController.apiModel.firebaseApp).verifyIdToken(tokenId) + .then(_ => { + if (language === undefined) + res.status(403).send('lang must be given [?lang=...]'); + else next(); + }) + .catch(e => { + console.error('Unauthorized'); + next(); + // res.status(401).send(e); + });*/ + } catch (e) { + console.error(`Possible missing authorization header: ${e}`); + res.status(401).send(e); + } +}); +router.get('/api/v1', apiController.queryNewsForLanguage); +router.get('/close', (req, res) => { + const authToken = req.get('Authorization'); + if (authToken === undefined) + return res.sendStatus(403); + if (authToken === process.env.ADMIN_TOKEN) { + return apiController.apiModel.stopScheduling() + .then(_ => res.sendStatus(200)) + .catch(_ => res.sendStatus(200)); + } else + return res.sendStatus(403); +}); + +export = router; diff --git a/functions/RApi/src/updater.ts b/functions/RApi/src/updater.ts new file mode 100644 index 0000000..d570703 --- /dev/null +++ b/functions/RApi/src/updater.ts @@ -0,0 +1,121 @@ +import {NewsriverData} from "./newsriver"; +import * as firebaseHelper from 'firebase-functions-helper'; +import * as fetch from 'node-fetch'; +import {Headers} from 'node-fetch'; + +export class Updater { + private readonly db: FirebaseFirestore.Firestore; + private readonly collectionName: string; + private readonly interval: number; + private _searchTerms: Array; + private readonly language: string; + private readonly auth: string; + private _url: string | undefined; + + get url(): Promise { + if (this._url === undefined) + return this.buildURL() + .then(url => this._url = url); + return Promise.resolve(this._url); + } + + get searchTerms(): Array { + return this._searchTerms; + } + + set searchTerms(value) { + this._searchTerms = value; + this._url = undefined; + } + + get collection(): FirebaseFirestore.CollectionReference { + return this.db.collection(this.collectionName); + } + + constructor(db: FirebaseFirestore.Firestore | null, + collectionName: string | null, + searchTerms: Array, + auth: string, + language: string = 'en', + intervalMins: number = 60) { + this.db = db; + this.collectionName = collectionName; + this.searchTerms = searchTerms; + this.language = language; + this.auth = auth; + this.interval = intervalMins * 60 * 1000; + this.request() + .then(response => { + this.updateData(response) + // tslint:disable-next-line:no-empty + .catch(ignored => {}); + }) + } + + schedule(): NodeJS.Timer { + return setInterval(() => { + const that = this; + this.request() + .then(response => { + that.updateData(response) + .catch(error => console.error(`error occurred while updating firebase data ${error}`)); + }) + .catch(error => console.error(`error occurred during process ${error}`)); + }, this.interval); + } + + async updateData(content: Array) { + console.log('Updating database content data'); + try { + content.forEach(element => { + firebaseHelper.firestore.checkDocumentExists(this.db, this.collectionName, element.id) + .then(_ => firebaseHelper.firestore.createDocumentWithID(this.db, this.collectionName, element.id, element)) + console.log(`Created element with ID: ${element.id}`); + }); + } catch (error) { + console.error(`Unhandled error ${error}`); + } + } + + async request(): Promise> { + try { + const requestUrl = await this.url; + const response = await fetch(requestUrl, { + method: 'GET', headers: new Headers({ + 'Authorization': this.auth, + 'Content-Type': 'application/json' + }) + }); + const body = await response.json(); + return body as Array; + } catch (e) { + console.error(`Captured error ${e}`); + throw e; + } + } + + buildURL(): Promise { + return new Promise(resolve => { + const parts = ['https://api.newsriver.io/v2/search?query=']; + this.searchTerms.forEach((term, i, _) => { + if (i !== 0) + parts.push(encodeURI(' OR ')); + parts.push(encodeURI(`title:${term} OR text:${term}`)); + }); + let language: string; + switch (this.language) { + case 'es': + language = 'ES'; + break; + default: + language = 'EN'; + break; + } + parts.push(encodeURI(` AND language:${language}`)); + parts.push(encodeURI('&sortBy=discoverDate')); + parts.push(encodeURI('&sortOrder=DESC')); + parts.push(encodeURI('&limit=10')); + resolve(parts.join('')); + }); + } +} \ No newline at end of file diff --git a/functions/RApi/src/views/error.pug b/functions/RApi/src/views/error.pug new file mode 100644 index 0000000..3b25cfa --- /dev/null +++ b/functions/RApi/src/views/error.pug @@ -0,0 +1,6 @@ +extends layout + +block content + h1= message + h2= error.status + pre #{error.stack} diff --git a/functions/RApi/src/views/layout.pug b/functions/RApi/src/views/layout.pug new file mode 100644 index 0000000..6dc17d8 --- /dev/null +++ b/functions/RApi/src/views/layout.pug @@ -0,0 +1,7 @@ +doctype html +html + head + title= title + link(rel='stylesheet', href='/stylesheets/style.css') + body + block content diff --git a/functions/RApi/tsconfig.json b/functions/RApi/tsconfig.json new file mode 100644 index 0000000..872b7f9 --- /dev/null +++ b/functions/RApi/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "module": "commonjs", + "noImplicitReturns": true, + "noUnusedLocals": true, + "outDir": "lib", + "sourceMap": true, + "strict": false, + "target": "es2017" + }, + "compileOnSave": true, + "include": [ + "src" + ] +} diff --git a/functions/RApi/tslint.json b/functions/RApi/tslint.json new file mode 100644 index 0000000..3c51143 --- /dev/null +++ b/functions/RApi/tslint.json @@ -0,0 +1,115 @@ +{ + "rules": { + // -- Strict errors -- + // These lint rules are likely always a good idea. + + // Force function overloads to be declared together. This ensures readers understand APIs. + "adjacent-overload-signatures": true, + + // Do not allow the subtle/obscure comma operator. + "ban-comma-operator": true, + + // Do not allow internal modules or namespaces . These are deprecated in favor of ES6 modules. + "no-namespace": true, + + // Do not allow parameters to be reassigned. To avoid bugs, developers should instead assign new values to new vars. + "no-parameter-reassignment": true, + + // Force the use of ES6-style imports instead of /// imports. + "no-reference": true, + + // Do not allow type assertions that do nothing. This is a big warning that the developer may not understand the + // code currently being edited (they may be incorrectly handling a different type case that does not exist). + "no-unnecessary-type-assertion": true, + + // Disallow nonsensical label usage. + "label-position": true, + + // Disallows the (often typo) syntax if (var1 = var2). Replace with if (var2) { var1 = var2 }. + "no-conditional-assignment": true, + + // Disallows constructors for primitive types (e.g. new Number('123'), though Number('123') is still allowed). + "no-construct": true, + + // Do not allow super() to be called twice in a constructor. + "no-duplicate-super": true, + + // Do not allow the same case to appear more than once in a switch block. + "no-duplicate-switch-case": true, + + // Do not allow a variable to be declared more than once in the same block. Consider function parameters in this + // rule. + "no-duplicate-variable": [true, "check-parameters"], + + // Disallows a variable definition in an inner scope from shadowing a variable in an outer scope. Developers should + // instead use a separate variable name. + "no-shadowed-variable": true, + + // Empty blocks are almost never needed. Allow the one general exception: empty catch blocks. + "no-empty": [true, "allow-empty-catch"], + + // Functions must either be handled directly (e.g. with a catch() handler) or returned to another function. + // This is a major source of errors in Cloud Functions and the team strongly recommends leaving this rule on. + "no-floating-promises": true, + + // Do not allow any imports for modules that are not in package.json. These will almost certainly fail when + // deployed. + "no-implicit-dependencies": true, + + // The 'this' keyword can only be used inside of classes. + "no-invalid-this": true, + + // Do not allow strings to be thrown because they will not include stack traces. Throw Errors instead. + "no-string-throw": true, + + // Disallow control flow statements, such as return, continue, break, and throw in finally blocks. + "no-unsafe-finally": true, + + // Expressions must always return a value. Avoids common errors like const myValue = functionReturningVoid(); + "no-void-expression": [true, "ignore-arrow-function-shorthand"], + + // Disallow duplicate imports in the same file. + "no-duplicate-imports": true, + + + // -- Strong Warnings -- + // These rules should almost never be needed, but may be included due to legacy code. + // They are left as a warning to avoid frustration with blocked deploys when the developer + // understand the warning and wants to deploy anyway. + + // Warn when an empty interface is defined. These are generally not useful. + "no-empty-interface": {"severity": "warning"}, + + // Warn when an import will have side effects. + "no-import-side-effect": {"severity": "warning"}, + + // Warn when variables are defined with var. Var has subtle meaning that can lead to bugs. Strongly prefer const for + // most values and let for values that will change. + "no-var-keyword": {"severity": "warning"}, + + // Prefer === and !== over == and !=. The latter operators support overloads that are often accidental. + "triple-equals": {"severity": "warning"}, + + // Warn when using deprecated APIs. + "deprecation": {"severity": "warning"}, + + // -- Light Warnings -- + // These rules are intended to help developers use better style. Simpler code has fewer bugs. These would be "info" + // if TSLint supported such a level. + + // prefer for( ... of ... ) to an index loop when the index is only used to fetch an object from an array. + // (Even better: check out utils like .map if transforming an array!) + "prefer-for-of": {"severity": "warning"}, + + // Warns if function overloads could be unified into a single function with optional or rest parameters. + "unified-signatures": {"severity": "warning"}, + + // Prefer const for values that will not change. This better documents code. + "prefer-const": {"severity": "warning"}, + + // Multi-line object literals and function calls should have a trailing comma. This helps avoid merge conflicts. + "trailing-comma": {"severity": "warning"} + }, + + "defaultSeverity": "warn" +} From a7321c50b42bbb01036d397d0efb4f4472be69a4 Mon Sep 17 00:00:00 2001 From: Javinator9889 Date: Mon, 8 Jun 2020 16:48:12 +0200 Subject: [PATCH 22/95] Removed unnecessary files --- functions/.gitignore | 15 - functions/.idea/.gitignore | 5 - functions/RApi/src/controllers/api.ts | 50 - functions/RApi/src/models/api.ts | 75 - functions/RApi/src/routes/api.ts | 178 - functions/RApi/src/routes/newsriver.ts | 1 - functions/RApi/src/updater.ts | 4 +- functions/package-lock.json | 3340 ----------------- functions/package.json | 31 - functions/src/app.ts | 43 - functions/src/bin/www.ts | 81 - functions/src/controllers/api.ts | 50 - functions/src/controllers/newsriver.ts | 17 - functions/src/interfaces/projectProperties.ts | 5 - functions/src/models/api.ts | 75 - functions/src/models/newsriver.ts | 70 - functions/src/newsriver.ts | 48 - functions/src/public/stylesheets/style.css | 8 - functions/src/rcdata.ts | 61 - functions/src/routes/api.ts | 178 - functions/src/routes/newsriver.ts | 51 - functions/src/updater.ts | 121 - functions/src/views/error.pug | 6 - functions/src/views/layout.pug | 7 - functions/tsconfig.json | 15 - functions/tslint.json | 115 - 26 files changed, 2 insertions(+), 4648 deletions(-) delete mode 100644 functions/.gitignore delete mode 100644 functions/.idea/.gitignore delete mode 100644 functions/RApi/src/controllers/api.ts delete mode 100644 functions/RApi/src/models/api.ts delete mode 100644 functions/RApi/src/routes/api.ts delete mode 100644 functions/package-lock.json delete mode 100644 functions/package.json delete mode 100644 functions/src/app.ts delete mode 100644 functions/src/bin/www.ts delete mode 100644 functions/src/controllers/api.ts delete mode 100644 functions/src/controllers/newsriver.ts delete mode 100644 functions/src/interfaces/projectProperties.ts delete mode 100644 functions/src/models/api.ts delete mode 100644 functions/src/models/newsriver.ts delete mode 100644 functions/src/newsriver.ts delete mode 100644 functions/src/public/stylesheets/style.css delete mode 100644 functions/src/rcdata.ts delete mode 100644 functions/src/routes/api.ts delete mode 100644 functions/src/routes/newsriver.ts delete mode 100644 functions/src/updater.ts delete mode 100644 functions/src/views/error.pug delete mode 100644 functions/src/views/layout.pug delete mode 100644 functions/tsconfig.json delete mode 100644 functions/tslint.json diff --git a/functions/.gitignore b/functions/.gitignore deleted file mode 100644 index 0b9c6b4..0000000 --- a/functions/.gitignore +++ /dev/null @@ -1,15 +0,0 @@ -## Compiled JavaScript files -**/*.js -**/*.js.map - -# Typescript v1 declaration files -typings/ - -node_modules/ - -handwashing-firebase-adminsdk.json -.runtimeconfig.json - -# Log files -*.log -admin-sdk.json \ No newline at end of file diff --git a/functions/.idea/.gitignore b/functions/.idea/.gitignore deleted file mode 100644 index b58b603..0000000 --- a/functions/.idea/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -# Default ignored files -/shelf/ -/workspace.xml -# Editor-based HTTP Client requests -/httpRequests/ diff --git a/functions/RApi/src/controllers/api.ts b/functions/RApi/src/controllers/api.ts deleted file mode 100644 index fc2d383..0000000 --- a/functions/RApi/src/controllers/api.ts +++ /dev/null @@ -1,50 +0,0 @@ -// import {Api} from '../models/api'; -// import api = require('../models/api'); -// import {api} from '../models/api'; -// import apiModels = require("../models/api"); - -import {Request, Response} from 'express'; -import {Api} from "../models/api"; - -/*export function getNewsByLanguage(req: Request, res: Response) { - console.log('Getting news by language'); - const language = req.query.lang as string; - apiModels.api.newsForLanguage(language) - .then(data => res.json(data)) - .catch(err => { - console.error(err); - res.sendStatus(304); - }); -}*/ - -export function ApiController(api: Api) { - return { - getNewsByLanguage(req: Request, res: Response) { - const language = req.query.lang as string; - api.newsForLanguage(language) - .then(data => res.json(data)) - .catch(err => { - console.error(err); - res.sendStatus(500); - }); - } - } -} - -/*export class ApiController { - api: Api - - constructor(api: Api) { - this.api = api; - } - - getNewsByLanguage(req: Request, res: Response) { - const language = req.query.lang as string; - this.api.newsForLanguage(language) - .then(data => res.json(data)) - .catch(err => { - console.error(err); - res.sendStatus(500); - }); - } -}*/ diff --git a/functions/RApi/src/models/api.ts b/functions/RApi/src/models/api.ts deleted file mode 100644 index 44ddc4e..0000000 --- a/functions/RApi/src/models/api.ts +++ /dev/null @@ -1,75 +0,0 @@ -// import * as functions from 'firebase-functions'; -// import * as admin from 'firebase-admin'; - -import {ProjectProperties} from '../interfaces/projectProperties'; -import {RemoteConfigData} from '../rcdata'; -import {Updater} from '../updater'; -import {NewsriverData} from '../newsriver'; - -export class Api { - properties: ProjectProperties; - remoteConfig: RemoteConfigData; - languages: Array; - updaters: Record; - timers: Set; - - constructor(properties: ProjectProperties, - remoteConfig: RemoteConfigData, - languages: Array) { - this.properties = properties; - this.remoteConfig = remoteConfig; - this.languages = languages; - this.updaters = {}; - for (const language in languages) { - this.updaters[language] = undefined; - } - this.timers = new Set(); - } - - async init() { - this.languages.forEach(language => { - this.remoteConfig.getSearchTermsForLanguage(language) - .then(terms => { - const updater = new Updater( - this.properties.database, - `${this.properties.collection}_${language}`, - terms, - this.properties.authToken, - language - ); - this.updaters[language] = updater; - this.timers.add(updater.schedule()); - }); - }); - this.remoteConfig.subscribeUpdaters(this.updaters); - } - - async newsForLanguage(language: string): Promise> { - if (language ! in this.languages) - language = 'en'; - const collection = this.updaters[language].collection; - const snapshot = await collection.get(); - const data = Array(snapshot.size); - snapshot.forEach(item => { - data.push(item.data() as NewsriverData); - }); - return data; - } - - finish() { - for (const timer of this.timers) { - clearInterval(timer); - } - } -} -/*const serviceAccount = require('../../handwashing-firebase-adminsdk.json'); -const firebaseApp = admin.initializeApp({ - credential: admin.credential.cert(serviceAccount), - databaseURL: 'https://handwashing.firebaseio.com' -});*/ -/*const firebaseApp = admin.initializeApp(); -const projectProperties: ProjectProperties = { - collection: 'news', - database: admin.firestore(), - authToken: functions.config().newsriver.key -};*/ \ No newline at end of file diff --git a/functions/RApi/src/routes/api.ts b/functions/RApi/src/routes/api.ts deleted file mode 100644 index 362378d..0000000 --- a/functions/RApi/src/routes/api.ts +++ /dev/null @@ -1,178 +0,0 @@ -import * as admin from 'firebase-admin'; -// import controller = require("../controllers/api"); -import {Router} from "express"; -// import get = Reflect.get; - -/*const router = Router(); - -router.get('/api/v1', (req, res, next) => { - try { - const language = req.query.lang; - const tokenId = req.get('Authorization').split('Bearer')[0]; - admin.auth().verifyIdToken(tokenId) - .then(_ => { - if (language === undefined) - res.status(403).send('lang must be given [?lang=...]'); - else next(); - }) - .catch(e => { - console.log('User unauthorized'); - next(); - // res.status(401).send(e) - }) - } catch (e) { - console.error(e); - res.status(401).send(e); - } -}); -router.get('/api/v1', controller.getNewsByLanguage);*/ -// app: admin.app.App, apiController: { getNewsByLanguage: (req, res) => any } -export function ApiRoutes(apiController: { getNewsByLanguage: (req, res) => any }) { - const router = Router(); - router.get('/api/v1', (req, res, next) => { - try { - const language = req.query.lang; - const tokenId = req.get('Authorization').split('Bearer')[0]; - admin.auth().verifyIdToken(tokenId) - .then(_ => { - if (language === undefined) - res.status(403).send('lang must be given [?lang=...]'); - else next(); - }) - .catch(e => { - console.log('User unauthorized'); - next(); - // res.status(401).send(e) - }) - } catch (e) { - console.error(e); - res.status(401).send(e); - } - }); - router.get('/api/v1', apiController.getNewsByLanguage); - - return {router}; -} - -/*export class ApiRouter { - router: Router - // firebaseApp: admin.app.App - - constructor(app: admin.app.App, apiController: ApiController) { - // this.firebaseApp = app; - this.router = Router(); - - this.router.get('/api/v1', (req, res, next) => { - try { - const language = req.query.lang; - const tokenId = req.get('Authorization').split('Bearer')[0]; - admin.auth(app).verifyIdToken(tokenId) - .then(_ => { - if (language === undefined) - res.status(403).send('lang must be given [?lang=...]'); - else next(); - }) - .catch(e => { - console.log('User unauthorized'); - next(); - // res.status(401).send(e) - }) - } catch (e) { - console.error(e); - res.status(401).send(e); - } - }); - // this.router.get('/api/v1', apiController.getNewsByLanguage); - } -}*/ - -// export = router; - -/*export class ApiRouter { - router: Router; - firebaseApp: admin.app.App; - - constructor(firebaseApp: admin.app.App, apiController: ApiController) { - this.router = Router(); - this.firebaseApp = firebaseApp; - // this.router.use('/api/v1', apiController.getNewsByLanguage); - this.router.use('/update', (req: Request, res: Response) => { - try { - apiController.api.updaters['en'].request() - .then(apiData => { - apiController.api.updaters['en'].updateData(apiData) - .then(_ => { - console.log("Data updated"); - res.json(apiData); - }) - .catch(err => { - console.error(err); - res.sendStatus(500); - }) - }) - .catch(e => { - console.error(e); - res.sendStatus(500); - }); - } catch (e) { - console.error(e); - res.sendStatus(500); - } - }); - } - - async init(): Promise { - this.router.use('/api/v1', (req: Request, res: Response, next: NextFunction) => { - try { - const language = req.query.lang; - const tokenId = req.get('Authorization').split('Bearer')[1]; - admin.auth(this.firebaseApp).verifyIdToken(tokenId) - .then(_ => { - if (language === undefined) - res.status(403).send('lang must be given [?lang=...]'); - else next(); - }) - .catch(err => res.status(401).send(err)); - } catch (e) { - res.status(401).send(e); - } - }); - return this.router; - } -}*/ - -// export const router = express.Router(); - -/*router.use('/api/v1', (req, res, next) => { - try { - const language = req.query.lang; - const tokenId = req.get('Authorization').split('Bearer ')[1]; - admin.auth().verifyIdToken(tokenId) - .then(_ => { - if (language === undefined) { - res.status(403).send('lang must be given [?lang=...]'); - } else next(); - }) - .catch(err => res.status(401).send(err)); - } catch (e) { - res.status(401).send(e); - } -});*/ -// router.use('/api/v1', getNewsByLanguage); -// router.use('/update', (req, res) => { -// api.updaters['es'].request() -// .then(apiData => { -// api.updaters['es'].updateData(apiData) -// .then(() => { -// console.log("Data was updated"); -// res.json(apiData); -// }) -// .catch((err) => { -// console.error(err); -// res.status(304).send(err); -// }); -// }) -// .catch((err) => console.error(err)); -// }); - -// module.exports = router; diff --git a/functions/RApi/src/routes/newsriver.ts b/functions/RApi/src/routes/newsriver.ts index 2ab181b..2df4f75 100644 --- a/functions/RApi/src/routes/newsriver.ts +++ b/functions/RApi/src/routes/newsriver.ts @@ -1,6 +1,5 @@ import apiController = require("../controllers/newsriver"); import express = require("express"); -// import admin = require("firebase-admin"); const router = express.Router(); diff --git a/functions/RApi/src/updater.ts b/functions/RApi/src/updater.ts index d570703..289f6b2 100644 --- a/functions/RApi/src/updater.ts +++ b/functions/RApi/src/updater.ts @@ -1,7 +1,7 @@ import {NewsriverData} from "./newsriver"; import * as firebaseHelper from 'firebase-functions-helper'; import * as fetch from 'node-fetch'; -import {Headers} from 'node-fetch'; + export class Updater { private readonly db: FirebaseFirestore.Firestore; @@ -81,7 +81,7 @@ export class Updater { try { const requestUrl = await this.url; const response = await fetch(requestUrl, { - method: 'GET', headers: new Headers({ + method: 'GET', headers: new fetch.Headers({ 'Authorization': this.auth, 'Content-Type': 'application/json' }) diff --git a/functions/package-lock.json b/functions/package-lock.json deleted file mode 100644 index 833ca3e..0000000 --- a/functions/package-lock.json +++ /dev/null @@ -1,3340 +0,0 @@ -{ - "name": "functions", - "requires": true, - "lockfileVersion": 1, - "dependencies": { - "@babel/code-frame": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.8.3.tgz", - "integrity": "sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g==", - "dev": true, - "requires": { - "@babel/highlight": "^7.8.3" - } - }, - "@babel/helper-validator-identifier": { - "version": "7.9.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.9.5.tgz", - "integrity": "sha512-/8arLKUFq882w4tWGj9JYzRpAlZgiWUJ+dtteNTDqrRBz9Iguck9Rn3ykuBDoUwh2TO4tSAJlrxDUOXWklJe4g==", - "dev": true - }, - "@babel/highlight": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.9.0.tgz", - "integrity": "sha512-lJZPilxX7Op3Nv/2cvFdnlepPXDxi29wxteT57Q965oc5R9v86ztx0jfxVrTcBk8C2kcPkkDa2Z4T3ZsPPVWsQ==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.9.0", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - } - }, - "@babel/parser": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.10.1.tgz", - "integrity": "sha512-AUTksaz3FqugBkbTZ1i+lDLG5qy8hIzCaAxEtttU6C0BtZZU9pkNZtWSVAht4EW9kl46YBiyTGMp9xTTGqViNg==" - }, - "@babel/types": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.10.1.tgz", - "integrity": "sha512-L2yqUOpf3tzlW9GVuipgLEcZxnO+96SzR6fjXMuxxNkIgFJ5+07mHCZ+HkHqaeZu8+3LKnNJJ1bKbjBETQAsrA==", - "requires": { - "@babel/helper-validator-identifier": "^7.10.1", - "lodash": "^4.17.13", - "to-fast-properties": "^2.0.0" - }, - "dependencies": { - "@babel/helper-validator-identifier": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.1.tgz", - "integrity": "sha512-5vW/JXLALhczRCWP0PnFDMCJAchlBvM7f4uk/jXritBnIa6E1KmqmtrS3yn1LAnxFBypQ3eneLuXjsnfQsgILw==" - } - } - }, - "@firebase/analytics": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/@firebase/analytics/-/analytics-0.3.6.tgz", - "integrity": "sha512-OgBPLsLcYSLBCBLMkuOPnW2YmGo0ruVBtnZ1Yhk0y54oCtrAcm0ijuI98h0evAdA7XfHPgmfKRpke9rU2X9OQQ==", - "requires": { - "@firebase/analytics-types": "0.3.1", - "@firebase/component": "0.1.13", - "@firebase/installations": "0.4.11", - "@firebase/logger": "0.2.5", - "@firebase/util": "0.2.48", - "tslib": "1.11.1" - }, - "dependencies": { - "@firebase/component": { - "version": "0.1.13", - "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.13.tgz", - "integrity": "sha512-DuSIM96NQkE3Yo77IOa5BWw8VBdvCR5cbMLNiFT4X3dTU15Dm0zHjncQHt/6rQpABGNYWAfOCJmSU1v6vc3DFA==", - "requires": { - "@firebase/util": "0.2.48", - "tslib": "1.11.1" - } - }, - "@firebase/logger": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.2.5.tgz", - "integrity": "sha512-qqw3m0tWs/qrg7axTZG/QZq24DIMdSY6dGoWuBn08ddq7+GLF5HiqkRj71XznYeUUbfRq5W9C/PSFnN4JxX+WA==" - }, - "@firebase/util": { - "version": "0.2.48", - "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.2.48.tgz", - "integrity": "sha512-6Wzq6IBF//3mrMTmTQ+JmceM0PMQpxV2GVfXhZn/4sMMkkhB0MA908nPDnatoHwUKyWE3BMw+uTLkyBnkuTu5A==", - "requires": { - "tslib": "1.11.1" - } - } - } - }, - "@firebase/analytics-types": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@firebase/analytics-types/-/analytics-types-0.3.1.tgz", - "integrity": "sha512-63vVJ5NIBh/JF8l9LuPrQYSzFimk7zYHySQB4Dk9rVdJ8kV/vGQoVTvRu1UW05sEc2Ug5PqtEChtTHU+9hvPcA==" - }, - "@firebase/app": { - "version": "0.6.5", - "resolved": "https://registry.npmjs.org/@firebase/app/-/app-0.6.5.tgz", - "integrity": "sha512-rhId5P4egyaVp4HaLsqxV1dJYWbjAyyTnoW8r6t2uXyUGBKUiv+tdK97abkHCo4UQwq/GvUu0Drd96Cm7nEIeA==", - "requires": { - "@firebase/app-types": "0.6.1", - "@firebase/component": "0.1.13", - "@firebase/logger": "0.2.5", - "@firebase/util": "0.2.48", - "dom-storage": "2.1.0", - "tslib": "1.11.1", - "xmlhttprequest": "1.8.0" - }, - "dependencies": { - "@firebase/component": { - "version": "0.1.13", - "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.13.tgz", - "integrity": "sha512-DuSIM96NQkE3Yo77IOa5BWw8VBdvCR5cbMLNiFT4X3dTU15Dm0zHjncQHt/6rQpABGNYWAfOCJmSU1v6vc3DFA==", - "requires": { - "@firebase/util": "0.2.48", - "tslib": "1.11.1" - } - }, - "@firebase/logger": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.2.5.tgz", - "integrity": "sha512-qqw3m0tWs/qrg7axTZG/QZq24DIMdSY6dGoWuBn08ddq7+GLF5HiqkRj71XznYeUUbfRq5W9C/PSFnN4JxX+WA==" - }, - "@firebase/util": { - "version": "0.2.48", - "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.2.48.tgz", - "integrity": "sha512-6Wzq6IBF//3mrMTmTQ+JmceM0PMQpxV2GVfXhZn/4sMMkkhB0MA908nPDnatoHwUKyWE3BMw+uTLkyBnkuTu5A==", - "requires": { - "tslib": "1.11.1" - } - } - } - }, - "@firebase/app-types": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.6.1.tgz", - "integrity": "sha512-L/ZnJRAq7F++utfuoTKX4CLBG5YR7tFO3PLzG1/oXXKEezJ0kRL3CMRoueBEmTCzVb/6SIs2Qlaw++uDgi5Xyg==" - }, - "@firebase/auth": { - "version": "0.14.6", - "resolved": "https://registry.npmjs.org/@firebase/auth/-/auth-0.14.6.tgz", - "integrity": "sha512-7gaEUWhUubWBGfOXAZvpTpJqBJT9KyG83RXC6VnjSQIfNUaarHZ485WkzERil43A6KvIl+f4kHxfZShE6ZCK3A==", - "requires": { - "@firebase/auth-types": "0.10.1" - } - }, - "@firebase/auth-interop-types": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/@firebase/auth-interop-types/-/auth-interop-types-0.1.5.tgz", - "integrity": "sha512-88h74TMQ6wXChPA6h9Q3E1Jg6TkTHep2+k63OWg3s0ozyGVMeY+TTOti7PFPzq5RhszQPQOoCi59es4MaRvgCw==" - }, - "@firebase/auth-types": { - "version": "0.10.1", - "resolved": "https://registry.npmjs.org/@firebase/auth-types/-/auth-types-0.10.1.tgz", - "integrity": "sha512-/+gBHb1O9x/YlG7inXfxff/6X3BPZt4zgBv4kql6HEmdzNQCodIRlEYnI+/da+lN+dha7PjaFH7C7ewMmfV7rw==" - }, - "@firebase/component": { - "version": "0.1.12", - "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.12.tgz", - "integrity": "sha512-03w800MxR/EW1m7N0Q46WNcngwdDIHDWpFPHTdbZEI6U/HuLks5RJQlBxWqb1P73nYPkN8YP3U8gTdqrDpqY3Q==", - "requires": { - "@firebase/util": "0.2.47", - "tslib": "1.11.1" - } - }, - "@firebase/database": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/@firebase/database/-/database-0.6.3.tgz", - "integrity": "sha512-gHoCISHQVLoq+rGu+PorYxMkhsjhXov3ocBxz/0uVdznNhrbKkAZaEKF+dIAsUPDlwSYeZuwWuik7xcV3DtRaw==", - "requires": { - "@firebase/auth-interop-types": "0.1.5", - "@firebase/component": "0.1.12", - "@firebase/database-types": "0.5.1", - "@firebase/logger": "0.2.4", - "@firebase/util": "0.2.47", - "faye-websocket": "0.11.3", - "tslib": "1.11.1" - } - }, - "@firebase/database-types": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-0.5.1.tgz", - "integrity": "sha512-onQxom1ZBYBJ648w/VNRzUewovEDAH7lvnrrpCd69ukkyrMk6rGEO/PQ9BcNEbhlNtukpsqRS0oNOFlHs0FaSA==", - "requires": { - "@firebase/app-types": "0.6.1" - } - }, - "@firebase/firestore": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/@firebase/firestore/-/firestore-1.15.0.tgz", - "integrity": "sha512-4eSBDY2hb/og8OEFZSjjzlb0y5+cWpridtQHLYsM4IPcOwxnnlE6RjGWN+UUxgKtvlkvOBdUIlTjL/YYoF+QcA==", - "requires": { - "@firebase/component": "0.1.13", - "@firebase/firestore-types": "1.11.0", - "@firebase/logger": "0.2.5", - "@firebase/util": "0.2.48", - "@firebase/webchannel-wrapper": "0.2.41", - "@grpc/grpc-js": "0.8.1", - "@grpc/proto-loader": "^0.5.0", - "tslib": "1.11.1" - }, - "dependencies": { - "@firebase/component": { - "version": "0.1.13", - "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.13.tgz", - "integrity": "sha512-DuSIM96NQkE3Yo77IOa5BWw8VBdvCR5cbMLNiFT4X3dTU15Dm0zHjncQHt/6rQpABGNYWAfOCJmSU1v6vc3DFA==", - "requires": { - "@firebase/util": "0.2.48", - "tslib": "1.11.1" - } - }, - "@firebase/logger": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.2.5.tgz", - "integrity": "sha512-qqw3m0tWs/qrg7axTZG/QZq24DIMdSY6dGoWuBn08ddq7+GLF5HiqkRj71XznYeUUbfRq5W9C/PSFnN4JxX+WA==" - }, - "@firebase/util": { - "version": "0.2.48", - "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.2.48.tgz", - "integrity": "sha512-6Wzq6IBF//3mrMTmTQ+JmceM0PMQpxV2GVfXhZn/4sMMkkhB0MA908nPDnatoHwUKyWE3BMw+uTLkyBnkuTu5A==", - "requires": { - "tslib": "1.11.1" - } - }, - "@grpc/grpc-js": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-0.8.1.tgz", - "integrity": "sha512-e8gSjRZnOUefsR3obOgxG9RtYW2Mw83hh7ogE2ByCdgRhoX0mdnJwBcZOami3E0l643KCTZvORFwfSEi48KFIQ==", - "requires": { - "semver": "^6.2.0" - } - } - } - }, - "@firebase/firestore-types": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@firebase/firestore-types/-/firestore-types-1.11.0.tgz", - "integrity": "sha512-hD7+cmMUvT5OJeWVrcRkE87PPuj/0/Wic6bntCopJE1WIX/Dm117AUkHgKd3S7Ici6DLp4bdlx1MjjwWL5942w==" - }, - "@firebase/functions": { - "version": "0.4.45", - "resolved": "https://registry.npmjs.org/@firebase/functions/-/functions-0.4.45.tgz", - "integrity": "sha512-Sy0D+52bkabdapTGxPlX+1b2FH+0BEJBmboLfM7EySZV/32oI277pDYKhyp9Pm//eOLspMOpEDvJz1WK8xmQcw==", - "requires": { - "@firebase/component": "0.1.13", - "@firebase/functions-types": "0.3.17", - "@firebase/messaging-types": "0.4.5", - "isomorphic-fetch": "2.2.1", - "tslib": "1.11.1" - }, - "dependencies": { - "@firebase/component": { - "version": "0.1.13", - "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.13.tgz", - "integrity": "sha512-DuSIM96NQkE3Yo77IOa5BWw8VBdvCR5cbMLNiFT4X3dTU15Dm0zHjncQHt/6rQpABGNYWAfOCJmSU1v6vc3DFA==", - "requires": { - "@firebase/util": "0.2.48", - "tslib": "1.11.1" - } - }, - "@firebase/util": { - "version": "0.2.48", - "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.2.48.tgz", - "integrity": "sha512-6Wzq6IBF//3mrMTmTQ+JmceM0PMQpxV2GVfXhZn/4sMMkkhB0MA908nPDnatoHwUKyWE3BMw+uTLkyBnkuTu5A==", - "requires": { - "tslib": "1.11.1" - } - } - } - }, - "@firebase/functions-types": { - "version": "0.3.17", - "resolved": "https://registry.npmjs.org/@firebase/functions-types/-/functions-types-0.3.17.tgz", - "integrity": "sha512-DGR4i3VI55KnYk4IxrIw7+VG7Q3gA65azHnZxo98Il8IvYLr2UTBlSh72dTLlDf25NW51HqvJgYJDKvSaAeyHQ==" - }, - "@firebase/installations": { - "version": "0.4.11", - "resolved": "https://registry.npmjs.org/@firebase/installations/-/installations-0.4.11.tgz", - "integrity": "sha512-ri+8O6vZPF0JKXboMzYFAbN7rn0OeUKLeMuCWdKZGJZD8NS8NRk/YvGRBa+IrkrwBeNNA/bMBUTEnhjN4CdVgQ==", - "requires": { - "@firebase/component": "0.1.13", - "@firebase/installations-types": "0.3.4", - "@firebase/util": "0.2.48", - "idb": "3.0.2", - "tslib": "1.11.1" - }, - "dependencies": { - "@firebase/component": { - "version": "0.1.13", - "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.13.tgz", - "integrity": "sha512-DuSIM96NQkE3Yo77IOa5BWw8VBdvCR5cbMLNiFT4X3dTU15Dm0zHjncQHt/6rQpABGNYWAfOCJmSU1v6vc3DFA==", - "requires": { - "@firebase/util": "0.2.48", - "tslib": "1.11.1" - } - }, - "@firebase/util": { - "version": "0.2.48", - "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.2.48.tgz", - "integrity": "sha512-6Wzq6IBF//3mrMTmTQ+JmceM0PMQpxV2GVfXhZn/4sMMkkhB0MA908nPDnatoHwUKyWE3BMw+uTLkyBnkuTu5A==", - "requires": { - "tslib": "1.11.1" - } - } - } - }, - "@firebase/installations-types": { - "version": "0.3.4", - "resolved": "https://registry.npmjs.org/@firebase/installations-types/-/installations-types-0.3.4.tgz", - "integrity": "sha512-RfePJFovmdIXb6rYwtngyxuEcWnOrzdZd9m7xAW0gRxDIjBT20n3BOhjpmgRWXo/DAxRmS7bRjWAyTHY9cqN7Q==" - }, - "@firebase/logger": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.2.4.tgz", - "integrity": "sha512-akHkOU7izYB1okp/B5sxClGjjw6KvZdSHyjNM5pKd67Zg5W6PsbkI/GFNv21+y6LkUkJwDRbdeDgJoYXWT3mMA==" - }, - "@firebase/messaging": { - "version": "0.6.17", - "resolved": "https://registry.npmjs.org/@firebase/messaging/-/messaging-0.6.17.tgz", - "integrity": "sha512-wwAn2HrklhBxHpk5UpudJ0wCrlUC9ovFJ88cSOALf82po423IOwR5ijq1z2zKzZiz4D1dLv7rJIqZ0N1MZ/Giw==", - "requires": { - "@firebase/component": "0.1.13", - "@firebase/installations": "0.4.11", - "@firebase/messaging-types": "0.4.5", - "@firebase/util": "0.2.48", - "idb": "3.0.2", - "tslib": "1.11.1" - }, - "dependencies": { - "@firebase/component": { - "version": "0.1.13", - "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.13.tgz", - "integrity": "sha512-DuSIM96NQkE3Yo77IOa5BWw8VBdvCR5cbMLNiFT4X3dTU15Dm0zHjncQHt/6rQpABGNYWAfOCJmSU1v6vc3DFA==", - "requires": { - "@firebase/util": "0.2.48", - "tslib": "1.11.1" - } - }, - "@firebase/util": { - "version": "0.2.48", - "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.2.48.tgz", - "integrity": "sha512-6Wzq6IBF//3mrMTmTQ+JmceM0PMQpxV2GVfXhZn/4sMMkkhB0MA908nPDnatoHwUKyWE3BMw+uTLkyBnkuTu5A==", - "requires": { - "tslib": "1.11.1" - } - } - } - }, - "@firebase/messaging-types": { - "version": "0.4.5", - "resolved": "https://registry.npmjs.org/@firebase/messaging-types/-/messaging-types-0.4.5.tgz", - "integrity": "sha512-sux4fgqr/0KyIxqzHlatI04Ajs5rc3WM+WmtCpxrKP1E5Bke8xu/0M+2oy4lK/sQ7nov9z15n3iltAHCgTRU3Q==" - }, - "@firebase/performance": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/@firebase/performance/-/performance-0.3.6.tgz", - "integrity": "sha512-AB+ohBYgF8fe9FacDAcwJaBLRrXgt93no6Pj14xzQ4oX31dQpPrWZdFfNYEUZRU1Gnb/fqWlCaBTObzUXD5cag==", - "requires": { - "@firebase/component": "0.1.13", - "@firebase/installations": "0.4.11", - "@firebase/logger": "0.2.5", - "@firebase/performance-types": "0.0.13", - "@firebase/util": "0.2.48", - "tslib": "1.11.1" - }, - "dependencies": { - "@firebase/component": { - "version": "0.1.13", - "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.13.tgz", - "integrity": "sha512-DuSIM96NQkE3Yo77IOa5BWw8VBdvCR5cbMLNiFT4X3dTU15Dm0zHjncQHt/6rQpABGNYWAfOCJmSU1v6vc3DFA==", - "requires": { - "@firebase/util": "0.2.48", - "tslib": "1.11.1" - } - }, - "@firebase/logger": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.2.5.tgz", - "integrity": "sha512-qqw3m0tWs/qrg7axTZG/QZq24DIMdSY6dGoWuBn08ddq7+GLF5HiqkRj71XznYeUUbfRq5W9C/PSFnN4JxX+WA==" - }, - "@firebase/util": { - "version": "0.2.48", - "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.2.48.tgz", - "integrity": "sha512-6Wzq6IBF//3mrMTmTQ+JmceM0PMQpxV2GVfXhZn/4sMMkkhB0MA908nPDnatoHwUKyWE3BMw+uTLkyBnkuTu5A==", - "requires": { - "tslib": "1.11.1" - } - } - } - }, - "@firebase/performance-types": { - "version": "0.0.13", - "resolved": "https://registry.npmjs.org/@firebase/performance-types/-/performance-types-0.0.13.tgz", - "integrity": "sha512-6fZfIGjQpwo9S5OzMpPyqgYAUZcFzZxHFqOyNtorDIgNXq33nlldTL/vtaUZA8iT9TT5cJlCrF/jthKU7X21EA==" - }, - "@firebase/polyfill": { - "version": "0.3.36", - "resolved": "https://registry.npmjs.org/@firebase/polyfill/-/polyfill-0.3.36.tgz", - "integrity": "sha512-zMM9oSJgY6cT2jx3Ce9LYqb0eIpDE52meIzd/oe/y70F+v9u1LDqk5kUF5mf16zovGBWMNFmgzlsh6Wj0OsFtg==", - "requires": { - "core-js": "3.6.5", - "promise-polyfill": "8.1.3", - "whatwg-fetch": "2.0.4" - }, - "dependencies": { - "whatwg-fetch": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-2.0.4.tgz", - "integrity": "sha512-dcQ1GWpOD/eEQ97k66aiEVpNnapVj90/+R+SXTPYGHpYBBypfKJEQjLrvMZ7YXbKm21gXd4NcuxUTjiv1YtLng==" - } - } - }, - "@firebase/remote-config": { - "version": "0.1.22", - "resolved": "https://registry.npmjs.org/@firebase/remote-config/-/remote-config-0.1.22.tgz", - "integrity": "sha512-xX/b+euI/RP1qAWqNI5YkZ4VFNL00CI7jBJmPzoBbSl1vVglR1ya7u1fbC5SeowxnD1/0QIOYbe5sdnqjmLsyg==", - "requires": { - "@firebase/component": "0.1.13", - "@firebase/installations": "0.4.11", - "@firebase/logger": "0.2.5", - "@firebase/remote-config-types": "0.1.9", - "@firebase/util": "0.2.48", - "tslib": "1.11.1" - }, - "dependencies": { - "@firebase/component": { - "version": "0.1.13", - "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.13.tgz", - "integrity": "sha512-DuSIM96NQkE3Yo77IOa5BWw8VBdvCR5cbMLNiFT4X3dTU15Dm0zHjncQHt/6rQpABGNYWAfOCJmSU1v6vc3DFA==", - "requires": { - "@firebase/util": "0.2.48", - "tslib": "1.11.1" - } - }, - "@firebase/logger": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.2.5.tgz", - "integrity": "sha512-qqw3m0tWs/qrg7axTZG/QZq24DIMdSY6dGoWuBn08ddq7+GLF5HiqkRj71XznYeUUbfRq5W9C/PSFnN4JxX+WA==" - }, - "@firebase/util": { - "version": "0.2.48", - "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.2.48.tgz", - "integrity": "sha512-6Wzq6IBF//3mrMTmTQ+JmceM0PMQpxV2GVfXhZn/4sMMkkhB0MA908nPDnatoHwUKyWE3BMw+uTLkyBnkuTu5A==", - "requires": { - "tslib": "1.11.1" - } - } - } - }, - "@firebase/remote-config-types": { - "version": "0.1.9", - "resolved": "https://registry.npmjs.org/@firebase/remote-config-types/-/remote-config-types-0.1.9.tgz", - "integrity": "sha512-G96qnF3RYGbZsTRut7NBX0sxyczxt1uyCgXQuH/eAfUCngxjEGcZQnBdy6mvSdqdJh5mC31rWPO4v9/s7HwtzA==" - }, - "@firebase/storage": { - "version": "0.3.35", - "resolved": "https://registry.npmjs.org/@firebase/storage/-/storage-0.3.35.tgz", - "integrity": "sha512-kS+P5X3lla9bpeWVwmRzJ5atMDPlhLPa8jgutN9vWXWfVnlj82U8VqeAqWc8ndHumHiV0TYLDk9DdGfs6rFL3w==", - "requires": { - "@firebase/component": "0.1.13", - "@firebase/storage-types": "0.3.12", - "@firebase/util": "0.2.48", - "tslib": "1.11.1" - }, - "dependencies": { - "@firebase/component": { - "version": "0.1.13", - "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.13.tgz", - "integrity": "sha512-DuSIM96NQkE3Yo77IOa5BWw8VBdvCR5cbMLNiFT4X3dTU15Dm0zHjncQHt/6rQpABGNYWAfOCJmSU1v6vc3DFA==", - "requires": { - "@firebase/util": "0.2.48", - "tslib": "1.11.1" - } - }, - "@firebase/util": { - "version": "0.2.48", - "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.2.48.tgz", - "integrity": "sha512-6Wzq6IBF//3mrMTmTQ+JmceM0PMQpxV2GVfXhZn/4sMMkkhB0MA908nPDnatoHwUKyWE3BMw+uTLkyBnkuTu5A==", - "requires": { - "tslib": "1.11.1" - } - } - } - }, - "@firebase/storage-types": { - "version": "0.3.12", - "resolved": "https://registry.npmjs.org/@firebase/storage-types/-/storage-types-0.3.12.tgz", - "integrity": "sha512-DDV6Fs6aYoGw3w/zZZTkqiipxihnsvHf6znbeZYjIIHit3tr1uLJdGPDPiCTfZcTGPpg2ux6ZmvNDvVgJdHALw==" - }, - "@firebase/util": { - "version": "0.2.47", - "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.2.47.tgz", - "integrity": "sha512-RjcIvcfswyxYhf0OMXod+qeI/933wl9FGLIszf0/O1yMZ/s8moXcse7xnOpMjmQPRLB9vHzCMoxW5X90kKg/bQ==", - "requires": { - "tslib": "1.11.1" - } - }, - "@firebase/webchannel-wrapper": { - "version": "0.2.41", - "resolved": "https://registry.npmjs.org/@firebase/webchannel-wrapper/-/webchannel-wrapper-0.2.41.tgz", - "integrity": "sha512-XcdMT5PSZHiuf7LJIhzKIe+RyYa25S3LHRRvLnZc6iFjwXkrSDJ8J/HWO6VT8d2ZTbawp3VcLEjRF/VN8glCrA==" - }, - "@google-cloud/common": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@google-cloud/common/-/common-2.4.0.tgz", - "integrity": "sha512-zWFjBS35eI9leAHhjfeOYlK5Plcuj/77EzstnrJIZbKgF/nkqjcQuGiMCpzCwOfPyUbz8ZaEOYgbHa759AKbjg==", - "optional": true, - "requires": { - "@google-cloud/projectify": "^1.0.0", - "@google-cloud/promisify": "^1.0.0", - "arrify": "^2.0.0", - "duplexify": "^3.6.0", - "ent": "^2.2.0", - "extend": "^3.0.2", - "google-auth-library": "^5.5.0", - "retry-request": "^4.0.0", - "teeny-request": "^6.0.0" - } - }, - "@google-cloud/firestore": { - "version": "3.8.0", - "resolved": "https://registry.npmjs.org/@google-cloud/firestore/-/firestore-3.8.0.tgz", - "integrity": "sha512-FEv52jhsRAV/0nHaMy0PAmExYkYRLDoOXRB+85euaC72HZU8aV/bgOi1OvAeiD+ogoO39ilxiHQh5PaRHIolug==", - "optional": true, - "requires": { - "deep-equal": "^2.0.0", - "functional-red-black-tree": "^1.0.1", - "google-gax": "^1.13.0", - "readable-stream": "^3.4.0", - "through2": "^3.0.0" - } - }, - "@google-cloud/paginator": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@google-cloud/paginator/-/paginator-2.0.3.tgz", - "integrity": "sha512-kp/pkb2p/p0d8/SKUu4mOq8+HGwF8NPzHWkj+VKrIPQPyMRw8deZtrO/OcSiy9C/7bpfU5Txah5ltUNfPkgEXg==", - "optional": true, - "requires": { - "arrify": "^2.0.0", - "extend": "^3.0.2" - } - }, - "@google-cloud/projectify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@google-cloud/projectify/-/projectify-1.0.4.tgz", - "integrity": "sha512-ZdzQUN02eRsmTKfBj9FDL0KNDIFNjBn/d6tHQmA/+FImH5DO6ZV8E7FzxMgAUiVAUq41RFAkb25p1oHOZ8psfg==", - "optional": true - }, - "@google-cloud/promisify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@google-cloud/promisify/-/promisify-1.0.4.tgz", - "integrity": "sha512-VccZDcOql77obTnFh0TbNED/6ZbbmHDf8UMNnzO1d5g9V0Htfm4k5cllY8P1tJsRKC3zWYGRLaViiupcgVjBoQ==", - "optional": true - }, - "@google-cloud/storage": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/@google-cloud/storage/-/storage-4.7.0.tgz", - "integrity": "sha512-f0guAlbeg7Z0m3gKjCfBCu7FG9qS3M3oL5OQQxlvGoPtK7/qg3+W+KQV73O2/sbuS54n0Kh2mvT5K2FWzF5vVQ==", - "optional": true, - "requires": { - "@google-cloud/common": "^2.1.1", - "@google-cloud/paginator": "^2.0.0", - "@google-cloud/promisify": "^1.0.0", - "arrify": "^2.0.0", - "compressible": "^2.0.12", - "concat-stream": "^2.0.0", - "date-and-time": "^0.13.0", - "duplexify": "^3.5.0", - "extend": "^3.0.2", - "gaxios": "^3.0.0", - "gcs-resumable-upload": "^2.2.4", - "hash-stream-validation": "^0.2.2", - "mime": "^2.2.0", - "mime-types": "^2.0.8", - "onetime": "^5.1.0", - "p-limit": "^2.2.0", - "pumpify": "^2.0.0", - "readable-stream": "^3.4.0", - "snakeize": "^0.1.0", - "stream-events": "^1.0.1", - "through2": "^3.0.0", - "xdg-basedir": "^4.0.0" - } - }, - "@grpc/grpc-js": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.0.4.tgz", - "integrity": "sha512-Qawt6HUrEmljQMPWnLnIXpcjelmtIAydi3M9awiG02WWJ1CmIvFEx4IOC1EsWUWUlabOGksRbpfvoIeZKFTNXw==", - "optional": true, - "requires": { - "google-auth-library": "^6.0.0", - "semver": "^6.2.0" - }, - "dependencies": { - "google-auth-library": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-6.0.0.tgz", - "integrity": "sha512-uLydy1t6SHN/EvYUJrtN3GCHFrnJ0c8HJjOxXiGjoTuYHIoCUT3jVxnzmjHwVnSdkfE9Akasm2rM6qG1COTXfQ==", - "optional": true, - "requires": { - "arrify": "^2.0.0", - "base64-js": "^1.3.0", - "ecdsa-sig-formatter": "^1.0.11", - "fast-text-encoding": "^1.0.0", - "gaxios": "^3.0.0", - "gcp-metadata": "^4.0.0", - "gtoken": "^5.0.0", - "jws": "^4.0.0", - "lru-cache": "^5.0.0" - } - } - } - }, - "@grpc/proto-loader": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.5.4.tgz", - "integrity": "sha512-HTM4QpI9B2XFkPz7pjwMyMgZchJ93TVkL3kWPW8GDMDKYxsMnmf4w2TNMJK7+KNiYHS5cJrCEAFlF+AwtXWVPA==", - "requires": { - "lodash.camelcase": "^4.3.0", - "protobufjs": "^6.8.6" - } - }, - "@protobufjs/aspromise": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", - "integrity": "sha1-m4sMxmPWaafY9vXQiToU00jzD78=" - }, - "@protobufjs/base64": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", - "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==" - }, - "@protobufjs/codegen": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", - "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==" - }, - "@protobufjs/eventemitter": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", - "integrity": "sha1-NVy8mLr61ZePntCV85diHx0Ga3A=" - }, - "@protobufjs/fetch": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", - "integrity": "sha1-upn7WYYUr2VwDBYZ/wbUVLDYTEU=", - "requires": { - "@protobufjs/aspromise": "^1.1.1", - "@protobufjs/inquire": "^1.1.0" - } - }, - "@protobufjs/float": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", - "integrity": "sha1-Xp4avctz/Ap8uLKR33jIy9l7h9E=" - }, - "@protobufjs/inquire": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", - "integrity": "sha1-/yAOPnzyQp4tyvwRQIKOjMY48Ik=" - }, - "@protobufjs/path": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", - "integrity": "sha1-bMKyDFya1q0NzP0hynZz2Nf79o0=" - }, - "@protobufjs/pool": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", - "integrity": "sha1-Cf0V8tbTq/qbZbw2ZQbWrXhG/1Q=" - }, - "@protobufjs/utf8": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", - "integrity": "sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA=" - }, - "@tootallnate/once": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", - "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==", - "optional": true - }, - "@types/body-parser": { - "version": "1.19.0", - "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.0.tgz", - "integrity": "sha512-W98JrE0j2K78swW4ukqMleo8R7h/pFETjM2DQ90MF6XK2i4LO4W3gQ71Lt4w3bfm2EvVSyWHplECvB5sK22yFQ==", - "requires": { - "@types/connect": "*", - "@types/node": "*" - } - }, - "@types/connect": { - "version": "3.4.33", - "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.33.tgz", - "integrity": "sha512-2+FrkXY4zllzTNfJth7jOqEHC+enpLeGslEhpnTAkg21GkRrWV4SsAtqchtT4YS9/nODBU2/ZfsBY2X4J/dX7A==", - "requires": { - "@types/node": "*" - } - }, - "@types/express": { - "version": "4.17.6", - "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.6.tgz", - "integrity": "sha512-n/mr9tZI83kd4azlPG5y997C/M4DNABK9yErhFM6hKdym4kkmd9j0vtsJyjFIwfRBxtrxZtAfGZCNRIBMFLK5w==", - "requires": { - "@types/body-parser": "*", - "@types/express-serve-static-core": "*", - "@types/qs": "*", - "@types/serve-static": "*" - } - }, - "@types/express-serve-static-core": { - "version": "4.17.7", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.7.tgz", - "integrity": "sha512-EMgTj/DF9qpgLXyc+Btimg+XoH7A2liE8uKul8qSmMTHCeNYzydDKFdsJskDvw42UsesCnhO63dO0Grbj8J4Dw==", - "requires": { - "@types/node": "*", - "@types/qs": "*", - "@types/range-parser": "*" - } - }, - "@types/fs-extra": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-8.1.1.tgz", - "integrity": "sha512-TcUlBem321DFQzBNuz8p0CLLKp0VvF/XH9E4KHNmgwyp4E3AfgI5cjiIVZWlbfThBop2qxFIh4+LeY6hVWWZ2w==", - "optional": true, - "requires": { - "@types/node": "*" - } - }, - "@types/lodash": { - "version": "4.14.152", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.152.tgz", - "integrity": "sha512-Vwf9YF2x1GE3WNeUMjT5bTHa2DqgUo87ocdgTScupY2JclZ5Nn7W2RLM/N0+oreexUk8uaVugR81NnTY/jNNXg==", - "dev": true - }, - "@types/long": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.1.tgz", - "integrity": "sha512-5tXH6Bx/kNGd3MgffdmP4dy2Z+G4eaXw0SE81Tq3BNadtnMR5/ySMzX4SLEzHJzSmPNn4HIdpQsBvXMUykr58w==" - }, - "@types/mime": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@types/mime/-/mime-2.0.2.tgz", - "integrity": "sha512-4kPlzbljFcsttWEq6aBW0OZe6BDajAmyvr2xknBG92tejQnvdGtT9+kXSZ580DqpxY9qG2xeQVF9Dq0ymUTo5Q==" - }, - "@types/node": { - "version": "8.10.61", - "resolved": "https://registry.npmjs.org/@types/node/-/node-8.10.61.tgz", - "integrity": "sha512-l+zSbvT8TPRaCxL1l9cwHCb0tSqGAGcjPJFItGGYat5oCTiq1uQQKYg5m7AF1mgnEBzFXGLJ2LRmNjtreRX76Q==" - }, - "@types/qs": { - "version": "6.9.3", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.3.tgz", - "integrity": "sha512-7s9EQWupR1fTc2pSMtXRQ9w9gLOcrJn+h7HOXw4evxyvVqMi4f+q7d2tnFe3ng3SNHjtK+0EzGMGFUQX4/AQRA==" - }, - "@types/range-parser": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.3.tgz", - "integrity": "sha512-ewFXqrQHlFsgc09MK5jP5iR7vumV/BYayNC6PgJO2LPe8vrnNFyjQjSppfEngITi0qvfKtzFvgKymGheFM9UOA==" - }, - "@types/serve-static": { - "version": "1.13.4", - "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.4.tgz", - "integrity": "sha512-jTDt0o/YbpNwZbQmE/+2e+lfjJEJJR0I3OFaKQKPWkASkCoW3i6fsUnqudSMcNAfbtmADGu8f4MV4q+GqULmug==", - "requires": { - "@types/express-serve-static-core": "*", - "@types/mime": "*" - } - }, - "abort-controller": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", - "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", - "optional": true, - "requires": { - "event-target-shim": "^5.0.0" - } - }, - "accepts": { - "version": "1.3.7", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", - "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", - "requires": { - "mime-types": "~2.1.24", - "negotiator": "0.6.2" - } - }, - "acorn": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.2.0.tgz", - "integrity": "sha512-apwXVmYVpQ34m/i71vrApRrRKCWQnZZF1+npOD0WV5xZFfwWOmKGQ2RWlfdy9vWITsenisM8M0Qeq8agcFHNiQ==" - }, - "agent-base": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.0.tgz", - "integrity": "sha512-j1Q7cSCqN+AwrmDd+pzgqc0/NpC655x2bUf5ZjRIO77DcNBFmh+OgRNzF6OKdCC9RSCb19fGd99+bhXFdkRNqw==", - "optional": true, - "requires": { - "debug": "4" - } - }, - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "requires": { - "sprintf-js": "~1.0.2" - } - }, - "array-filter": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/array-filter/-/array-filter-1.0.0.tgz", - "integrity": "sha1-uveeYubvTCpMC4MSMtr/7CUfnYM=", - "optional": true - }, - "array-flatten": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" - }, - "arrify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz", - "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==", - "optional": true - }, - "asap": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", - "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=" - }, - "assert-never": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/assert-never/-/assert-never-1.2.1.tgz", - "integrity": "sha512-TaTivMB6pYI1kXwrFlEhLeGfOqoDNdTxjCdwRfFFkEA30Eu+k48W34nlok2EYWJfFFzqaEmichdNM7th6M5HNw==" - }, - "assertion-error": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", - "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==" - }, - "available-typed-arrays": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.2.tgz", - "integrity": "sha512-XWX3OX8Onv97LMk/ftVyBibpGwY5a8SmuxZPzeOxqmuEqUCOM9ZE+uIaD1VNJ5QnvU2UQusvmKbuM1FR8QWGfQ==", - "optional": true, - "requires": { - "array-filter": "^1.0.0" - } - }, - "babel-walk": { - "version": "3.0.0-canary-5", - "resolved": "https://registry.npmjs.org/babel-walk/-/babel-walk-3.0.0-canary-5.tgz", - "integrity": "sha512-GAwkz0AihzY5bkwIY5QDR+LvsRQgB/B+1foMPvi0FZPMl5fjD7ICiznUiBdLYMH1QYe6vqu4gWYytZOccLouFw==", - "requires": { - "@babel/types": "^7.9.6" - } - }, - "balanced-match": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", - "dev": true - }, - "base64-js": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz", - "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==", - "optional": true - }, - "basic-auth": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", - "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", - "requires": { - "safe-buffer": "5.1.2" - }, - "dependencies": { - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - } - } - }, - "bignumber.js": { - "version": "7.2.1", - "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-7.2.1.tgz", - "integrity": "sha512-S4XzBk5sMB+Rcb/LNcpzXr57VRTxgAvaAEDAl1AwRx27j00hT84O6OkteE7u8UB3NuaaygCRrEpqox4uDOrbdQ==", - "optional": true - }, - "body-parser": { - "version": "1.19.0", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", - "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", - "requires": { - "bytes": "3.1.0", - "content-type": "~1.0.4", - "debug": "2.6.9", - "depd": "~1.1.2", - "http-errors": "1.7.2", - "iconv-lite": "0.4.24", - "on-finished": "~2.3.0", - "qs": "6.7.0", - "raw-body": "2.4.0", - "type-is": "~1.6.17" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } - }, - "http-errors": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", - "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", - "requires": { - "depd": "~1.1.2", - "inherits": "2.0.3", - "setprototypeof": "1.1.1", - "statuses": ">= 1.5.0 < 2", - "toidentifier": "1.0.0" - } - }, - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - } - } - }, - "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "buffer-equal-constant-time": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", - "integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=" - }, - "buffer-from": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", - "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", - "optional": true - }, - "builtin-modules": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", - "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=", - "dev": true - }, - "bytes": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", - "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==" - }, - "chai": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/chai/-/chai-4.2.0.tgz", - "integrity": "sha512-XQU3bhBukrOsQCuwZndwGcCVQHyZi53fQ6Ys1Fym7E4olpIqqZZhhoFJoaKVvV17lWQoXYwgWN2nF5crA8J2jw==", - "requires": { - "assertion-error": "^1.1.0", - "check-error": "^1.0.2", - "deep-eql": "^3.0.1", - "get-func-name": "^2.0.0", - "pathval": "^1.1.0", - "type-detect": "^4.0.5" - } - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "character-parser": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/character-parser/-/character-parser-2.2.0.tgz", - "integrity": "sha1-x84o821LzZdE5f/CxfzeHHMmH8A=", - "requires": { - "is-regex": "^1.0.3" - } - }, - "check-error": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", - "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=" - }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true - }, - "commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true - }, - "compressible": { - "version": "2.0.18", - "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", - "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", - "optional": true, - "requires": { - "mime-db": ">= 1.43.0 < 2" - } - }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true - }, - "concat-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz", - "integrity": "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==", - "optional": true, - "requires": { - "buffer-from": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^3.0.2", - "typedarray": "^0.0.6" - } - }, - "configstore": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/configstore/-/configstore-5.0.1.tgz", - "integrity": "sha512-aMKprgk5YhBNyH25hj8wGt2+D52Sw1DRRIzqBwLp2Ya9mFmY8KPvvtvmna8SxVR9JMZ4kzMD68N22vlaRpkeFA==", - "optional": true, - "requires": { - "dot-prop": "^5.2.0", - "graceful-fs": "^4.1.2", - "make-dir": "^3.0.0", - "unique-string": "^2.0.0", - "write-file-atomic": "^3.0.0", - "xdg-basedir": "^4.0.0" - } - }, - "constantinople": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/constantinople/-/constantinople-4.0.1.tgz", - "integrity": "sha512-vCrqcSIq4//Gx74TXXCGnHpulY1dskqLTFGDmhrGxzeXL8lF8kvXv6mpNWlJj1uD4DW23D4ljAqbY4RRaaUZIw==", - "requires": { - "@babel/parser": "^7.6.0", - "@babel/types": "^7.6.1" - } - }, - "content-disposition": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", - "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", - "requires": { - "safe-buffer": "5.1.2" - }, - "dependencies": { - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - } - } - }, - "content-type": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", - "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" - }, - "cookie": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", - "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==" - }, - "cookie-signature": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" - }, - "core-js": { - "version": "3.6.5", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.6.5.tgz", - "integrity": "sha512-vZVEEwZoIsI+vPEuoF9Iqf5H7/M3eeQqWlQnYa8FSKKePuYTf5MWnxb5SDAzCa60b3JBRS5g9b+Dq7b1y/RCrA==" - }, - "core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", - "optional": true - }, - "cors": { - "version": "2.8.5", - "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", - "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", - "requires": { - "object-assign": "^4", - "vary": "^1" - } - }, - "crypto-random-string": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz", - "integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==", - "optional": true - }, - "date-and-time": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/date-and-time/-/date-and-time-0.13.1.tgz", - "integrity": "sha512-/Uge9DJAT+s+oAcDxtBhyR8+sKjUnZbYmyhbmWjTHNtX7B7oWD8YyYdeXcBRbwSj6hVvj+IQegJam7m7czhbFw==", - "optional": true - }, - "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", - "optional": true, - "requires": { - "ms": "^2.1.1" - } - }, - "deep-eql": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", - "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==", - "requires": { - "type-detect": "^4.0.0" - } - }, - "deep-equal": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.0.3.tgz", - "integrity": "sha512-Spqdl4H+ky45I9ByyJtXteOm9CaIrPmnIPmOhrkKGNYWeDgCvJ8jNYVCTjChxW4FqGuZnLHADc8EKRMX6+CgvA==", - "optional": true, - "requires": { - "es-abstract": "^1.17.5", - "es-get-iterator": "^1.1.0", - "is-arguments": "^1.0.4", - "is-date-object": "^1.0.2", - "is-regex": "^1.0.5", - "isarray": "^2.0.5", - "object-is": "^1.1.2", - "object-keys": "^1.1.1", - "object.assign": "^4.1.0", - "regexp.prototype.flags": "^1.3.0", - "side-channel": "^1.0.2", - "which-boxed-primitive": "^1.0.1", - "which-collection": "^1.0.1", - "which-typed-array": "^1.1.2" - } - }, - "define-properties": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", - "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", - "requires": { - "object-keys": "^1.0.12" - } - }, - "depd": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" - }, - "destroy": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", - "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" - }, - "dicer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/dicer/-/dicer-0.3.0.tgz", - "integrity": "sha512-MdceRRWqltEG2dZqO769g27N/3PXfcKl04VhYnBlo2YhH7zPi88VebsjTKclaOyiuMaGU72hTfw3VkUitGcVCA==", - "requires": { - "streamsearch": "0.1.2" - } - }, - "diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", - "dev": true - }, - "doctypes": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/doctypes/-/doctypes-1.1.0.tgz", - "integrity": "sha1-6oCxBqh1OHdOijpKWv4pPeSJ4Kk=" - }, - "dom-storage": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/dom-storage/-/dom-storage-2.1.0.tgz", - "integrity": "sha512-g6RpyWXzl0RR6OTElHKBl7nwnK87GUyZMYC7JWsB/IA73vpqK2K6LT39x4VepLxlSsWBFrPVLnsSR5Jyty0+2Q==" - }, - "dot-prop": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.2.0.tgz", - "integrity": "sha512-uEUyaDKoSQ1M4Oq8l45hSE26SnTxL6snNnqvK/VWx5wJhmff5z0FUVJDKDanor/6w3kzE3i7XZOk+7wC0EXr1A==", - "optional": true, - "requires": { - "is-obj": "^2.0.0" - } - }, - "duplexify": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz", - "integrity": "sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==", - "optional": true, - "requires": { - "end-of-stream": "^1.0.0", - "inherits": "^2.0.1", - "readable-stream": "^2.0.0", - "stream-shift": "^1.0.0" - }, - "dependencies": { - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "optional": true - }, - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "optional": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "optional": true - } - } - }, - "ecdsa-sig-formatter": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", - "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", - "requires": { - "safe-buffer": "^5.0.1" - } - }, - "ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" - }, - "encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" - }, - "encoding": { - "version": "0.1.12", - "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.12.tgz", - "integrity": "sha1-U4tm8+5izRq1HsMjgp0flIDHS+s=", - "requires": { - "iconv-lite": "~0.4.13" - } - }, - "end-of-stream": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", - "optional": true, - "requires": { - "once": "^1.4.0" - } - }, - "ent": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/ent/-/ent-2.2.0.tgz", - "integrity": "sha1-6WQhkyWiHQX0RGai9obtbOX13R0=", - "optional": true - }, - "es-abstract": { - "version": "1.17.5", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.5.tgz", - "integrity": "sha512-BR9auzDbySxOcfog0tLECW8l28eRGpDpU3Dm3Hp4q/N+VtLTmyj4EUN088XZWQDW/hzj6sYRDXeOFsaAODKvpg==", - "requires": { - "es-to-primitive": "^1.2.1", - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.1", - "is-callable": "^1.1.5", - "is-regex": "^1.0.5", - "object-inspect": "^1.7.0", - "object-keys": "^1.1.1", - "object.assign": "^4.1.0", - "string.prototype.trimleft": "^2.1.1", - "string.prototype.trimright": "^2.1.1" - } - }, - "es-get-iterator": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.0.tgz", - "integrity": "sha512-UfrmHuWQlNMTs35e1ypnvikg6jCz3SK8v8ImvmDsh36fCVUR1MqoFDiyn0/k52C8NqO3YsO8Oe0azeesNuqSsQ==", - "optional": true, - "requires": { - "es-abstract": "^1.17.4", - "has-symbols": "^1.0.1", - "is-arguments": "^1.0.4", - "is-map": "^2.0.1", - "is-set": "^2.0.1", - "is-string": "^1.0.5", - "isarray": "^2.0.5" - } - }, - "es-to-primitive": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", - "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", - "requires": { - "is-callable": "^1.1.4", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.2" - } - }, - "escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" - }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", - "dev": true - }, - "esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true - }, - "etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" - }, - "event-target-shim": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", - "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", - "optional": true - }, - "express": { - "version": "4.17.1", - "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", - "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==", - "requires": { - "accepts": "~1.3.7", - "array-flatten": "1.1.1", - "body-parser": "1.19.0", - "content-disposition": "0.5.3", - "content-type": "~1.0.4", - "cookie": "0.4.0", - "cookie-signature": "1.0.6", - "debug": "2.6.9", - "depd": "~1.1.2", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "finalhandler": "~1.1.2", - "fresh": "0.5.2", - "merge-descriptors": "1.0.1", - "methods": "~1.1.2", - "on-finished": "~2.3.0", - "parseurl": "~1.3.3", - "path-to-regexp": "0.1.7", - "proxy-addr": "~2.0.5", - "qs": "6.7.0", - "range-parser": "~1.2.1", - "safe-buffer": "5.1.2", - "send": "0.17.1", - "serve-static": "1.14.1", - "setprototypeof": "1.1.1", - "statuses": "~1.5.0", - "type-is": "~1.6.18", - "utils-merge": "1.0.1", - "vary": "~1.1.2" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - } - } - }, - "extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", - "optional": true - }, - "fast-text-encoding": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/fast-text-encoding/-/fast-text-encoding-1.0.2.tgz", - "integrity": "sha512-5rQdinSsycpzvAoHga2EDn+LRX1d5xLFsuNG0Kg61JrAT/tASXcLL0nf/33v+sAxlQcfYmWbTURa1mmAf55jGw==", - "optional": true - }, - "faye-websocket": { - "version": "0.11.3", - "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.3.tgz", - "integrity": "sha512-D2y4bovYpzziGgbHYtGCMjlJM36vAl/y+xUyn1C+FVx8szd1E+86KwVw6XvYSzOP8iMpm1X0I4xJD+QtUb36OA==", - "requires": { - "websocket-driver": ">=0.5.1" - } - }, - "finalhandler": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", - "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", - "requires": { - "debug": "2.6.9", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "on-finished": "~2.3.0", - "parseurl": "~1.3.3", - "statuses": "~1.5.0", - "unpipe": "~1.0.0" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - } - } - }, - "firebase": { - "version": "7.15.0", - "resolved": "https://registry.npmjs.org/firebase/-/firebase-7.15.0.tgz", - "integrity": "sha512-0t8w/TT+230/n/XWBw9jGMApCkIEb5K1b+6t4R+SAMOZHMJZvoAwMcwgQXoYeUBFOJOQpgDhIZK8PzApq4iUXw==", - "requires": { - "@firebase/analytics": "0.3.6", - "@firebase/app": "0.6.5", - "@firebase/app-types": "0.6.1", - "@firebase/auth": "0.14.6", - "@firebase/database": "0.6.4", - "@firebase/firestore": "1.15.0", - "@firebase/functions": "0.4.45", - "@firebase/installations": "0.4.11", - "@firebase/messaging": "0.6.17", - "@firebase/performance": "0.3.6", - "@firebase/polyfill": "0.3.36", - "@firebase/remote-config": "0.1.22", - "@firebase/storage": "0.3.35", - "@firebase/util": "0.2.48" - }, - "dependencies": { - "@firebase/component": { - "version": "0.1.13", - "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.13.tgz", - "integrity": "sha512-DuSIM96NQkE3Yo77IOa5BWw8VBdvCR5cbMLNiFT4X3dTU15Dm0zHjncQHt/6rQpABGNYWAfOCJmSU1v6vc3DFA==", - "requires": { - "@firebase/util": "0.2.48", - "tslib": "1.11.1" - } - }, - "@firebase/database": { - "version": "0.6.4", - "resolved": "https://registry.npmjs.org/@firebase/database/-/database-0.6.4.tgz", - "integrity": "sha512-m3jaElEEXhr3a9D+M/kbDuRCQG5EmrnSqyEq7iNk3s5ankIrALid0AYm2RZF764F/DIeMFtAzng4EyyEqsaQlQ==", - "requires": { - "@firebase/auth-interop-types": "0.1.5", - "@firebase/component": "0.1.13", - "@firebase/database-types": "0.5.1", - "@firebase/logger": "0.2.5", - "@firebase/util": "0.2.48", - "faye-websocket": "0.11.3", - "tslib": "1.11.1" - } - }, - "@firebase/logger": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.2.5.tgz", - "integrity": "sha512-qqw3m0tWs/qrg7axTZG/QZq24DIMdSY6dGoWuBn08ddq7+GLF5HiqkRj71XznYeUUbfRq5W9C/PSFnN4JxX+WA==" - }, - "@firebase/util": { - "version": "0.2.48", - "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.2.48.tgz", - "integrity": "sha512-6Wzq6IBF//3mrMTmTQ+JmceM0PMQpxV2GVfXhZn/4sMMkkhB0MA908nPDnatoHwUKyWE3BMw+uTLkyBnkuTu5A==", - "requires": { - "tslib": "1.11.1" - } - } - } - }, - "firebase-admin": { - "version": "8.12.1", - "resolved": "https://registry.npmjs.org/firebase-admin/-/firebase-admin-8.12.1.tgz", - "integrity": "sha512-DZ4Q7QQJYaO2BhnhZLrhL+mGRTCLS5WrxjbJtuKGmbKRBepwMhx++EQA5yhnGnIXgDHnp5SrZnVKygNdXtH8BQ==", - "requires": { - "@firebase/database": "^0.6.0", - "@google-cloud/firestore": "^3.0.0", - "@google-cloud/storage": "^4.1.2", - "@types/node": "^8.10.59", - "dicer": "^0.3.0", - "jsonwebtoken": "8.1.0", - "node-forge": "0.7.4" - } - }, - "firebase-functions": { - "version": "3.6.1", - "resolved": "https://registry.npmjs.org/firebase-functions/-/firebase-functions-3.6.1.tgz", - "integrity": "sha512-CBvlDEoFgsdm10PTHs7gRd5xBmhp+eqCqgsyqKbzmdbU3J8RYqtBWoHm2O31gjtZv6MyOWvS3oFITShzBulylQ==", - "requires": { - "@types/express": "^4.17.3", - "cors": "^2.8.5", - "express": "^4.17.1", - "jsonwebtoken": "^8.5.1", - "lodash": "^4.17.14" - }, - "dependencies": { - "jsonwebtoken": { - "version": "8.5.1", - "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz", - "integrity": "sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==", - "requires": { - "jws": "^3.2.2", - "lodash.includes": "^4.3.0", - "lodash.isboolean": "^3.0.3", - "lodash.isinteger": "^4.0.4", - "lodash.isnumber": "^3.0.3", - "lodash.isplainobject": "^4.0.6", - "lodash.isstring": "^4.0.1", - "lodash.once": "^4.0.0", - "ms": "^2.1.1", - "semver": "^5.6.0" - } - }, - "jwa": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", - "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", - "requires": { - "buffer-equal-constant-time": "1.0.1", - "ecdsa-sig-formatter": "1.0.11", - "safe-buffer": "^5.0.1" - } - }, - "jws": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", - "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", - "requires": { - "jwa": "^1.4.1", - "safe-buffer": "^5.0.1" - } - }, - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" - } - } - }, - "firebase-functions-helper": { - "version": "0.7.5", - "resolved": "https://registry.npmjs.org/firebase-functions-helper/-/firebase-functions-helper-0.7.5.tgz", - "integrity": "sha512-iZripWqrsbBeFq/UP8ycuYl7J7fuQVsjMAnvV3tahBTEPleSk44nUJDo6XRxzQos3gNvPupw2+slAXbrHVtBkg==", - "requires": { - "chai": "^4.2.0", - "firebase-admin": "^8.9.0" - } - }, - "firebase-functions-test": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/firebase-functions-test/-/firebase-functions-test-0.2.1.tgz", - "integrity": "sha512-+ZaNrDoRVy0ar4NGtrYbqVTsnitL3/Ud5yC7ElZUkX3956j+AzPCcrsCfa+5GJnpnVODXkMKpw9AySFJ/12nvA==", - "dev": true, - "requires": { - "@types/lodash": "^4.14.104", - "lodash": "^4.17.5" - } - }, - "foreach": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.5.tgz", - "integrity": "sha1-C+4AUBiusmDQo6865ljdATbsG5k=", - "optional": true - }, - "forwarded": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", - "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=" - }, - "fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" - }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "dev": true - }, - "function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" - }, - "functional-red-black-tree": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", - "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", - "optional": true - }, - "gaxios": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-3.0.3.tgz", - "integrity": "sha512-PkzQludeIFhd535/yucALT/Wxyj/y2zLyrMwPcJmnLHDugmV49NvAi/vb+VUq/eWztATZCNcb8ue+ywPG+oLuw==", - "optional": true, - "requires": { - "abort-controller": "^3.0.0", - "extend": "^3.0.2", - "https-proxy-agent": "^5.0.0", - "is-stream": "^2.0.0", - "node-fetch": "^2.3.0" - } - }, - "gcp-metadata": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-4.1.0.tgz", - "integrity": "sha512-r57SV28+olVsflPlKyVig3Muo/VDlcsObMtvDGOEtEJXj+DDE8bEl0coIkXh//hbkSDTvo+f5lbihZOndYXQQQ==", - "optional": true, - "requires": { - "gaxios": "^3.0.0", - "json-bigint": "^0.3.0" - } - }, - "gcs-resumable-upload": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/gcs-resumable-upload/-/gcs-resumable-upload-2.3.3.tgz", - "integrity": "sha512-sf896I5CC/1AxeaGfSFg3vKMjUq/r+A3bscmVzZm10CElyRanN0XwPu/MxeIO4LSP+9uF6yKzXvNsaTsMXUG6Q==", - "optional": true, - "requires": { - "abort-controller": "^3.0.0", - "configstore": "^5.0.0", - "gaxios": "^2.0.0", - "google-auth-library": "^5.0.0", - "pumpify": "^2.0.0", - "stream-events": "^1.0.4" - }, - "dependencies": { - "gaxios": { - "version": "2.3.4", - "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-2.3.4.tgz", - "integrity": "sha512-US8UMj8C5pRnao3Zykc4AAVr+cffoNKRTg9Rsf2GiuZCW69vgJj38VK2PzlPuQU73FZ/nTk9/Av6/JGcE1N9vA==", - "optional": true, - "requires": { - "abort-controller": "^3.0.0", - "extend": "^3.0.2", - "https-proxy-agent": "^5.0.0", - "is-stream": "^2.0.0", - "node-fetch": "^2.3.0" - } - } - } - }, - "get-func-name": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", - "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=" - }, - "glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "google-auth-library": { - "version": "5.10.1", - "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-5.10.1.tgz", - "integrity": "sha512-rOlaok5vlpV9rSiUu5EpR0vVpc+PhN62oF4RyX/6++DG1VsaulAFEMlDYBLjJDDPI6OcNOCGAKy9UVB/3NIDXg==", - "optional": true, - "requires": { - "arrify": "^2.0.0", - "base64-js": "^1.3.0", - "ecdsa-sig-formatter": "^1.0.11", - "fast-text-encoding": "^1.0.0", - "gaxios": "^2.1.0", - "gcp-metadata": "^3.4.0", - "gtoken": "^4.1.0", - "jws": "^4.0.0", - "lru-cache": "^5.0.0" - }, - "dependencies": { - "gaxios": { - "version": "2.3.4", - "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-2.3.4.tgz", - "integrity": "sha512-US8UMj8C5pRnao3Zykc4AAVr+cffoNKRTg9Rsf2GiuZCW69vgJj38VK2PzlPuQU73FZ/nTk9/Av6/JGcE1N9vA==", - "optional": true, - "requires": { - "abort-controller": "^3.0.0", - "extend": "^3.0.2", - "https-proxy-agent": "^5.0.0", - "is-stream": "^2.0.0", - "node-fetch": "^2.3.0" - } - }, - "gcp-metadata": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-3.5.0.tgz", - "integrity": "sha512-ZQf+DLZ5aKcRpLzYUyBS3yo3N0JSa82lNDO8rj3nMSlovLcz2riKFBsYgDzeXcv75oo5eqB2lx+B14UvPoCRnA==", - "optional": true, - "requires": { - "gaxios": "^2.1.0", - "json-bigint": "^0.3.0" - } - }, - "google-p12-pem": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-2.0.4.tgz", - "integrity": "sha512-S4blHBQWZRnEW44OcR7TL9WR+QCqByRvhNDZ/uuQfpxywfupikf/miba8js1jZi6ZOGv5slgSuoshCWh6EMDzg==", - "optional": true, - "requires": { - "node-forge": "^0.9.0" - } - }, - "gtoken": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-4.1.4.tgz", - "integrity": "sha512-VxirzD0SWoFUo5p8RDP8Jt2AGyOmyYcT/pOUgDKJCK+iSw0TMqwrVfY37RXTNmoKwrzmDHSk0GMT9FsgVmnVSA==", - "optional": true, - "requires": { - "gaxios": "^2.1.0", - "google-p12-pem": "^2.0.0", - "jws": "^4.0.0", - "mime": "^2.2.0" - } - }, - "node-forge": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.9.1.tgz", - "integrity": "sha512-G6RlQt5Sb4GMBzXvhfkeFmbqR6MzhtnT7VTHuLadjkii3rdYHNdw0m8zA4BTxVIh68FicCQ2NSUANpsqkr9jvQ==", - "optional": true - } - } - }, - "google-gax": { - "version": "1.15.3", - "resolved": "https://registry.npmjs.org/google-gax/-/google-gax-1.15.3.tgz", - "integrity": "sha512-3JKJCRumNm3x2EksUTw4P1Rad43FTpqrtW9jzpf3xSMYXx+ogaqTM1vGo7VixHB4xkAyATXVIa3OcNSh8H9zsQ==", - "optional": true, - "requires": { - "@grpc/grpc-js": "~1.0.3", - "@grpc/proto-loader": "^0.5.1", - "@types/fs-extra": "^8.0.1", - "@types/long": "^4.0.0", - "abort-controller": "^3.0.0", - "duplexify": "^3.6.0", - "google-auth-library": "^5.0.0", - "is-stream-ended": "^0.1.4", - "lodash.at": "^4.6.0", - "lodash.has": "^4.5.2", - "node-fetch": "^2.6.0", - "protobufjs": "^6.8.9", - "retry-request": "^4.0.0", - "semver": "^6.0.0", - "walkdir": "^0.4.0" - } - }, - "google-p12-pem": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-3.0.1.tgz", - "integrity": "sha512-VlQgtozgNVVVcYTXS36eQz4PXPt9gIPqLOhHN0QiV6W6h4qSCNVKPtKC5INtJsaHHF2r7+nOIa26MJeJMTaZEQ==", - "optional": true, - "requires": { - "node-forge": "^0.9.0" - }, - "dependencies": { - "node-forge": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.9.1.tgz", - "integrity": "sha512-G6RlQt5Sb4GMBzXvhfkeFmbqR6MzhtnT7VTHuLadjkii3rdYHNdw0m8zA4BTxVIh68FicCQ2NSUANpsqkr9jvQ==", - "optional": true - } - } - }, - "graceful-fs": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", - "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", - "optional": true - }, - "gtoken": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-5.0.1.tgz", - "integrity": "sha512-33w4FNDkUcyIOq/TqyC+drnKdI4PdXmWp9lZzssyEQKuvu9ZFN3KttaSnDKo52U3E51oujVGop93mKxmqO8HHg==", - "optional": true, - "requires": { - "gaxios": "^3.0.0", - "google-p12-pem": "^3.0.0", - "jws": "^4.0.0", - "mime": "^2.2.0" - } - }, - "has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "requires": { - "function-bind": "^1.1.1" - } - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true - }, - "has-symbols": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", - "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==" - }, - "hash-stream-validation": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/hash-stream-validation/-/hash-stream-validation-0.2.3.tgz", - "integrity": "sha512-OEohGLoUOh+bwsIpHpdvhIXFyRGjeLqJbT8Yc5QTZPbRM7LKywagTQxnX/6mghLDOrD9YGz88hy5mLN2eKflYQ==", - "optional": true, - "requires": { - "through2": "^2.0.0" - }, - "dependencies": { - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "optional": true - }, - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "optional": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "optional": true - }, - "through2": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", - "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", - "optional": true, - "requires": { - "readable-stream": "~2.3.6", - "xtend": "~4.0.1" - } - } - } - }, - "http-errors": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.3.tgz", - "integrity": "sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw==", - "requires": { - "depd": "~1.1.2", - "inherits": "2.0.4", - "setprototypeof": "1.1.1", - "statuses": ">= 1.5.0 < 2", - "toidentifier": "1.0.0" - } - }, - "http-parser-js": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.2.tgz", - "integrity": "sha512-opCO9ASqg5Wy2FNo7A0sxy71yGbbkJJXLdgMK04Tcypw9jr2MgWbyubb0+WdmDmGnFflO7fRbqbaihh/ENDlRQ==" - }, - "http-proxy-agent": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", - "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==", - "optional": true, - "requires": { - "@tootallnate/once": "1", - "agent-base": "6", - "debug": "4" - } - }, - "https-proxy-agent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", - "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==", - "optional": true, - "requires": { - "agent-base": "6", - "debug": "4" - } - }, - "iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "requires": { - "safer-buffer": ">= 2.1.2 < 3" - } - }, - "idb": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/idb/-/idb-3.0.2.tgz", - "integrity": "sha512-+FLa/0sTXqyux0o6C+i2lOR0VoS60LU/jzUo5xjfY6+7sEEgy4Gz1O7yFBXvjd7N0NyIGWIRg8DcQSLEG+VSPw==" - }, - "imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", - "optional": true - }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "dev": true, - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, - "ipaddr.js": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" - }, - "is-arguments": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.0.4.tgz", - "integrity": "sha512-xPh0Rmt8NE65sNzvyUmWgI1tz3mKq74lGA0mL8LYZcoIzKOzDh6HmrYm3d18k60nHerC8A9Km8kYu87zfSFnLA==", - "optional": true - }, - "is-bigint": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.0.tgz", - "integrity": "sha512-t5mGUXC/xRheCK431ylNiSkGGpBp8bHENBcENTkDT6ppwPzEVxNGZRvgvmOEfbWkFhA7D2GEuE2mmQTr78sl2g==", - "optional": true - }, - "is-boolean-object": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.0.1.tgz", - "integrity": "sha512-TqZuVwa/sppcrhUCAYkGBk7w0yxfQQnxq28fjkO53tnK9FQXmdwz2JS5+GjsWQ6RByES1K40nI+yDic5c9/aAQ==", - "optional": true - }, - "is-callable": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.5.tgz", - "integrity": "sha512-ESKv5sMCJB2jnHTWZ3O5itG+O128Hsus4K4Qh1h2/cgn2vbgnLSVqfV46AeJA9D5EeeLa9w81KUXMtn34zhX+Q==" - }, - "is-date-object": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz", - "integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==" - }, - "is-expression": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-expression/-/is-expression-4.0.0.tgz", - "integrity": "sha512-zMIXX63sxzG3XrkHkrAPvm/OVZVSCPNkwMHU8oTX7/U3AL78I0QXCEICXUM13BIa8TYGZ68PiTKfQz3yaTNr4A==", - "requires": { - "acorn": "^7.1.1", - "object-assign": "^4.1.1" - } - }, - "is-map": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.1.tgz", - "integrity": "sha512-T/S49scO8plUiAOA2DBTBG3JHpn1yiw0kRp6dgiZ0v2/6twi5eiB0rHtHFH9ZIrvlWc6+4O+m4zg5+Z833aXgw==", - "optional": true - }, - "is-number-object": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.4.tgz", - "integrity": "sha512-zohwelOAur+5uXtk8O3GPQ1eAcu4ZX3UwxQhUlfFFMNpUd83gXgjbhJh6HmB6LUNV/ieOLQuDwJO3dWJosUeMw==", - "optional": true - }, - "is-obj": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", - "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", - "optional": true - }, - "is-promise": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz", - "integrity": "sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==" - }, - "is-regex": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.5.tgz", - "integrity": "sha512-vlKW17SNq44owv5AQR3Cq0bQPEb8+kF3UKZ2fiZNOWtztYE5i0CzCZxFDwO58qAOWtxdBRVO/V5Qin1wjCqFYQ==", - "requires": { - "has": "^1.0.3" - } - }, - "is-set": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.1.tgz", - "integrity": "sha512-eJEzOtVyenDs1TMzSQ3kU3K+E0GUS9sno+F0OBT97xsgcJsF9nXMBtkT9/kut5JEpM7oL7X/0qxR17K3mcwIAA==", - "optional": true - }, - "is-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", - "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==", - "optional": true - }, - "is-stream-ended": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-stream-ended/-/is-stream-ended-0.1.4.tgz", - "integrity": "sha512-xj0XPvmr7bQFTvirqnFr50o0hQIh6ZItDqloxt5aJrR4NQsYeSsyFQERYGCAzfindAcnKjINnwEEgLx4IqVzQw==", - "optional": true - }, - "is-string": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.5.tgz", - "integrity": "sha512-buY6VNRjhQMiF1qWDouloZlQbRhDPCebwxSjxMjxgemYT46YMd2NR0/H+fBhEfWX4A/w9TBJ+ol+okqJKFE6vQ==", - "optional": true - }, - "is-symbol": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz", - "integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==", - "requires": { - "has-symbols": "^1.0.1" - } - }, - "is-typed-array": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.3.tgz", - "integrity": "sha512-BSYUBOK/HJibQ30wWkWold5txYwMUXQct9YHAQJr8fSwvZoiglcqB0pd7vEN23+Tsi9IUEjztdOSzl4qLVYGTQ==", - "optional": true, - "requires": { - "available-typed-arrays": "^1.0.0", - "es-abstract": "^1.17.4", - "foreach": "^2.0.5", - "has-symbols": "^1.0.1" - } - }, - "is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", - "optional": true - }, - "is-weakmap": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.1.tgz", - "integrity": "sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA==", - "optional": true - }, - "is-weakset": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.1.tgz", - "integrity": "sha512-pi4vhbhVHGLxohUw7PhGsueT4vRGFoXhP7+RGN0jKIv9+8PWYCQTqtADngrxOm2g46hoH0+g8uZZBzMrvVGDmw==", - "optional": true - }, - "isarray": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", - "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", - "optional": true - }, - "isomorphic-fetch": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz", - "integrity": "sha1-YRrhrPFPXoH3KVB0coGf6XM1WKk=", - "requires": { - "node-fetch": "^1.0.1", - "whatwg-fetch": ">=0.10.0" - }, - "dependencies": { - "is-stream": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=" - }, - "node-fetch": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-1.7.3.tgz", - "integrity": "sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ==", - "requires": { - "encoding": "^0.1.11", - "is-stream": "^1.0.1" - } - } - } - }, - "js-stringify": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/js-stringify/-/js-stringify-1.0.2.tgz", - "integrity": "sha1-Fzb939lyTyijaCrcYjCufk6Weds=" - }, - "js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true - }, - "js-yaml": { - "version": "3.14.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.0.tgz", - "integrity": "sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A==", - "dev": true, - "requires": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - } - }, - "json-bigint": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-0.3.0.tgz", - "integrity": "sha1-DM2RLEuCcNBfBW+9E4FLU9OCWx4=", - "optional": true, - "requires": { - "bignumber.js": "^7.0.0" - } - }, - "jsonwebtoken": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.1.0.tgz", - "integrity": "sha1-xjl80uX9WD1lwAeoPce7eOaYK4M=", - "requires": { - "jws": "^3.1.4", - "lodash.includes": "^4.3.0", - "lodash.isboolean": "^3.0.3", - "lodash.isinteger": "^4.0.4", - "lodash.isnumber": "^3.0.3", - "lodash.isplainobject": "^4.0.6", - "lodash.isstring": "^4.0.1", - "lodash.once": "^4.0.0", - "ms": "^2.0.0", - "xtend": "^4.0.1" - }, - "dependencies": { - "jwa": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", - "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", - "requires": { - "buffer-equal-constant-time": "1.0.1", - "ecdsa-sig-formatter": "1.0.11", - "safe-buffer": "^5.0.1" - } - }, - "jws": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", - "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", - "requires": { - "jwa": "^1.4.1", - "safe-buffer": "^5.0.1" - } - } - } - }, - "jstransformer": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/jstransformer/-/jstransformer-1.0.0.tgz", - "integrity": "sha1-7Yvwkh4vPx7U1cGkT2hwntJHIsM=", - "requires": { - "is-promise": "^2.0.0", - "promise": "^7.0.1" - } - }, - "jwa": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", - "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", - "optional": true, - "requires": { - "buffer-equal-constant-time": "1.0.1", - "ecdsa-sig-formatter": "1.0.11", - "safe-buffer": "^5.0.1" - } - }, - "jws": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz", - "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==", - "optional": true, - "requires": { - "jwa": "^2.0.0", - "safe-buffer": "^5.0.1" - } - }, - "lodash": { - "version": "4.17.15", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", - "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==" - }, - "lodash.at": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/lodash.at/-/lodash.at-4.6.0.tgz", - "integrity": "sha1-k83OZk8KGZTqM9181A4jr9EbD/g=", - "optional": true - }, - "lodash.camelcase": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", - "integrity": "sha1-soqmKIorn8ZRA1x3EfZathkDMaY=" - }, - "lodash.has": { - "version": "4.5.2", - "resolved": "https://registry.npmjs.org/lodash.has/-/lodash.has-4.5.2.tgz", - "integrity": "sha1-0Z9NwQlQWMzL4rDN9O4P5Ko3yGI=", - "optional": true - }, - "lodash.includes": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", - "integrity": "sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8=" - }, - "lodash.isboolean": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", - "integrity": "sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY=" - }, - "lodash.isinteger": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", - "integrity": "sha1-YZwK89A/iwTDH1iChAt3sRzWg0M=" - }, - "lodash.isnumber": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", - "integrity": "sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w=" - }, - "lodash.isplainobject": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", - "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=" - }, - "lodash.isstring": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", - "integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=" - }, - "lodash.once": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", - "integrity": "sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=" - }, - "long": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", - "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" - }, - "lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "optional": true, - "requires": { - "yallist": "^3.0.2" - } - }, - "make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", - "optional": true, - "requires": { - "semver": "^6.0.0" - } - }, - "media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" - }, - "merge-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" - }, - "methods": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" - }, - "mime": { - "version": "2.4.5", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.5.tgz", - "integrity": "sha512-3hQhEUF027BuxZjQA3s7rIv/7VCQPa27hN9u9g87sEkWaKwQPuXOkVKtOeiyUrnWqTDiOs8Ed2rwg733mB0R5w==", - "optional": true - }, - "mime-db": { - "version": "1.44.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz", - "integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==" - }, - "mime-types": { - "version": "2.1.27", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz", - "integrity": "sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==", - "requires": { - "mime-db": "1.44.0" - } - }, - "mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "optional": true - }, - "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", - "dev": true - }, - "mkdirp": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", - "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", - "dev": true, - "requires": { - "minimist": "^1.2.5" - } - }, - "morgan": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.0.tgz", - "integrity": "sha512-AbegBVI4sh6El+1gNwvD5YIck7nSA36weD7xvIxG4in80j/UoK8AEGaWnnz8v1GxonMCltmlNs5ZKbGvl9b1XQ==", - "requires": { - "basic-auth": "~2.0.1", - "debug": "2.6.9", - "depd": "~2.0.0", - "on-finished": "~2.3.0", - "on-headers": "~1.0.2" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } - }, - "depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - } - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - }, - "negotiator": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", - "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==" - }, - "node-fetch": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.0.tgz", - "integrity": "sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA==" - }, - "node-forge": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.7.4.tgz", - "integrity": "sha512-8Df0906+tq/omxuCZD6PqhPaQDYuyJ1d+VITgxoIA8zvQd1ru+nMJcDChHH324MWitIgbVkAkQoGEEVJNpn/PA==" - }, - "object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" - }, - "object-inspect": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.7.0.tgz", - "integrity": "sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw==" - }, - "object-is": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.2.tgz", - "integrity": "sha512-5lHCz+0uufF6wZ7CRFWJN3hp8Jqblpgve06U5CMQ3f//6iDjPr2PEo9MWCjEssDsa+UZEL4PkFpr+BMop6aKzQ==", - "optional": true, - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.5" - } - }, - "object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==" - }, - "object.assign": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", - "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", - "requires": { - "define-properties": "^1.1.2", - "function-bind": "^1.1.1", - "has-symbols": "^1.0.0", - "object-keys": "^1.0.11" - } - }, - "on-finished": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", - "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", - "requires": { - "ee-first": "1.1.1" - } - }, - "on-headers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", - "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==" - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "requires": { - "wrappy": "1" - } - }, - "onetime": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.0.tgz", - "integrity": "sha512-5NcSkPHhwTVFIQN+TUqXoS5+dlElHXdpAWu9I0HP20YOtIi+aZ0Ct82jdlILDxjLEAWwvm+qj1m6aEtsDVmm6Q==", - "optional": true, - "requires": { - "mimic-fn": "^2.1.0" - } - }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "optional": true, - "requires": { - "p-try": "^2.0.0" - } - }, - "p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "optional": true - }, - "parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" - }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "dev": true - }, - "path-parse": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", - "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==" - }, - "path-to-regexp": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" - }, - "pathval": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.0.tgz", - "integrity": "sha1-uULm1L3mUwBe9rcTYd74cn0GReA=" - }, - "process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "optional": true - }, - "promise": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", - "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==", - "requires": { - "asap": "~2.0.3" - } - }, - "promise-polyfill": { - "version": "8.1.3", - "resolved": "https://registry.npmjs.org/promise-polyfill/-/promise-polyfill-8.1.3.tgz", - "integrity": "sha512-MG5r82wBzh7pSKDRa9y+vllNHz3e3d4CNj1PQE4BQYxLme0gKYYBm9YENq+UkEikyZ0XbiGWxYlVw3Rl9O/U8g==" - }, - "protobufjs": { - "version": "6.9.0", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.9.0.tgz", - "integrity": "sha512-LlGVfEWDXoI/STstRDdZZKb/qusoAWUnmLg9R8OLSO473mBLWHowx8clbX5/+mKDEI+v7GzjoK9tRPZMMcoTrg==", - "requires": { - "@protobufjs/aspromise": "^1.1.2", - "@protobufjs/base64": "^1.1.2", - "@protobufjs/codegen": "^2.0.4", - "@protobufjs/eventemitter": "^1.1.0", - "@protobufjs/fetch": "^1.1.0", - "@protobufjs/float": "^1.0.2", - "@protobufjs/inquire": "^1.1.0", - "@protobufjs/path": "^1.1.2", - "@protobufjs/pool": "^1.1.0", - "@protobufjs/utf8": "^1.1.0", - "@types/long": "^4.0.1", - "@types/node": "^13.7.0", - "long": "^4.0.0" - }, - "dependencies": { - "@types/node": { - "version": "13.13.9", - "resolved": "https://registry.npmjs.org/@types/node/-/node-13.13.9.tgz", - "integrity": "sha512-EPZBIGed5gNnfWCiwEIwTE2Jdg4813odnG8iNPMQGrqVxrI+wL68SPtPeCX+ZxGBaA6pKAVc6jaKgP/Q0QzfdQ==" - } - } - }, - "proxy-addr": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.6.tgz", - "integrity": "sha512-dh/frvCBVmSsDYzw6n926jv974gddhkFPfiN8hPOi30Wax25QZyZEGveluCgliBnqmuM+UJmBErbAUFIoDbjOw==", - "requires": { - "forwarded": "~0.1.2", - "ipaddr.js": "1.9.1" - } - }, - "pug": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pug/-/pug-3.0.0.tgz", - "integrity": "sha512-inmsJyFBSHZaiGLaguoFgJGViX0If6AcfcElimvwj9perqjDpUpw79UIEDZbWFmoGVidh08aoE+e8tVkjVJPCw==", - "requires": { - "pug-code-gen": "^3.0.0", - "pug-filters": "^4.0.0", - "pug-lexer": "^5.0.0", - "pug-linker": "^4.0.0", - "pug-load": "^3.0.0", - "pug-parser": "^6.0.0", - "pug-runtime": "^3.0.0", - "pug-strip-comments": "^2.0.0" - } - }, - "pug-attrs": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pug-attrs/-/pug-attrs-3.0.0.tgz", - "integrity": "sha512-azINV9dUtzPMFQktvTXciNAfAuVh/L/JCl0vtPCwvOA21uZrC08K/UnmrL+SXGEVc1FwzjW62+xw5S/uaLj6cA==", - "requires": { - "constantinople": "^4.0.1", - "js-stringify": "^1.0.2", - "pug-runtime": "^3.0.0" - } - }, - "pug-code-gen": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/pug-code-gen/-/pug-code-gen-3.0.1.tgz", - "integrity": "sha512-xJIGvmXTQlkJllq6hqxxjRWcay2F9CU69TuAuiVZgHK0afOhG5txrQOcZyaPHBvSWCU/QQOqEp5XCH94rRZpBQ==", - "requires": { - "constantinople": "^4.0.1", - "doctypes": "^1.1.0", - "js-stringify": "^1.0.2", - "pug-attrs": "^3.0.0", - "pug-error": "^2.0.0", - "pug-runtime": "^3.0.0", - "void-elements": "^3.1.0", - "with": "^7.0.0" - } - }, - "pug-error": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/pug-error/-/pug-error-2.0.0.tgz", - "integrity": "sha512-sjiUsi9M4RAGHktC1drQfCr5C5eriu24Lfbt4s+7SykztEOwVZtbFk1RRq0tzLxcMxMYTBR+zMQaG07J/btayQ==" - }, - "pug-filters": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/pug-filters/-/pug-filters-4.0.0.tgz", - "integrity": "sha512-yeNFtq5Yxmfz0f9z2rMXGw/8/4i1cCFecw/Q7+D0V2DdtII5UvqE12VaZ2AY7ri6o5RNXiweGH79OCq+2RQU4A==", - "requires": { - "constantinople": "^4.0.1", - "jstransformer": "1.0.0", - "pug-error": "^2.0.0", - "pug-walk": "^2.0.0", - "resolve": "^1.15.1" - } - }, - "pug-lexer": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/pug-lexer/-/pug-lexer-5.0.0.tgz", - "integrity": "sha512-52xMk8nNpuyQ/M2wjZBN5gXQLIylaGkAoTk5Y1pBhVqaopaoj8Z0iVzpbFZAqitL4RHNVDZRnJDsqEYe99Ti0A==", - "requires": { - "character-parser": "^2.2.0", - "is-expression": "^4.0.0", - "pug-error": "^2.0.0" - } - }, - "pug-linker": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/pug-linker/-/pug-linker-4.0.0.tgz", - "integrity": "sha512-gjD1yzp0yxbQqnzBAdlhbgoJL5qIFJw78juN1NpTLt/mfPJ5VgC4BvkoD3G23qKzJtIIXBbcCt6FioLSFLOHdw==", - "requires": { - "pug-error": "^2.0.0", - "pug-walk": "^2.0.0" - } - }, - "pug-load": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pug-load/-/pug-load-3.0.0.tgz", - "integrity": "sha512-OCjTEnhLWZBvS4zni/WUMjH2YSUosnsmjGBB1An7CsKQarYSWQ0GCVyd4eQPMFJqZ8w9xgs01QdiZXKVjk92EQ==", - "requires": { - "object-assign": "^4.1.1", - "pug-walk": "^2.0.0" - } - }, - "pug-parser": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/pug-parser/-/pug-parser-6.0.0.tgz", - "integrity": "sha512-ukiYM/9cH6Cml+AOl5kETtM9NR3WulyVP2y4HOU45DyMim1IeP/OOiyEWRr6qk5I5klpsBnbuHpwKmTx6WURnw==", - "requires": { - "pug-error": "^2.0.0", - "token-stream": "1.0.0" - } - }, - "pug-runtime": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pug-runtime/-/pug-runtime-3.0.0.tgz", - "integrity": "sha512-GoEPcmQNnaTsePEdVA05bDpY+Op5VLHKayg08AQiqJBWU/yIaywEYv7TetC5dEQS3fzBBoyb2InDcZEg3mPTIA==" - }, - "pug-strip-comments": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/pug-strip-comments/-/pug-strip-comments-2.0.0.tgz", - "integrity": "sha512-zo8DsDpH7eTkPHCXFeAk1xZXJbyoTfdPlNR0bK7rpOMuhBYb0f5qUVCO1xlsitYd3w5FQTK7zpNVKb3rZoUrrQ==", - "requires": { - "pug-error": "^2.0.0" - } - }, - "pug-walk": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/pug-walk/-/pug-walk-2.0.0.tgz", - "integrity": "sha512-yYELe9Q5q9IQhuvqsZNwA5hfPkMJ8u92bQLIMcsMxf/VADjNtEYptU+inlufAFYcWdHlwNfZOEnOOQrZrcyJCQ==" - }, - "pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "optional": true, - "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "pumpify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-2.0.1.tgz", - "integrity": "sha512-m7KOje7jZxrmutanlkS1daj1dS6z6BgslzOXmcSEpIlCxM3VJH7lG5QLeck/6hgF6F4crFf01UtQmNsJfweTAw==", - "optional": true, - "requires": { - "duplexify": "^4.1.1", - "inherits": "^2.0.3", - "pump": "^3.0.0" - }, - "dependencies": { - "duplexify": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.1.tgz", - "integrity": "sha512-DY3xVEmVHTv1wSzKNbwoU6nVjzI369Y6sPoqfYr0/xlx3IdX2n94xIszTcjPO8W8ZIv0Wb0PXNcjuZyT4wiICA==", - "optional": true, - "requires": { - "end-of-stream": "^1.4.1", - "inherits": "^2.0.3", - "readable-stream": "^3.1.1", - "stream-shift": "^1.0.0" - } - } - } - }, - "qs": { - "version": "6.7.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", - "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==" - }, - "range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" - }, - "raw-body": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", - "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", - "requires": { - "bytes": "3.1.0", - "http-errors": "1.7.2", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" - }, - "dependencies": { - "http-errors": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", - "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", - "requires": { - "depd": "~1.1.2", - "inherits": "2.0.3", - "setprototypeof": "1.1.1", - "statuses": ">= 1.5.0 < 2", - "toidentifier": "1.0.0" - } - }, - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" - } - } - }, - "readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "optional": true, - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - }, - "regexp.prototype.flags": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.3.0.tgz", - "integrity": "sha512-2+Q0C5g951OlYlJz6yu5/M33IcsESLlLfsyIaLJaG4FA2r4yP8MvVMJUUP/fVBkSpbbbZlS5gynbEWLipiiXiQ==", - "optional": true, - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.0-next.1" - } - }, - "resolve": { - "version": "1.17.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz", - "integrity": "sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==", - "requires": { - "path-parse": "^1.0.6" - } - }, - "retry-request": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/retry-request/-/retry-request-4.1.1.tgz", - "integrity": "sha512-BINDzVtLI2BDukjWmjAIRZ0oglnCAkpP2vQjM3jdLhmT62h0xnQgciPwBRDAvHqpkPT2Wo1XuUyLyn6nbGrZQQ==", - "optional": true, - "requires": { - "debug": "^4.1.1", - "through2": "^3.0.1" - } - }, - "safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" - }, - "safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" - }, - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" - }, - "send": { - "version": "0.17.1", - "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", - "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==", - "requires": { - "debug": "2.6.9", - "depd": "~1.1.2", - "destroy": "~1.0.4", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "~1.7.2", - "mime": "1.6.0", - "ms": "2.1.1", - "on-finished": "~2.3.0", - "range-parser": "~1.2.1", - "statuses": "~1.5.0" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - }, - "dependencies": { - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - } - } - }, - "mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" - }, - "ms": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" - } - } - }, - "serve-static": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz", - "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==", - "requires": { - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", - "send": "0.17.1" - } - }, - "setprototypeof": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", - "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" - }, - "side-channel": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.2.tgz", - "integrity": "sha512-7rL9YlPHg7Ancea1S96Pa8/QWb4BtXL/TZvS6B8XFetGBeuhAsfmUspK6DokBeZ64+Kj9TCNRD/30pVz1BvQNA==", - "optional": true, - "requires": { - "es-abstract": "^1.17.0-next.1", - "object-inspect": "^1.7.0" - } - }, - "signal-exit": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", - "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==", - "optional": true - }, - "snakeize": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/snakeize/-/snakeize-0.1.0.tgz", - "integrity": "sha1-EMCI2LWOsHazIpu1oE4jLOEmQi0=", - "optional": true - }, - "sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", - "dev": true - }, - "statuses": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", - "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" - }, - "stream-events": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/stream-events/-/stream-events-1.0.5.tgz", - "integrity": "sha512-E1GUzBSgvct8Jsb3v2X15pjzN1tYebtbLaMg+eBOUOAxgbLoSbT2NS91ckc5lJD1KfLjId+jXJRgo0qnV5Nerg==", - "optional": true, - "requires": { - "stubs": "^3.0.0" - } - }, - "stream-shift": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz", - "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==", - "optional": true - }, - "streamsearch": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-0.1.2.tgz", - "integrity": "sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo=" - }, - "string.prototype.trimend": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.1.tgz", - "integrity": "sha512-LRPxFUaTtpqYsTeNKaFOw3R4bxIzWOnbQ837QfBylo8jIxtcbK/A/sMV7Q+OAV/vWo+7s25pOE10KYSjaSO06g==", - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.5" - } - }, - "string.prototype.trimleft": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/string.prototype.trimleft/-/string.prototype.trimleft-2.1.2.tgz", - "integrity": "sha512-gCA0tza1JBvqr3bfAIFJGqfdRTyPae82+KTnm3coDXkZN9wnuW3HjGgN386D7hfv5CHQYCI022/rJPVlqXyHSw==", - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.5", - "string.prototype.trimstart": "^1.0.0" - } - }, - "string.prototype.trimright": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/string.prototype.trimright/-/string.prototype.trimright-2.1.2.tgz", - "integrity": "sha512-ZNRQ7sY3KroTaYjRS6EbNiiHrOkjihL9aQE/8gfQ4DtAC/aEBRHFJa44OmoWxGGqXuJlfKkZW4WcXErGr+9ZFg==", - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.5", - "string.prototype.trimend": "^1.0.0" - } - }, - "string.prototype.trimstart": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.1.tgz", - "integrity": "sha512-XxZn+QpvrBI1FOcg6dIpxUPgWCPuNXvMD72aaRaUQv1eD4e/Qy8i/hFTe0BUmD60p/QA6bh1avmuPTfNjqVWRw==", - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.5" - } - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "optional": true, - "requires": { - "safe-buffer": "~5.1.0" - }, - "dependencies": { - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "optional": true - } - } - }, - "stubs": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/stubs/-/stubs-3.0.0.tgz", - "integrity": "sha1-6NK6H6nJBXAwPAMLaQD31fiavls=", - "optional": true - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - }, - "teeny-request": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-6.0.3.tgz", - "integrity": "sha512-TZG/dfd2r6yeji19es1cUIwAlVD8y+/svB1kAC2Y0bjEyysrfbO8EZvJBRwIE6WkwmUoB7uvWLwTIhJbMXZ1Dw==", - "optional": true, - "requires": { - "http-proxy-agent": "^4.0.0", - "https-proxy-agent": "^5.0.0", - "node-fetch": "^2.2.0", - "stream-events": "^1.0.5", - "uuid": "^7.0.0" - } - }, - "through2": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/through2/-/through2-3.0.1.tgz", - "integrity": "sha512-M96dvTalPT3YbYLaKaCuwu+j06D/8Jfib0o/PxbVt6Amhv3dUAtW6rTV1jPgJSBG83I/e04Y6xkVdVhSRhi0ww==", - "optional": true, - "requires": { - "readable-stream": "2 || 3" - } - }, - "to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=" - }, - "toidentifier": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", - "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==" - }, - "token-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/token-stream/-/token-stream-1.0.0.tgz", - "integrity": "sha1-zCAOqyYT9BZtJ/+a/HylbUnfbrQ=" - }, - "tslib": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.11.1.tgz", - "integrity": "sha512-aZW88SY8kQbU7gpV19lN24LtXh/yD4ZZg6qieAJDDg+YBsJcSmLGK9QpnUjAKVG/xefmvJGd1WUmfpT/g6AJGA==" - }, - "tslint": { - "version": "5.20.1", - "resolved": "https://registry.npmjs.org/tslint/-/tslint-5.20.1.tgz", - "integrity": "sha512-EcMxhzCFt8k+/UP5r8waCf/lzmeSyVlqxqMEDQE7rWYiQky8KpIBz1JAoYXfROHrPZ1XXd43q8yQnULOLiBRQg==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.0.0", - "builtin-modules": "^1.1.1", - "chalk": "^2.3.0", - "commander": "^2.12.1", - "diff": "^4.0.1", - "glob": "^7.1.1", - "js-yaml": "^3.13.1", - "minimatch": "^3.0.4", - "mkdirp": "^0.5.1", - "resolve": "^1.3.2", - "semver": "^5.3.0", - "tslib": "^1.8.0", - "tsutils": "^2.29.0" - }, - "dependencies": { - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true - } - } - }, - "tsutils": { - "version": "2.29.0", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.29.0.tgz", - "integrity": "sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA==", - "dev": true, - "requires": { - "tslib": "^1.8.1" - } - }, - "type-detect": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", - "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==" - }, - "type-is": { - "version": "1.6.18", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", - "requires": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" - } - }, - "typedarray": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", - "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", - "optional": true - }, - "typedarray-to-buffer": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", - "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", - "optional": true, - "requires": { - "is-typedarray": "^1.0.0" - } - }, - "typescript": { - "version": "3.9.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.3.tgz", - "integrity": "sha512-D/wqnB2xzNFIcoBG9FG8cXRDjiqSTbG2wd8DMZeQyJlP1vfTkIxH4GKveWaEBYySKIg+USu+E+EDIR47SqnaMQ==", - "dev": true - }, - "unique-string": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz", - "integrity": "sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==", - "optional": true, - "requires": { - "crypto-random-string": "^2.0.0" - } - }, - "unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" - }, - "util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", - "optional": true - }, - "utils-merge": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" - }, - "uuid": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-7.0.3.tgz", - "integrity": "sha512-DPSke0pXhTZgoF/d+WSt2QaKMCFSfx7QegxEWT+JOuHF5aWrKEn0G+ztjuJg/gG8/ItK+rbPCD/yNv8yyih6Cg==", - "optional": true - }, - "vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" - }, - "void-elements": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz", - "integrity": "sha1-YU9/v42AHwu18GYfWy9XhXUOTwk=" - }, - "walkdir": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/walkdir/-/walkdir-0.4.1.tgz", - "integrity": "sha512-3eBwRyEln6E1MSzcxcVpQIhRG8Q1jLvEqRmCZqS3dsfXEDR/AhOF4d+jHg1qvDCpYaVRZjENPQyrVxAkQqxPgQ==", - "optional": true - }, - "websocket-driver": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", - "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", - "requires": { - "http-parser-js": ">=0.5.1", - "safe-buffer": ">=5.1.0", - "websocket-extensions": ">=0.1.1" - } - }, - "websocket-extensions": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.3.tgz", - "integrity": "sha512-nqHUnMXmBzT0w570r2JpJxfiSD1IzoI+HGVdd3aZ0yNi3ngvQ4jv1dtHt5VGxfI2yj5yqImPhOK4vmIh2xMbGg==" - }, - "whatwg-fetch": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.0.0.tgz", - "integrity": "sha512-9GSJUgz1D4MfyKU7KRqwOjXCXTqWdFNvEr7eUBYchQiVc744mqK/MzXPNR2WsPkmkOa4ywfg8C2n8h+13Bey1Q==" - }, - "which-boxed-primitive": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.1.tgz", - "integrity": "sha512-7BT4TwISdDGBgaemWU0N0OU7FeAEJ9Oo2P1PHRm/FCWoEi2VLWC9b6xvxAA3C/NMpxg3HXVgi0sMmGbNUbNepQ==", - "optional": true, - "requires": { - "is-bigint": "^1.0.0", - "is-boolean-object": "^1.0.0", - "is-number-object": "^1.0.3", - "is-string": "^1.0.4", - "is-symbol": "^1.0.2" - } - }, - "which-collection": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.1.tgz", - "integrity": "sha512-W8xeTUwaln8i3K/cY1nGXzdnVZlidBcagyNFtBdD5kxnb4TvGKR7FfSIS3mYpwWS1QUCutfKz8IY8RjftB0+1A==", - "optional": true, - "requires": { - "is-map": "^2.0.1", - "is-set": "^2.0.1", - "is-weakmap": "^2.0.1", - "is-weakset": "^2.0.1" - } - }, - "which-typed-array": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.2.tgz", - "integrity": "sha512-KT6okrd1tE6JdZAy3o2VhMoYPh3+J6EMZLyrxBQsZflI1QCZIxMrIYLkosd8Twf+YfknVIHmYQPgJt238p8dnQ==", - "optional": true, - "requires": { - "available-typed-arrays": "^1.0.2", - "es-abstract": "^1.17.5", - "foreach": "^2.0.5", - "function-bind": "^1.1.1", - "has-symbols": "^1.0.1", - "is-typed-array": "^1.1.3" - } - }, - "with": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/with/-/with-7.0.1.tgz", - "integrity": "sha512-TpHxhlaRS5mNJbCDXqbDJB4qhyV8zQUPytY3o3cCb6t2m13Qw+vsWFvJCBBIkWILRjNlmlnvd/0AW0dPaO7n/w==", - "requires": { - "@babel/parser": "^7.9.6", - "@babel/types": "^7.9.6", - "assert-never": "^1.2.1", - "babel-walk": "3.0.0-canary-5" - } - }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" - }, - "write-file-atomic": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", - "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", - "optional": true, - "requires": { - "imurmurhash": "^0.1.4", - "is-typedarray": "^1.0.0", - "signal-exit": "^3.0.2", - "typedarray-to-buffer": "^3.1.5" - } - }, - "xdg-basedir": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-4.0.0.tgz", - "integrity": "sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==", - "optional": true - }, - "xmlhttprequest": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/xmlhttprequest/-/xmlhttprequest-1.8.0.tgz", - "integrity": "sha1-Z/4HXFwk/vOfnWX197f+dRcZaPw=" - }, - "xtend": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==" - }, - "yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "optional": true - } - } -} diff --git a/functions/package.json b/functions/package.json deleted file mode 100644 index cdb7f18..0000000 --- a/functions/package.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "name": "functions", - "scripts": { - "lint": "tslint --project tsconfig.json", - "build": "tsc", - "serve": "npm run build && firebase emulators:start --only functions", - "shell": "npm run build && firebase functions:shell", - "start": "npm run shell", - "deploy": "firebase deploy --only functions", - "logs": "firebase functions:log", - "server": "npm run build && node lib/bin/www.js" - }, - "main": "lib/bin/www.js", - "dependencies": { - "body-parser": "^1.19.0", - "express": "^4.17.1", - "firebase-admin": "^8.10.0", - "firebase-functions": "^3.6.1", - "firebase-functions-helper": "^0.7.5", - "http-errors": "^1.7.3", - "morgan": "^1.10.0", - "node-fetch": "^2.6.0", - "pug": "^3.0.0" - }, - "devDependencies": { - "tslint": "^5.12.0", - "typescript": "^3.8.0", - "firebase-functions-test": "^0.2.0" - }, - "private": true -} diff --git a/functions/src/app.ts b/functions/src/app.ts deleted file mode 100644 index 748ecb9..0000000 --- a/functions/src/app.ts +++ /dev/null @@ -1,43 +0,0 @@ -import * as path from 'path'; -import * as logger from 'morgan'; -import * as express from 'express'; -import * as createError from 'http-errors'; -import router = require("./routes/newsriver"); - - -const app = express(); - -// view engine setup -app.set('views', path.join(__dirname, 'views')); -app.set('view engine', 'pug'); - -app.use(logger('dev')); -app.use(express.json()); -app.use(express.urlencoded({extended: false})); -app.use(express.static(path.join(__dirname, 'public'))); - -/** - * ------------- - * Test purposes - * ------------- - const testUpdater = new updater.Updater(null, null, ['covid-19', 'enfermedad'], functions.config().newsriver.key, 'es'); - app.get('/api', (req, res) => - testUpdater.request() - .then(it => res.json(it)) - ); - * --------------- - */ -app.use(router); -// catch 404 and forward to error handler -app.use((req, res, next) => next(createError(404))); - -// error handler -app.use((err, req, res, next) => { - res.locals.message = err.message; - res.locals.error = res.app.get('env') === 'development' ? err : {}; - - res.status(err.status || 500); - res.render('error'); -}); - -export = app; diff --git a/functions/src/bin/www.ts b/functions/src/bin/www.ts deleted file mode 100644 index 4055b63..0000000 --- a/functions/src/bin/www.ts +++ /dev/null @@ -1,81 +0,0 @@ -const app = require('../app'); -import * as http from 'http'; -import functions = require("firebase-functions"); - - -/** - * Get port from environment and store in Express - */ -const port = normalizePort(process.env.PORT || '3000'); - -/** - * Create the http server - */ -const server = http.createServer(app); - -/** - * Listen on a provided port, on all network interfaces - */ -server.on('error', onError); -server.on('listening', onListening); -export const webApi = functions.https.onRequest(app); - -/** - * Normalize a port into a number, string or false - * @param val the port - */ -function normalizePort(val) { - const parsedPort = parseInt(val, 10); - - if (isNaN(parsedPort)) { - // named pipe - return val; - } - - if (parsedPort >= 0) { - // port number - return parsedPort; - } - - return false; -} - -/** - * Event listener for HTTP server "error" event. - */ - -function onError(error) { - if (error.syscall !== 'listen') { - throw error; - } - - const bind = typeof port === 'string' - ? 'Pipe ' + port - : 'Port ' + port; - - // handle specific listen errors with friendly messages - switch (error.code) { - case 'EACCES': - console.error(bind + ' requires elevated privileges'); - process.exit(1); - break; - case 'EADDRINUSE': - console.error(bind + ' is already in use'); - process.exit(1); - break; - default: - throw error; - } -} - -/** - * Event listener for HTTP server "listening" event. - */ - -function onListening() { - const addr = server.address(); - const bind = typeof addr === 'string' - ? 'pipe ' + addr - : 'port ' + addr.port; - console.log('Listening on ' + bind); -} diff --git a/functions/src/controllers/api.ts b/functions/src/controllers/api.ts deleted file mode 100644 index fc2d383..0000000 --- a/functions/src/controllers/api.ts +++ /dev/null @@ -1,50 +0,0 @@ -// import {Api} from '../models/api'; -// import api = require('../models/api'); -// import {api} from '../models/api'; -// import apiModels = require("../models/api"); - -import {Request, Response} from 'express'; -import {Api} from "../models/api"; - -/*export function getNewsByLanguage(req: Request, res: Response) { - console.log('Getting news by language'); - const language = req.query.lang as string; - apiModels.api.newsForLanguage(language) - .then(data => res.json(data)) - .catch(err => { - console.error(err); - res.sendStatus(304); - }); -}*/ - -export function ApiController(api: Api) { - return { - getNewsByLanguage(req: Request, res: Response) { - const language = req.query.lang as string; - api.newsForLanguage(language) - .then(data => res.json(data)) - .catch(err => { - console.error(err); - res.sendStatus(500); - }); - } - } -} - -/*export class ApiController { - api: Api - - constructor(api: Api) { - this.api = api; - } - - getNewsByLanguage(req: Request, res: Response) { - const language = req.query.lang as string; - this.api.newsForLanguage(language) - .then(data => res.json(data)) - .catch(err => { - console.error(err); - res.sendStatus(500); - }); - } -}*/ diff --git a/functions/src/controllers/newsriver.ts b/functions/src/controllers/newsriver.ts deleted file mode 100644 index 538845b..0000000 --- a/functions/src/controllers/newsriver.ts +++ /dev/null @@ -1,17 +0,0 @@ -export import apiModel = require("../models/newsriver"); -import {Request, Response} from "express"; - - -export async function queryNewsForLanguage(req: Request, res: Response) { - const language = req.query.lang as string; - apiModel.newsForLanguage(language) - .then(newsData => res.json(newsData)) - .catch(err => { - if (err !instanceof RangeError) { - console.error(`Error while getting news data: ${err}`); - res.sendStatus(500); - } - else - res.status(403).send(err); - }) -} \ No newline at end of file diff --git a/functions/src/interfaces/projectProperties.ts b/functions/src/interfaces/projectProperties.ts deleted file mode 100644 index 2bdee61..0000000 --- a/functions/src/interfaces/projectProperties.ts +++ /dev/null @@ -1,5 +0,0 @@ -export interface ProjectProperties { - collection: string, - database: FirebaseFirestore.Firestore, - authToken: string -} \ No newline at end of file diff --git a/functions/src/models/api.ts b/functions/src/models/api.ts deleted file mode 100644 index 44ddc4e..0000000 --- a/functions/src/models/api.ts +++ /dev/null @@ -1,75 +0,0 @@ -// import * as functions from 'firebase-functions'; -// import * as admin from 'firebase-admin'; - -import {ProjectProperties} from '../interfaces/projectProperties'; -import {RemoteConfigData} from '../rcdata'; -import {Updater} from '../updater'; -import {NewsriverData} from '../newsriver'; - -export class Api { - properties: ProjectProperties; - remoteConfig: RemoteConfigData; - languages: Array; - updaters: Record; - timers: Set; - - constructor(properties: ProjectProperties, - remoteConfig: RemoteConfigData, - languages: Array) { - this.properties = properties; - this.remoteConfig = remoteConfig; - this.languages = languages; - this.updaters = {}; - for (const language in languages) { - this.updaters[language] = undefined; - } - this.timers = new Set(); - } - - async init() { - this.languages.forEach(language => { - this.remoteConfig.getSearchTermsForLanguage(language) - .then(terms => { - const updater = new Updater( - this.properties.database, - `${this.properties.collection}_${language}`, - terms, - this.properties.authToken, - language - ); - this.updaters[language] = updater; - this.timers.add(updater.schedule()); - }); - }); - this.remoteConfig.subscribeUpdaters(this.updaters); - } - - async newsForLanguage(language: string): Promise> { - if (language ! in this.languages) - language = 'en'; - const collection = this.updaters[language].collection; - const snapshot = await collection.get(); - const data = Array(snapshot.size); - snapshot.forEach(item => { - data.push(item.data() as NewsriverData); - }); - return data; - } - - finish() { - for (const timer of this.timers) { - clearInterval(timer); - } - } -} -/*const serviceAccount = require('../../handwashing-firebase-adminsdk.json'); -const firebaseApp = admin.initializeApp({ - credential: admin.credential.cert(serviceAccount), - databaseURL: 'https://handwashing.firebaseio.com' -});*/ -/*const firebaseApp = admin.initializeApp(); -const projectProperties: ProjectProperties = { - collection: 'news', - database: admin.firestore(), - authToken: functions.config().newsriver.key -};*/ \ No newline at end of file diff --git a/functions/src/models/newsriver.ts b/functions/src/models/newsriver.ts deleted file mode 100644 index a6c42b8..0000000 --- a/functions/src/models/newsriver.ts +++ /dev/null @@ -1,70 +0,0 @@ -import admin = require('firebase-admin'); -import functions = require("firebase-functions"); -import {ProjectProperties} from "../interfaces/projectProperties"; -import {Updater} from "../updater"; -import {RemoteConfigData} from "../rcdata"; -import {NewsriverData} from "../newsriver"; - - -const serviceAccount = require('../../handwashing-firebase-adminsdk.json'); -export const firebaseApp = admin.initializeApp({ - credential: admin.credential.cert(serviceAccount), - databaseURL: 'https://handwashing.firebaseio.com' -}); -const languages = new Set(['es', 'en']); -const projectProperties: ProjectProperties = { - collection: 'news', - database: admin.firestore(), - authToken: functions.config().newsriver.key -} -const updaters: Record = {}; -const remoteConfig = new RemoteConfigData(firebaseApp); -const timers = new Set(); -let initCalled = false; - -export async function initialize() { - initCalled = true; - for (const language of languages) { - const terms = await remoteConfig.getSearchTermsForLanguage(language); - const updater = new Updater( - projectProperties.database, - `${projectProperties.collection}_${language}`, - terms, - projectProperties.authToken, - language - ); - console.log('Created updater object'); - updaters[language] = updater; - } - remoteConfig.subscribeUpdaters(updaters); -} - -export async function scheduleUpdates() { - if (!initCalled) - throw new Error('`initialize` not called'); - for (const language of languages) { - timers.add(updaters[language].schedule()); - } -} - -export async function newsForLanguage(language: string) { - if (!initCalled) - throw new Error('`initialize` not called'); - if (language !in languages) - throw new RangeError(`invalid language "${language}"`); - const collection = updaters[language].collection; - const snapshot = await collection.get(); - const data = new Array(); - snapshot.forEach(item => { - if (item.data() !== null) - data.push(item.data() as NewsriverData) - }); - return data; -} - -export async function stopScheduling() { - if (!initCalled) - throw new Error('`initialize` not called'); - for (const timer of timers) - clearInterval(timer); -} diff --git a/functions/src/newsriver.ts b/functions/src/newsriver.ts deleted file mode 100644 index 1fd8258..0000000 --- a/functions/src/newsriver.ts +++ /dev/null @@ -1,48 +0,0 @@ -interface NewsriverElements { - type: string, - primary: boolean, - url: string, - width: number | null, - height: number | null, - title: string | null, - alternative: string | null - } - - interface NewsriverWebsite { - name: string, - hostName: string, - domainName: string, - iconURL: string, - countryName: string | null, - countryCode: string | null, - region: null - } - - interface Sentiment { - type: string, - sentiment: number - } - - interface ReadTime { - type: string, - seconds: number - } - - interface NewsriverMetadata { - finSentiment: Sentiment, - readTime: ReadTime - } - - export interface NewsriverData { - id: string, - discoverDate: Date, - title: string, - language: string, - text: string, - structuredText: string, - elements: Array, - website: NewsriverWebsite, - metadata: NewsriverMetadata, - highlight: string, - score: number - } \ No newline at end of file diff --git a/functions/src/public/stylesheets/style.css b/functions/src/public/stylesheets/style.css deleted file mode 100644 index b993201..0000000 --- a/functions/src/public/stylesheets/style.css +++ /dev/null @@ -1,8 +0,0 @@ -body { - padding: 50px; - font: 14px "Lucida Grande", Helvetica, Arial, sans-serif; -} - -a { - color: #00B7FF; -} diff --git a/functions/src/rcdata.ts b/functions/src/rcdata.ts deleted file mode 100644 index ccbe159..0000000 --- a/functions/src/rcdata.ts +++ /dev/null @@ -1,61 +0,0 @@ -import {Updater} from './updater'; -import * as admin from 'firebase-admin'; -import * as functions from 'firebase-functions' - -export class RemoteConfigData { - remoteConfig: admin.remoteConfig.RemoteConfig; - updaters: Record; - - constructor(app: admin.app.App) { - this.remoteConfig = admin.remoteConfig(app); - this.updaters = {}; - this.listenToRCChanges(); - } - - getSearchTermsForLanguage(language: string): Promise> { - return new Promise>(resolve => { - this.remoteConfig.getTemplate() - .then(template => { - let condition: string; - switch (language) { - case 'es': - condition = 'Spanish users'; - break; - default: - condition = 'Default language users'; - break; - } - const values = JSON.parse(template.parameters['search_terms'].conditionalValues[condition]['value']); - resolve(values); - }); - }); - } - - subscribeUpdaters(updaters: Record) { - this.updaters = updaters; - } - - listenToRCChanges() { - functions.remoteConfig.onUpdate(_ => { - return admin.credential.applicationDefault().getAccessToken() - .then(_ => { - this.remoteConfig.getTemplate() - .then(template => { - const languages = Object.keys(template.parameters['search_terms'].conditionalValues); - for (const language of languages) { - const terms = JSON.parse( - template.parameters['search_terms'].conditionalValues[language]['value'] - ); - try { - if (this.updaters[language].searchTerms.length !== terms.lenght) - this.updaters[language].searchTerms = terms; - } catch (e) { - console.warn(`Updaters are not set yet - ${e}`); - } - } - }); - }) - .catch(err => console.error(`Error while obtaining data from RC: ${err}`)); - }); - } -} diff --git a/functions/src/routes/api.ts b/functions/src/routes/api.ts deleted file mode 100644 index 362378d..0000000 --- a/functions/src/routes/api.ts +++ /dev/null @@ -1,178 +0,0 @@ -import * as admin from 'firebase-admin'; -// import controller = require("../controllers/api"); -import {Router} from "express"; -// import get = Reflect.get; - -/*const router = Router(); - -router.get('/api/v1', (req, res, next) => { - try { - const language = req.query.lang; - const tokenId = req.get('Authorization').split('Bearer')[0]; - admin.auth().verifyIdToken(tokenId) - .then(_ => { - if (language === undefined) - res.status(403).send('lang must be given [?lang=...]'); - else next(); - }) - .catch(e => { - console.log('User unauthorized'); - next(); - // res.status(401).send(e) - }) - } catch (e) { - console.error(e); - res.status(401).send(e); - } -}); -router.get('/api/v1', controller.getNewsByLanguage);*/ -// app: admin.app.App, apiController: { getNewsByLanguage: (req, res) => any } -export function ApiRoutes(apiController: { getNewsByLanguage: (req, res) => any }) { - const router = Router(); - router.get('/api/v1', (req, res, next) => { - try { - const language = req.query.lang; - const tokenId = req.get('Authorization').split('Bearer')[0]; - admin.auth().verifyIdToken(tokenId) - .then(_ => { - if (language === undefined) - res.status(403).send('lang must be given [?lang=...]'); - else next(); - }) - .catch(e => { - console.log('User unauthorized'); - next(); - // res.status(401).send(e) - }) - } catch (e) { - console.error(e); - res.status(401).send(e); - } - }); - router.get('/api/v1', apiController.getNewsByLanguage); - - return {router}; -} - -/*export class ApiRouter { - router: Router - // firebaseApp: admin.app.App - - constructor(app: admin.app.App, apiController: ApiController) { - // this.firebaseApp = app; - this.router = Router(); - - this.router.get('/api/v1', (req, res, next) => { - try { - const language = req.query.lang; - const tokenId = req.get('Authorization').split('Bearer')[0]; - admin.auth(app).verifyIdToken(tokenId) - .then(_ => { - if (language === undefined) - res.status(403).send('lang must be given [?lang=...]'); - else next(); - }) - .catch(e => { - console.log('User unauthorized'); - next(); - // res.status(401).send(e) - }) - } catch (e) { - console.error(e); - res.status(401).send(e); - } - }); - // this.router.get('/api/v1', apiController.getNewsByLanguage); - } -}*/ - -// export = router; - -/*export class ApiRouter { - router: Router; - firebaseApp: admin.app.App; - - constructor(firebaseApp: admin.app.App, apiController: ApiController) { - this.router = Router(); - this.firebaseApp = firebaseApp; - // this.router.use('/api/v1', apiController.getNewsByLanguage); - this.router.use('/update', (req: Request, res: Response) => { - try { - apiController.api.updaters['en'].request() - .then(apiData => { - apiController.api.updaters['en'].updateData(apiData) - .then(_ => { - console.log("Data updated"); - res.json(apiData); - }) - .catch(err => { - console.error(err); - res.sendStatus(500); - }) - }) - .catch(e => { - console.error(e); - res.sendStatus(500); - }); - } catch (e) { - console.error(e); - res.sendStatus(500); - } - }); - } - - async init(): Promise { - this.router.use('/api/v1', (req: Request, res: Response, next: NextFunction) => { - try { - const language = req.query.lang; - const tokenId = req.get('Authorization').split('Bearer')[1]; - admin.auth(this.firebaseApp).verifyIdToken(tokenId) - .then(_ => { - if (language === undefined) - res.status(403).send('lang must be given [?lang=...]'); - else next(); - }) - .catch(err => res.status(401).send(err)); - } catch (e) { - res.status(401).send(e); - } - }); - return this.router; - } -}*/ - -// export const router = express.Router(); - -/*router.use('/api/v1', (req, res, next) => { - try { - const language = req.query.lang; - const tokenId = req.get('Authorization').split('Bearer ')[1]; - admin.auth().verifyIdToken(tokenId) - .then(_ => { - if (language === undefined) { - res.status(403).send('lang must be given [?lang=...]'); - } else next(); - }) - .catch(err => res.status(401).send(err)); - } catch (e) { - res.status(401).send(e); - } -});*/ -// router.use('/api/v1', getNewsByLanguage); -// router.use('/update', (req, res) => { -// api.updaters['es'].request() -// .then(apiData => { -// api.updaters['es'].updateData(apiData) -// .then(() => { -// console.log("Data was updated"); -// res.json(apiData); -// }) -// .catch((err) => { -// console.error(err); -// res.status(304).send(err); -// }); -// }) -// .catch((err) => console.error(err)); -// }); - -// module.exports = router; diff --git a/functions/src/routes/newsriver.ts b/functions/src/routes/newsriver.ts deleted file mode 100644 index 2ab181b..0000000 --- a/functions/src/routes/newsriver.ts +++ /dev/null @@ -1,51 +0,0 @@ -import apiController = require("../controllers/newsriver"); -import express = require("express"); -// import admin = require("firebase-admin"); - - -const router = express.Router(); - -apiController.apiModel.initialize() - .then(_ => apiController.apiModel.scheduleUpdates()) - .catch(e => { - console.error(e); - throw e; - }); - -router.get('/api/v1', (req, res, next) => { - try { - const language = req.query.lang; - // const tokenId = req.get('Authorization').split('Bearer')[0]; - if (language === undefined) - res.status(403).send('lang must be given [?lang=...]'); - else next(); - /*admin.auth(apiController.apiModel.firebaseApp).verifyIdToken(tokenId) - .then(_ => { - if (language === undefined) - res.status(403).send('lang must be given [?lang=...]'); - else next(); - }) - .catch(e => { - console.error('Unauthorized'); - next(); - // res.status(401).send(e); - });*/ - } catch (e) { - console.error(`Possible missing authorization header: ${e}`); - res.status(401).send(e); - } -}); -router.get('/api/v1', apiController.queryNewsForLanguage); -router.get('/close', (req, res) => { - const authToken = req.get('Authorization'); - if (authToken === undefined) - return res.sendStatus(403); - if (authToken === process.env.ADMIN_TOKEN) { - return apiController.apiModel.stopScheduling() - .then(_ => res.sendStatus(200)) - .catch(_ => res.sendStatus(200)); - } else - return res.sendStatus(403); -}); - -export = router; diff --git a/functions/src/updater.ts b/functions/src/updater.ts deleted file mode 100644 index d0f5c30..0000000 --- a/functions/src/updater.ts +++ /dev/null @@ -1,121 +0,0 @@ -import {NewsriverData} from "./newsriver"; -import * as firebaseHelper from 'firebase-functions-helper'; -import * as fetch from 'node-fetch'; -import {Headers} from 'node-fetch'; - -export class Updater { - private readonly db: FirebaseFirestore.Firestore; - private readonly collectionName: string; - private readonly interval: number; - private _searchTerms: Array; - private readonly language: string; - private readonly auth: string; - private _url: string | undefined; - - get url(): Promise { - if (this._url === undefined) - return this.buildURL() - .then(url => this._url = url); - return Promise.resolve(this._url); - } - - get searchTerms(): Array { - return this._searchTerms; - } - - set searchTerms(value) { - this._searchTerms = value; - this._url = undefined; - } - - get collection(): FirebaseFirestore.CollectionReference { - return this.db.collection(this.collectionName); - } - - constructor(db: FirebaseFirestore.Firestore | null, - collectionName: string | null, - searchTerms: Array, - auth: string, - language: string = 'en', - intervalMins: number = 60) { - this.db = db; - this.collectionName = collectionName; - this.searchTerms = searchTerms; - this.language = language; - this.auth = auth; - this.interval = intervalMins * 60 * 1000; - this.request() - .then(response => { - this.updateData(response) - .catch(ignored => { - }); - }) - } - - schedule(): NodeJS.Timer { - return setInterval(() => { - const that = this; - this.request() - .then(response => { - that.updateData(response) - .catch(error => console.error(`error occurred while updating firebase data ${error}`)); - }) - .catch(error => console.error(`error occurred during process ${error}`)); - }, this.interval); - } - - async updateData(content: Array) { - console.log('Updating database content data'); - try { - content.forEach(element => { - firebaseHelper.firestore.checkDocumentExists(this.db, this.collectionName, element.id) - .then(_ => firebaseHelper.firestore.createDocumentWithID(this.db, this.collectionName, element.id, element)) - console.log(`Created element with ID: ${element.id}`); - }); - } catch (error) { - console.error(`Unhandled error ${error}`); - } - } - - async request(): Promise> { - try { - const requestUrl = await this.url; - const response = await fetch(requestUrl, { - method: 'GET', headers: new Headers({ - 'Authorization': this.auth, - 'Content-Type': 'application/json' - }) - }); - const body = await response.json(); - return body as Array; - } catch (e) { - console.error(`Captured error ${e}`); - throw e; - } - } - - buildURL(): Promise { - return new Promise(resolve => { - const parts = ['https://api.newsriver.io/v2/search?query=']; - this.searchTerms.forEach((term, i, _) => { - if (i !== 0) - parts.push(encodeURI(' OR ')); - parts.push(encodeURI(`title:${term} OR text:${term}`)); - }); - let language: string; - switch (this.language) { - case 'es': - language = 'ES'; - break; - default: - language = 'EN'; - break; - } - parts.push(encodeURI(` AND language:${language}`)); - parts.push(encodeURI('&sortBy=discoverDate')); - parts.push(encodeURI('&sortOrder=DESC')); - parts.push(encodeURI('&limit=10')); - resolve(parts.join('')); - }); - } -} \ No newline at end of file diff --git a/functions/src/views/error.pug b/functions/src/views/error.pug deleted file mode 100644 index 3b25cfa..0000000 --- a/functions/src/views/error.pug +++ /dev/null @@ -1,6 +0,0 @@ -extends layout - -block content - h1= message - h2= error.status - pre #{error.stack} diff --git a/functions/src/views/layout.pug b/functions/src/views/layout.pug deleted file mode 100644 index 6dc17d8..0000000 --- a/functions/src/views/layout.pug +++ /dev/null @@ -1,7 +0,0 @@ -doctype html -html - head - title= title - link(rel='stylesheet', href='/stylesheets/style.css') - body - block content diff --git a/functions/tsconfig.json b/functions/tsconfig.json deleted file mode 100644 index 872b7f9..0000000 --- a/functions/tsconfig.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "compilerOptions": { - "module": "commonjs", - "noImplicitReturns": true, - "noUnusedLocals": true, - "outDir": "lib", - "sourceMap": true, - "strict": false, - "target": "es2017" - }, - "compileOnSave": true, - "include": [ - "src" - ] -} diff --git a/functions/tslint.json b/functions/tslint.json deleted file mode 100644 index 3c51143..0000000 --- a/functions/tslint.json +++ /dev/null @@ -1,115 +0,0 @@ -{ - "rules": { - // -- Strict errors -- - // These lint rules are likely always a good idea. - - // Force function overloads to be declared together. This ensures readers understand APIs. - "adjacent-overload-signatures": true, - - // Do not allow the subtle/obscure comma operator. - "ban-comma-operator": true, - - // Do not allow internal modules or namespaces . These are deprecated in favor of ES6 modules. - "no-namespace": true, - - // Do not allow parameters to be reassigned. To avoid bugs, developers should instead assign new values to new vars. - "no-parameter-reassignment": true, - - // Force the use of ES6-style imports instead of /// imports. - "no-reference": true, - - // Do not allow type assertions that do nothing. This is a big warning that the developer may not understand the - // code currently being edited (they may be incorrectly handling a different type case that does not exist). - "no-unnecessary-type-assertion": true, - - // Disallow nonsensical label usage. - "label-position": true, - - // Disallows the (often typo) syntax if (var1 = var2). Replace with if (var2) { var1 = var2 }. - "no-conditional-assignment": true, - - // Disallows constructors for primitive types (e.g. new Number('123'), though Number('123') is still allowed). - "no-construct": true, - - // Do not allow super() to be called twice in a constructor. - "no-duplicate-super": true, - - // Do not allow the same case to appear more than once in a switch block. - "no-duplicate-switch-case": true, - - // Do not allow a variable to be declared more than once in the same block. Consider function parameters in this - // rule. - "no-duplicate-variable": [true, "check-parameters"], - - // Disallows a variable definition in an inner scope from shadowing a variable in an outer scope. Developers should - // instead use a separate variable name. - "no-shadowed-variable": true, - - // Empty blocks are almost never needed. Allow the one general exception: empty catch blocks. - "no-empty": [true, "allow-empty-catch"], - - // Functions must either be handled directly (e.g. with a catch() handler) or returned to another function. - // This is a major source of errors in Cloud Functions and the team strongly recommends leaving this rule on. - "no-floating-promises": true, - - // Do not allow any imports for modules that are not in package.json. These will almost certainly fail when - // deployed. - "no-implicit-dependencies": true, - - // The 'this' keyword can only be used inside of classes. - "no-invalid-this": true, - - // Do not allow strings to be thrown because they will not include stack traces. Throw Errors instead. - "no-string-throw": true, - - // Disallow control flow statements, such as return, continue, break, and throw in finally blocks. - "no-unsafe-finally": true, - - // Expressions must always return a value. Avoids common errors like const myValue = functionReturningVoid(); - "no-void-expression": [true, "ignore-arrow-function-shorthand"], - - // Disallow duplicate imports in the same file. - "no-duplicate-imports": true, - - - // -- Strong Warnings -- - // These rules should almost never be needed, but may be included due to legacy code. - // They are left as a warning to avoid frustration with blocked deploys when the developer - // understand the warning and wants to deploy anyway. - - // Warn when an empty interface is defined. These are generally not useful. - "no-empty-interface": {"severity": "warning"}, - - // Warn when an import will have side effects. - "no-import-side-effect": {"severity": "warning"}, - - // Warn when variables are defined with var. Var has subtle meaning that can lead to bugs. Strongly prefer const for - // most values and let for values that will change. - "no-var-keyword": {"severity": "warning"}, - - // Prefer === and !== over == and !=. The latter operators support overloads that are often accidental. - "triple-equals": {"severity": "warning"}, - - // Warn when using deprecated APIs. - "deprecation": {"severity": "warning"}, - - // -- Light Warnings -- - // These rules are intended to help developers use better style. Simpler code has fewer bugs. These would be "info" - // if TSLint supported such a level. - - // prefer for( ... of ... ) to an index loop when the index is only used to fetch an object from an array. - // (Even better: check out utils like .map if transforming an array!) - "prefer-for-of": {"severity": "warning"}, - - // Warns if function overloads could be unified into a single function with optional or rest parameters. - "unified-signatures": {"severity": "warning"}, - - // Prefer const for values that will not change. This better documents code. - "prefer-const": {"severity": "warning"}, - - // Multi-line object literals and function calls should have a trailing comma. This helps avoid merge conflicts. - "trailing-comma": {"severity": "warning"} - }, - - "defaultSeverity": "warn" -} From 8cf2a494ed0ac4138e00060721f0b5ce7707c67c Mon Sep 17 00:00:00 2001 From: Javinator9889 Date: Mon, 8 Jun 2020 16:51:48 +0200 Subject: [PATCH 23/95] Renamed directories for matching NodeJS structure --- functions/{RApi => rapi}/.gitignore | 0 functions/{RApi => rapi}/.idea/.gitignore | 0 .../.idea/codeStyles/codeStyleConfig.xml | 0 .../.idea/dictionaries/javinator9889.xml | 0 .../.idea/inspectionProfiles/Project_Default.xml | 0 .../{RApi => rapi}/.idea/jsLibraryMappings.xml | 0 functions/{RApi => rapi}/.idea/misc.xml | 0 functions/{RApi => rapi}/.idea/modules.xml | 0 functions/{RApi => rapi}/.idea/vcs.xml | 0 functions/{RApi => rapi}/.idea/watcherTasks.xml | 0 functions/{RApi => rapi}/lib/tsconfig.tsbuildinfo | 0 functions/{RApi => rapi}/lib/views/error.pug | 0 functions/{RApi => rapi}/lib/views/layout.pug | 0 functions/{RApi => rapi}/package-lock.json | 0 functions/{RApi => rapi}/package.json | 0 functions/{RApi => rapi}/src/app.ts | 0 functions/{RApi => rapi}/src/bin/www.ts | 0 .../{RApi => rapi}/src/controllers/newsriver.ts | 0 .../src/interfaces/projectProperties.ts | 0 functions/{RApi => rapi}/src/models/newsriver.ts | 0 functions/{RApi => rapi}/src/newsriver.ts | 0 .../{RApi => rapi}/src/public/stylesheets/style.css | 0 functions/{RApi => rapi}/src/rcdata.ts | 0 functions/{RApi => rapi}/src/routes/newsriver.ts | 13 +++++-------- functions/{RApi => rapi}/src/updater.ts | 0 functions/{RApi => rapi}/src/views/error.pug | 0 functions/{RApi => rapi}/src/views/layout.pug | 0 functions/{RApi => rapi}/tsconfig.json | 0 functions/{RApi => rapi}/tslint.json | 0 29 files changed, 5 insertions(+), 8 deletions(-) rename functions/{RApi => rapi}/.gitignore (100%) rename functions/{RApi => rapi}/.idea/.gitignore (100%) rename functions/{RApi => rapi}/.idea/codeStyles/codeStyleConfig.xml (100%) rename functions/{RApi => rapi}/.idea/dictionaries/javinator9889.xml (100%) rename functions/{RApi => rapi}/.idea/inspectionProfiles/Project_Default.xml (100%) rename functions/{RApi => rapi}/.idea/jsLibraryMappings.xml (100%) rename functions/{RApi => rapi}/.idea/misc.xml (100%) rename functions/{RApi => rapi}/.idea/modules.xml (100%) rename functions/{RApi => rapi}/.idea/vcs.xml (100%) rename functions/{RApi => rapi}/.idea/watcherTasks.xml (100%) rename functions/{RApi => rapi}/lib/tsconfig.tsbuildinfo (100%) rename functions/{RApi => rapi}/lib/views/error.pug (100%) rename functions/{RApi => rapi}/lib/views/layout.pug (100%) rename functions/{RApi => rapi}/package-lock.json (100%) rename functions/{RApi => rapi}/package.json (100%) rename functions/{RApi => rapi}/src/app.ts (100%) rename functions/{RApi => rapi}/src/bin/www.ts (100%) rename functions/{RApi => rapi}/src/controllers/newsriver.ts (100%) rename functions/{RApi => rapi}/src/interfaces/projectProperties.ts (100%) rename functions/{RApi => rapi}/src/models/newsriver.ts (100%) rename functions/{RApi => rapi}/src/newsriver.ts (100%) rename functions/{RApi => rapi}/src/public/stylesheets/style.css (100%) rename functions/{RApi => rapi}/src/rcdata.ts (100%) rename functions/{RApi => rapi}/src/routes/newsriver.ts (78%) rename functions/{RApi => rapi}/src/updater.ts (100%) rename functions/{RApi => rapi}/src/views/error.pug (100%) rename functions/{RApi => rapi}/src/views/layout.pug (100%) rename functions/{RApi => rapi}/tsconfig.json (100%) rename functions/{RApi => rapi}/tslint.json (100%) diff --git a/functions/RApi/.gitignore b/functions/rapi/.gitignore similarity index 100% rename from functions/RApi/.gitignore rename to functions/rapi/.gitignore diff --git a/functions/RApi/.idea/.gitignore b/functions/rapi/.idea/.gitignore similarity index 100% rename from functions/RApi/.idea/.gitignore rename to functions/rapi/.idea/.gitignore diff --git a/functions/RApi/.idea/codeStyles/codeStyleConfig.xml b/functions/rapi/.idea/codeStyles/codeStyleConfig.xml similarity index 100% rename from functions/RApi/.idea/codeStyles/codeStyleConfig.xml rename to functions/rapi/.idea/codeStyles/codeStyleConfig.xml diff --git a/functions/RApi/.idea/dictionaries/javinator9889.xml b/functions/rapi/.idea/dictionaries/javinator9889.xml similarity index 100% rename from functions/RApi/.idea/dictionaries/javinator9889.xml rename to functions/rapi/.idea/dictionaries/javinator9889.xml diff --git a/functions/RApi/.idea/inspectionProfiles/Project_Default.xml b/functions/rapi/.idea/inspectionProfiles/Project_Default.xml similarity index 100% rename from functions/RApi/.idea/inspectionProfiles/Project_Default.xml rename to functions/rapi/.idea/inspectionProfiles/Project_Default.xml diff --git a/functions/RApi/.idea/jsLibraryMappings.xml b/functions/rapi/.idea/jsLibraryMappings.xml similarity index 100% rename from functions/RApi/.idea/jsLibraryMappings.xml rename to functions/rapi/.idea/jsLibraryMappings.xml diff --git a/functions/RApi/.idea/misc.xml b/functions/rapi/.idea/misc.xml similarity index 100% rename from functions/RApi/.idea/misc.xml rename to functions/rapi/.idea/misc.xml diff --git a/functions/RApi/.idea/modules.xml b/functions/rapi/.idea/modules.xml similarity index 100% rename from functions/RApi/.idea/modules.xml rename to functions/rapi/.idea/modules.xml diff --git a/functions/RApi/.idea/vcs.xml b/functions/rapi/.idea/vcs.xml similarity index 100% rename from functions/RApi/.idea/vcs.xml rename to functions/rapi/.idea/vcs.xml diff --git a/functions/RApi/.idea/watcherTasks.xml b/functions/rapi/.idea/watcherTasks.xml similarity index 100% rename from functions/RApi/.idea/watcherTasks.xml rename to functions/rapi/.idea/watcherTasks.xml diff --git a/functions/RApi/lib/tsconfig.tsbuildinfo b/functions/rapi/lib/tsconfig.tsbuildinfo similarity index 100% rename from functions/RApi/lib/tsconfig.tsbuildinfo rename to functions/rapi/lib/tsconfig.tsbuildinfo diff --git a/functions/RApi/lib/views/error.pug b/functions/rapi/lib/views/error.pug similarity index 100% rename from functions/RApi/lib/views/error.pug rename to functions/rapi/lib/views/error.pug diff --git a/functions/RApi/lib/views/layout.pug b/functions/rapi/lib/views/layout.pug similarity index 100% rename from functions/RApi/lib/views/layout.pug rename to functions/rapi/lib/views/layout.pug diff --git a/functions/RApi/package-lock.json b/functions/rapi/package-lock.json similarity index 100% rename from functions/RApi/package-lock.json rename to functions/rapi/package-lock.json diff --git a/functions/RApi/package.json b/functions/rapi/package.json similarity index 100% rename from functions/RApi/package.json rename to functions/rapi/package.json diff --git a/functions/RApi/src/app.ts b/functions/rapi/src/app.ts similarity index 100% rename from functions/RApi/src/app.ts rename to functions/rapi/src/app.ts diff --git a/functions/RApi/src/bin/www.ts b/functions/rapi/src/bin/www.ts similarity index 100% rename from functions/RApi/src/bin/www.ts rename to functions/rapi/src/bin/www.ts diff --git a/functions/RApi/src/controllers/newsriver.ts b/functions/rapi/src/controllers/newsriver.ts similarity index 100% rename from functions/RApi/src/controllers/newsriver.ts rename to functions/rapi/src/controllers/newsriver.ts diff --git a/functions/RApi/src/interfaces/projectProperties.ts b/functions/rapi/src/interfaces/projectProperties.ts similarity index 100% rename from functions/RApi/src/interfaces/projectProperties.ts rename to functions/rapi/src/interfaces/projectProperties.ts diff --git a/functions/RApi/src/models/newsriver.ts b/functions/rapi/src/models/newsriver.ts similarity index 100% rename from functions/RApi/src/models/newsriver.ts rename to functions/rapi/src/models/newsriver.ts diff --git a/functions/RApi/src/newsriver.ts b/functions/rapi/src/newsriver.ts similarity index 100% rename from functions/RApi/src/newsriver.ts rename to functions/rapi/src/newsriver.ts diff --git a/functions/RApi/src/public/stylesheets/style.css b/functions/rapi/src/public/stylesheets/style.css similarity index 100% rename from functions/RApi/src/public/stylesheets/style.css rename to functions/rapi/src/public/stylesheets/style.css diff --git a/functions/RApi/src/rcdata.ts b/functions/rapi/src/rcdata.ts similarity index 100% rename from functions/RApi/src/rcdata.ts rename to functions/rapi/src/rcdata.ts diff --git a/functions/RApi/src/routes/newsriver.ts b/functions/rapi/src/routes/newsriver.ts similarity index 78% rename from functions/RApi/src/routes/newsriver.ts rename to functions/rapi/src/routes/newsriver.ts index 2df4f75..8e14f4a 100644 --- a/functions/RApi/src/routes/newsriver.ts +++ b/functions/rapi/src/routes/newsriver.ts @@ -1,5 +1,6 @@ import apiController = require("../controllers/newsriver"); import express = require("express"); +import admin = require("firebase-admin"); const router = express.Router(); @@ -14,11 +15,8 @@ apiController.apiModel.initialize() router.get('/api/v1', (req, res, next) => { try { const language = req.query.lang; - // const tokenId = req.get('Authorization').split('Bearer')[0]; - if (language === undefined) - res.status(403).send('lang must be given [?lang=...]'); - else next(); - /*admin.auth(apiController.apiModel.firebaseApp).verifyIdToken(tokenId) + const tokenId = req.get('Authorization').split('Bearer')[0]; + admin.auth(apiController.apiModel.firebaseApp).verifyIdToken(tokenId) .then(_ => { if (language === undefined) res.status(403).send('lang must be given [?lang=...]'); @@ -26,9 +24,8 @@ router.get('/api/v1', (req, res, next) => { }) .catch(e => { console.error('Unauthorized'); - next(); - // res.status(401).send(e); - });*/ + res.status(401).send(e); + }); } catch (e) { console.error(`Possible missing authorization header: ${e}`); res.status(401).send(e); diff --git a/functions/RApi/src/updater.ts b/functions/rapi/src/updater.ts similarity index 100% rename from functions/RApi/src/updater.ts rename to functions/rapi/src/updater.ts diff --git a/functions/RApi/src/views/error.pug b/functions/rapi/src/views/error.pug similarity index 100% rename from functions/RApi/src/views/error.pug rename to functions/rapi/src/views/error.pug diff --git a/functions/RApi/src/views/layout.pug b/functions/rapi/src/views/layout.pug similarity index 100% rename from functions/RApi/src/views/layout.pug rename to functions/rapi/src/views/layout.pug diff --git a/functions/RApi/tsconfig.json b/functions/rapi/tsconfig.json similarity index 100% rename from functions/RApi/tsconfig.json rename to functions/rapi/tsconfig.json diff --git a/functions/RApi/tslint.json b/functions/rapi/tslint.json similarity index 100% rename from functions/RApi/tslint.json rename to functions/rapi/tslint.json From 942899538d339b7ecfb9e8644b9ab38867dc221d Mon Sep 17 00:00:00 2001 From: Javinator9889 Date: Mon, 8 Jun 2020 17:02:49 +0200 Subject: [PATCH 24/95] Undo latest commit --- functions/{rapi => }/.gitignore | 0 functions/{rapi => }/.idea/.gitignore | 0 functions/{rapi => }/.idea/codeStyles/codeStyleConfig.xml | 0 functions/{rapi => }/.idea/dictionaries/javinator9889.xml | 0 .../{rapi => }/.idea/inspectionProfiles/Project_Default.xml | 0 functions/{rapi => }/.idea/jsLibraryMappings.xml | 0 functions/{rapi => }/.idea/misc.xml | 0 functions/{rapi => }/.idea/modules.xml | 0 functions/{rapi => }/.idea/vcs.xml | 2 +- functions/{rapi => }/.idea/watcherTasks.xml | 0 functions/{rapi => }/lib/tsconfig.tsbuildinfo | 0 functions/{rapi => }/lib/views/error.pug | 0 functions/{rapi => }/lib/views/layout.pug | 0 functions/{rapi => }/package-lock.json | 0 functions/{rapi => }/package.json | 0 functions/{rapi => }/src/app.ts | 0 functions/{rapi => }/src/bin/www.ts | 0 functions/{rapi => }/src/controllers/newsriver.ts | 0 functions/{rapi => }/src/interfaces/projectProperties.ts | 0 functions/{rapi => }/src/models/newsriver.ts | 0 functions/{rapi => }/src/newsriver.ts | 0 functions/{rapi => }/src/public/stylesheets/style.css | 0 functions/{rapi => }/src/rcdata.ts | 0 functions/{rapi => }/src/routes/newsriver.ts | 0 functions/{rapi => }/src/updater.ts | 0 functions/{rapi => }/src/views/error.pug | 0 functions/{rapi => }/src/views/layout.pug | 0 functions/{rapi => }/tsconfig.json | 0 functions/{rapi => }/tslint.json | 0 29 files changed, 1 insertion(+), 1 deletion(-) rename functions/{rapi => }/.gitignore (100%) rename functions/{rapi => }/.idea/.gitignore (100%) rename functions/{rapi => }/.idea/codeStyles/codeStyleConfig.xml (100%) rename functions/{rapi => }/.idea/dictionaries/javinator9889.xml (100%) rename functions/{rapi => }/.idea/inspectionProfiles/Project_Default.xml (100%) rename functions/{rapi => }/.idea/jsLibraryMappings.xml (100%) rename functions/{rapi => }/.idea/misc.xml (100%) rename functions/{rapi => }/.idea/modules.xml (100%) rename functions/{rapi => }/.idea/vcs.xml (63%) rename functions/{rapi => }/.idea/watcherTasks.xml (100%) rename functions/{rapi => }/lib/tsconfig.tsbuildinfo (100%) rename functions/{rapi => }/lib/views/error.pug (100%) rename functions/{rapi => }/lib/views/layout.pug (100%) rename functions/{rapi => }/package-lock.json (100%) rename functions/{rapi => }/package.json (100%) rename functions/{rapi => }/src/app.ts (100%) rename functions/{rapi => }/src/bin/www.ts (100%) rename functions/{rapi => }/src/controllers/newsriver.ts (100%) rename functions/{rapi => }/src/interfaces/projectProperties.ts (100%) rename functions/{rapi => }/src/models/newsriver.ts (100%) rename functions/{rapi => }/src/newsriver.ts (100%) rename functions/{rapi => }/src/public/stylesheets/style.css (100%) rename functions/{rapi => }/src/rcdata.ts (100%) rename functions/{rapi => }/src/routes/newsriver.ts (100%) rename functions/{rapi => }/src/updater.ts (100%) rename functions/{rapi => }/src/views/error.pug (100%) rename functions/{rapi => }/src/views/layout.pug (100%) rename functions/{rapi => }/tsconfig.json (100%) rename functions/{rapi => }/tslint.json (100%) diff --git a/functions/rapi/.gitignore b/functions/.gitignore similarity index 100% rename from functions/rapi/.gitignore rename to functions/.gitignore diff --git a/functions/rapi/.idea/.gitignore b/functions/.idea/.gitignore similarity index 100% rename from functions/rapi/.idea/.gitignore rename to functions/.idea/.gitignore diff --git a/functions/rapi/.idea/codeStyles/codeStyleConfig.xml b/functions/.idea/codeStyles/codeStyleConfig.xml similarity index 100% rename from functions/rapi/.idea/codeStyles/codeStyleConfig.xml rename to functions/.idea/codeStyles/codeStyleConfig.xml diff --git a/functions/rapi/.idea/dictionaries/javinator9889.xml b/functions/.idea/dictionaries/javinator9889.xml similarity index 100% rename from functions/rapi/.idea/dictionaries/javinator9889.xml rename to functions/.idea/dictionaries/javinator9889.xml diff --git a/functions/rapi/.idea/inspectionProfiles/Project_Default.xml b/functions/.idea/inspectionProfiles/Project_Default.xml similarity index 100% rename from functions/rapi/.idea/inspectionProfiles/Project_Default.xml rename to functions/.idea/inspectionProfiles/Project_Default.xml diff --git a/functions/rapi/.idea/jsLibraryMappings.xml b/functions/.idea/jsLibraryMappings.xml similarity index 100% rename from functions/rapi/.idea/jsLibraryMappings.xml rename to functions/.idea/jsLibraryMappings.xml diff --git a/functions/rapi/.idea/misc.xml b/functions/.idea/misc.xml similarity index 100% rename from functions/rapi/.idea/misc.xml rename to functions/.idea/misc.xml diff --git a/functions/rapi/.idea/modules.xml b/functions/.idea/modules.xml similarity index 100% rename from functions/rapi/.idea/modules.xml rename to functions/.idea/modules.xml diff --git a/functions/rapi/.idea/vcs.xml b/functions/.idea/vcs.xml similarity index 63% rename from functions/rapi/.idea/vcs.xml rename to functions/.idea/vcs.xml index b2bdec2..6c0b863 100644 --- a/functions/rapi/.idea/vcs.xml +++ b/functions/.idea/vcs.xml @@ -1,6 +1,6 @@ - + \ No newline at end of file diff --git a/functions/rapi/.idea/watcherTasks.xml b/functions/.idea/watcherTasks.xml similarity index 100% rename from functions/rapi/.idea/watcherTasks.xml rename to functions/.idea/watcherTasks.xml diff --git a/functions/rapi/lib/tsconfig.tsbuildinfo b/functions/lib/tsconfig.tsbuildinfo similarity index 100% rename from functions/rapi/lib/tsconfig.tsbuildinfo rename to functions/lib/tsconfig.tsbuildinfo diff --git a/functions/rapi/lib/views/error.pug b/functions/lib/views/error.pug similarity index 100% rename from functions/rapi/lib/views/error.pug rename to functions/lib/views/error.pug diff --git a/functions/rapi/lib/views/layout.pug b/functions/lib/views/layout.pug similarity index 100% rename from functions/rapi/lib/views/layout.pug rename to functions/lib/views/layout.pug diff --git a/functions/rapi/package-lock.json b/functions/package-lock.json similarity index 100% rename from functions/rapi/package-lock.json rename to functions/package-lock.json diff --git a/functions/rapi/package.json b/functions/package.json similarity index 100% rename from functions/rapi/package.json rename to functions/package.json diff --git a/functions/rapi/src/app.ts b/functions/src/app.ts similarity index 100% rename from functions/rapi/src/app.ts rename to functions/src/app.ts diff --git a/functions/rapi/src/bin/www.ts b/functions/src/bin/www.ts similarity index 100% rename from functions/rapi/src/bin/www.ts rename to functions/src/bin/www.ts diff --git a/functions/rapi/src/controllers/newsriver.ts b/functions/src/controllers/newsriver.ts similarity index 100% rename from functions/rapi/src/controllers/newsriver.ts rename to functions/src/controllers/newsriver.ts diff --git a/functions/rapi/src/interfaces/projectProperties.ts b/functions/src/interfaces/projectProperties.ts similarity index 100% rename from functions/rapi/src/interfaces/projectProperties.ts rename to functions/src/interfaces/projectProperties.ts diff --git a/functions/rapi/src/models/newsriver.ts b/functions/src/models/newsriver.ts similarity index 100% rename from functions/rapi/src/models/newsriver.ts rename to functions/src/models/newsriver.ts diff --git a/functions/rapi/src/newsriver.ts b/functions/src/newsriver.ts similarity index 100% rename from functions/rapi/src/newsriver.ts rename to functions/src/newsriver.ts diff --git a/functions/rapi/src/public/stylesheets/style.css b/functions/src/public/stylesheets/style.css similarity index 100% rename from functions/rapi/src/public/stylesheets/style.css rename to functions/src/public/stylesheets/style.css diff --git a/functions/rapi/src/rcdata.ts b/functions/src/rcdata.ts similarity index 100% rename from functions/rapi/src/rcdata.ts rename to functions/src/rcdata.ts diff --git a/functions/rapi/src/routes/newsriver.ts b/functions/src/routes/newsriver.ts similarity index 100% rename from functions/rapi/src/routes/newsriver.ts rename to functions/src/routes/newsriver.ts diff --git a/functions/rapi/src/updater.ts b/functions/src/updater.ts similarity index 100% rename from functions/rapi/src/updater.ts rename to functions/src/updater.ts diff --git a/functions/rapi/src/views/error.pug b/functions/src/views/error.pug similarity index 100% rename from functions/rapi/src/views/error.pug rename to functions/src/views/error.pug diff --git a/functions/rapi/src/views/layout.pug b/functions/src/views/layout.pug similarity index 100% rename from functions/rapi/src/views/layout.pug rename to functions/src/views/layout.pug diff --git a/functions/rapi/tsconfig.json b/functions/tsconfig.json similarity index 100% rename from functions/rapi/tsconfig.json rename to functions/tsconfig.json diff --git a/functions/rapi/tslint.json b/functions/tslint.json similarity index 100% rename from functions/rapi/tslint.json rename to functions/tslint.json From 1ae8d16a637ec24f4a6d1e1d26cc2cf585105289 Mon Sep 17 00:00:00 2001 From: Javinator9889 Date: Mon, 8 Jun 2020 17:46:17 +0200 Subject: [PATCH 25/95] Updated project for managing environment variables and exporting only the necessary functions --- firebase.json | 7 +- functions/daemon.json | 9 + functions/package-lock.json | 614 +++++------------------------------- functions/package.json | 13 +- functions/src/bin/daemon.ts | 0 functions/src/bin/index.ts | 12 + functions/src/bin/www.ts | 3 +- functions/src/database.ts | 13 + 8 files changed, 132 insertions(+), 539 deletions(-) create mode 100644 functions/daemon.json create mode 100644 functions/src/bin/daemon.ts create mode 100644 functions/src/bin/index.ts create mode 100644 functions/src/database.ts diff --git a/firebase.json b/firebase.json index 0db4534..01f98cc 100644 --- a/firebase.json +++ b/firebase.json @@ -3,7 +3,8 @@ "predeploy": [ "npm --prefix \"$RESOURCE_DIR\" run lint", "npm --prefix \"$RESOURCE_DIR\" run build" - ] + ], + "source": "functions" }, "hosting": { "public": "public", @@ -12,10 +13,10 @@ "**/.*", "**/node_modules/**" ], - "rewrites": [ + "rewrites": [ { "source": "/api/v1/**", - "function": "webApi" + "function": "wwww.webApi" } ] } diff --git a/functions/daemon.json b/functions/daemon.json new file mode 100644 index 0000000..2c41e72 --- /dev/null +++ b/functions/daemon.json @@ -0,0 +1,9 @@ +{ + "apps": [ + { + "name": "Newsriver-daemon", + "script": "npm", + "args": "run updater" + } + ] +} \ No newline at end of file diff --git a/functions/package-lock.json b/functions/package-lock.json index 833ca3e..70c10b2 100644 --- a/functions/package-lock.json +++ b/functions/package-lock.json @@ -51,109 +51,16 @@ } } }, - "@firebase/analytics": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/@firebase/analytics/-/analytics-0.3.6.tgz", - "integrity": "sha512-OgBPLsLcYSLBCBLMkuOPnW2YmGo0ruVBtnZ1Yhk0y54oCtrAcm0ijuI98h0evAdA7XfHPgmfKRpke9rU2X9OQQ==", - "requires": { - "@firebase/analytics-types": "0.3.1", - "@firebase/component": "0.1.13", - "@firebase/installations": "0.4.11", - "@firebase/logger": "0.2.5", - "@firebase/util": "0.2.48", - "tslib": "1.11.1" - }, - "dependencies": { - "@firebase/component": { - "version": "0.1.13", - "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.13.tgz", - "integrity": "sha512-DuSIM96NQkE3Yo77IOa5BWw8VBdvCR5cbMLNiFT4X3dTU15Dm0zHjncQHt/6rQpABGNYWAfOCJmSU1v6vc3DFA==", - "requires": { - "@firebase/util": "0.2.48", - "tslib": "1.11.1" - } - }, - "@firebase/logger": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.2.5.tgz", - "integrity": "sha512-qqw3m0tWs/qrg7axTZG/QZq24DIMdSY6dGoWuBn08ddq7+GLF5HiqkRj71XznYeUUbfRq5W9C/PSFnN4JxX+WA==" - }, - "@firebase/util": { - "version": "0.2.48", - "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.2.48.tgz", - "integrity": "sha512-6Wzq6IBF//3mrMTmTQ+JmceM0PMQpxV2GVfXhZn/4sMMkkhB0MA908nPDnatoHwUKyWE3BMw+uTLkyBnkuTu5A==", - "requires": { - "tslib": "1.11.1" - } - } - } - }, - "@firebase/analytics-types": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@firebase/analytics-types/-/analytics-types-0.3.1.tgz", - "integrity": "sha512-63vVJ5NIBh/JF8l9LuPrQYSzFimk7zYHySQB4Dk9rVdJ8kV/vGQoVTvRu1UW05sEc2Ug5PqtEChtTHU+9hvPcA==" - }, - "@firebase/app": { - "version": "0.6.5", - "resolved": "https://registry.npmjs.org/@firebase/app/-/app-0.6.5.tgz", - "integrity": "sha512-rhId5P4egyaVp4HaLsqxV1dJYWbjAyyTnoW8r6t2uXyUGBKUiv+tdK97abkHCo4UQwq/GvUu0Drd96Cm7nEIeA==", - "requires": { - "@firebase/app-types": "0.6.1", - "@firebase/component": "0.1.13", - "@firebase/logger": "0.2.5", - "@firebase/util": "0.2.48", - "dom-storage": "2.1.0", - "tslib": "1.11.1", - "xmlhttprequest": "1.8.0" - }, - "dependencies": { - "@firebase/component": { - "version": "0.1.13", - "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.13.tgz", - "integrity": "sha512-DuSIM96NQkE3Yo77IOa5BWw8VBdvCR5cbMLNiFT4X3dTU15Dm0zHjncQHt/6rQpABGNYWAfOCJmSU1v6vc3DFA==", - "requires": { - "@firebase/util": "0.2.48", - "tslib": "1.11.1" - } - }, - "@firebase/logger": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.2.5.tgz", - "integrity": "sha512-qqw3m0tWs/qrg7axTZG/QZq24DIMdSY6dGoWuBn08ddq7+GLF5HiqkRj71XznYeUUbfRq5W9C/PSFnN4JxX+WA==" - }, - "@firebase/util": { - "version": "0.2.48", - "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.2.48.tgz", - "integrity": "sha512-6Wzq6IBF//3mrMTmTQ+JmceM0PMQpxV2GVfXhZn/4sMMkkhB0MA908nPDnatoHwUKyWE3BMw+uTLkyBnkuTu5A==", - "requires": { - "tslib": "1.11.1" - } - } - } - }, "@firebase/app-types": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.6.1.tgz", "integrity": "sha512-L/ZnJRAq7F++utfuoTKX4CLBG5YR7tFO3PLzG1/oXXKEezJ0kRL3CMRoueBEmTCzVb/6SIs2Qlaw++uDgi5Xyg==" }, - "@firebase/auth": { - "version": "0.14.6", - "resolved": "https://registry.npmjs.org/@firebase/auth/-/auth-0.14.6.tgz", - "integrity": "sha512-7gaEUWhUubWBGfOXAZvpTpJqBJT9KyG83RXC6VnjSQIfNUaarHZ485WkzERil43A6KvIl+f4kHxfZShE6ZCK3A==", - "requires": { - "@firebase/auth-types": "0.10.1" - } - }, "@firebase/auth-interop-types": { "version": "0.1.5", "resolved": "https://registry.npmjs.org/@firebase/auth-interop-types/-/auth-interop-types-0.1.5.tgz", "integrity": "sha512-88h74TMQ6wXChPA6h9Q3E1Jg6TkTHep2+k63OWg3s0ozyGVMeY+TTOti7PFPzq5RhszQPQOoCi59es4MaRvgCw==" }, - "@firebase/auth-types": { - "version": "0.10.1", - "resolved": "https://registry.npmjs.org/@firebase/auth-types/-/auth-types-0.10.1.tgz", - "integrity": "sha512-/+gBHb1O9x/YlG7inXfxff/6X3BPZt4zgBv4kql6HEmdzNQCodIRlEYnI+/da+lN+dha7PjaFH7C7ewMmfV7rw==" - }, "@firebase/component": { "version": "0.1.12", "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.12.tgz", @@ -185,308 +92,11 @@ "@firebase/app-types": "0.6.1" } }, - "@firebase/firestore": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/@firebase/firestore/-/firestore-1.15.0.tgz", - "integrity": "sha512-4eSBDY2hb/og8OEFZSjjzlb0y5+cWpridtQHLYsM4IPcOwxnnlE6RjGWN+UUxgKtvlkvOBdUIlTjL/YYoF+QcA==", - "requires": { - "@firebase/component": "0.1.13", - "@firebase/firestore-types": "1.11.0", - "@firebase/logger": "0.2.5", - "@firebase/util": "0.2.48", - "@firebase/webchannel-wrapper": "0.2.41", - "@grpc/grpc-js": "0.8.1", - "@grpc/proto-loader": "^0.5.0", - "tslib": "1.11.1" - }, - "dependencies": { - "@firebase/component": { - "version": "0.1.13", - "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.13.tgz", - "integrity": "sha512-DuSIM96NQkE3Yo77IOa5BWw8VBdvCR5cbMLNiFT4X3dTU15Dm0zHjncQHt/6rQpABGNYWAfOCJmSU1v6vc3DFA==", - "requires": { - "@firebase/util": "0.2.48", - "tslib": "1.11.1" - } - }, - "@firebase/logger": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.2.5.tgz", - "integrity": "sha512-qqw3m0tWs/qrg7axTZG/QZq24DIMdSY6dGoWuBn08ddq7+GLF5HiqkRj71XznYeUUbfRq5W9C/PSFnN4JxX+WA==" - }, - "@firebase/util": { - "version": "0.2.48", - "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.2.48.tgz", - "integrity": "sha512-6Wzq6IBF//3mrMTmTQ+JmceM0PMQpxV2GVfXhZn/4sMMkkhB0MA908nPDnatoHwUKyWE3BMw+uTLkyBnkuTu5A==", - "requires": { - "tslib": "1.11.1" - } - }, - "@grpc/grpc-js": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-0.8.1.tgz", - "integrity": "sha512-e8gSjRZnOUefsR3obOgxG9RtYW2Mw83hh7ogE2ByCdgRhoX0mdnJwBcZOami3E0l643KCTZvORFwfSEi48KFIQ==", - "requires": { - "semver": "^6.2.0" - } - } - } - }, - "@firebase/firestore-types": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@firebase/firestore-types/-/firestore-types-1.11.0.tgz", - "integrity": "sha512-hD7+cmMUvT5OJeWVrcRkE87PPuj/0/Wic6bntCopJE1WIX/Dm117AUkHgKd3S7Ici6DLp4bdlx1MjjwWL5942w==" - }, - "@firebase/functions": { - "version": "0.4.45", - "resolved": "https://registry.npmjs.org/@firebase/functions/-/functions-0.4.45.tgz", - "integrity": "sha512-Sy0D+52bkabdapTGxPlX+1b2FH+0BEJBmboLfM7EySZV/32oI277pDYKhyp9Pm//eOLspMOpEDvJz1WK8xmQcw==", - "requires": { - "@firebase/component": "0.1.13", - "@firebase/functions-types": "0.3.17", - "@firebase/messaging-types": "0.4.5", - "isomorphic-fetch": "2.2.1", - "tslib": "1.11.1" - }, - "dependencies": { - "@firebase/component": { - "version": "0.1.13", - "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.13.tgz", - "integrity": "sha512-DuSIM96NQkE3Yo77IOa5BWw8VBdvCR5cbMLNiFT4X3dTU15Dm0zHjncQHt/6rQpABGNYWAfOCJmSU1v6vc3DFA==", - "requires": { - "@firebase/util": "0.2.48", - "tslib": "1.11.1" - } - }, - "@firebase/util": { - "version": "0.2.48", - "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.2.48.tgz", - "integrity": "sha512-6Wzq6IBF//3mrMTmTQ+JmceM0PMQpxV2GVfXhZn/4sMMkkhB0MA908nPDnatoHwUKyWE3BMw+uTLkyBnkuTu5A==", - "requires": { - "tslib": "1.11.1" - } - } - } - }, - "@firebase/functions-types": { - "version": "0.3.17", - "resolved": "https://registry.npmjs.org/@firebase/functions-types/-/functions-types-0.3.17.tgz", - "integrity": "sha512-DGR4i3VI55KnYk4IxrIw7+VG7Q3gA65azHnZxo98Il8IvYLr2UTBlSh72dTLlDf25NW51HqvJgYJDKvSaAeyHQ==" - }, - "@firebase/installations": { - "version": "0.4.11", - "resolved": "https://registry.npmjs.org/@firebase/installations/-/installations-0.4.11.tgz", - "integrity": "sha512-ri+8O6vZPF0JKXboMzYFAbN7rn0OeUKLeMuCWdKZGJZD8NS8NRk/YvGRBa+IrkrwBeNNA/bMBUTEnhjN4CdVgQ==", - "requires": { - "@firebase/component": "0.1.13", - "@firebase/installations-types": "0.3.4", - "@firebase/util": "0.2.48", - "idb": "3.0.2", - "tslib": "1.11.1" - }, - "dependencies": { - "@firebase/component": { - "version": "0.1.13", - "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.13.tgz", - "integrity": "sha512-DuSIM96NQkE3Yo77IOa5BWw8VBdvCR5cbMLNiFT4X3dTU15Dm0zHjncQHt/6rQpABGNYWAfOCJmSU1v6vc3DFA==", - "requires": { - "@firebase/util": "0.2.48", - "tslib": "1.11.1" - } - }, - "@firebase/util": { - "version": "0.2.48", - "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.2.48.tgz", - "integrity": "sha512-6Wzq6IBF//3mrMTmTQ+JmceM0PMQpxV2GVfXhZn/4sMMkkhB0MA908nPDnatoHwUKyWE3BMw+uTLkyBnkuTu5A==", - "requires": { - "tslib": "1.11.1" - } - } - } - }, - "@firebase/installations-types": { - "version": "0.3.4", - "resolved": "https://registry.npmjs.org/@firebase/installations-types/-/installations-types-0.3.4.tgz", - "integrity": "sha512-RfePJFovmdIXb6rYwtngyxuEcWnOrzdZd9m7xAW0gRxDIjBT20n3BOhjpmgRWXo/DAxRmS7bRjWAyTHY9cqN7Q==" - }, "@firebase/logger": { "version": "0.2.4", "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.2.4.tgz", "integrity": "sha512-akHkOU7izYB1okp/B5sxClGjjw6KvZdSHyjNM5pKd67Zg5W6PsbkI/GFNv21+y6LkUkJwDRbdeDgJoYXWT3mMA==" }, - "@firebase/messaging": { - "version": "0.6.17", - "resolved": "https://registry.npmjs.org/@firebase/messaging/-/messaging-0.6.17.tgz", - "integrity": "sha512-wwAn2HrklhBxHpk5UpudJ0wCrlUC9ovFJ88cSOALf82po423IOwR5ijq1z2zKzZiz4D1dLv7rJIqZ0N1MZ/Giw==", - "requires": { - "@firebase/component": "0.1.13", - "@firebase/installations": "0.4.11", - "@firebase/messaging-types": "0.4.5", - "@firebase/util": "0.2.48", - "idb": "3.0.2", - "tslib": "1.11.1" - }, - "dependencies": { - "@firebase/component": { - "version": "0.1.13", - "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.13.tgz", - "integrity": "sha512-DuSIM96NQkE3Yo77IOa5BWw8VBdvCR5cbMLNiFT4X3dTU15Dm0zHjncQHt/6rQpABGNYWAfOCJmSU1v6vc3DFA==", - "requires": { - "@firebase/util": "0.2.48", - "tslib": "1.11.1" - } - }, - "@firebase/util": { - "version": "0.2.48", - "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.2.48.tgz", - "integrity": "sha512-6Wzq6IBF//3mrMTmTQ+JmceM0PMQpxV2GVfXhZn/4sMMkkhB0MA908nPDnatoHwUKyWE3BMw+uTLkyBnkuTu5A==", - "requires": { - "tslib": "1.11.1" - } - } - } - }, - "@firebase/messaging-types": { - "version": "0.4.5", - "resolved": "https://registry.npmjs.org/@firebase/messaging-types/-/messaging-types-0.4.5.tgz", - "integrity": "sha512-sux4fgqr/0KyIxqzHlatI04Ajs5rc3WM+WmtCpxrKP1E5Bke8xu/0M+2oy4lK/sQ7nov9z15n3iltAHCgTRU3Q==" - }, - "@firebase/performance": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/@firebase/performance/-/performance-0.3.6.tgz", - "integrity": "sha512-AB+ohBYgF8fe9FacDAcwJaBLRrXgt93no6Pj14xzQ4oX31dQpPrWZdFfNYEUZRU1Gnb/fqWlCaBTObzUXD5cag==", - "requires": { - "@firebase/component": "0.1.13", - "@firebase/installations": "0.4.11", - "@firebase/logger": "0.2.5", - "@firebase/performance-types": "0.0.13", - "@firebase/util": "0.2.48", - "tslib": "1.11.1" - }, - "dependencies": { - "@firebase/component": { - "version": "0.1.13", - "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.13.tgz", - "integrity": "sha512-DuSIM96NQkE3Yo77IOa5BWw8VBdvCR5cbMLNiFT4X3dTU15Dm0zHjncQHt/6rQpABGNYWAfOCJmSU1v6vc3DFA==", - "requires": { - "@firebase/util": "0.2.48", - "tslib": "1.11.1" - } - }, - "@firebase/logger": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.2.5.tgz", - "integrity": "sha512-qqw3m0tWs/qrg7axTZG/QZq24DIMdSY6dGoWuBn08ddq7+GLF5HiqkRj71XznYeUUbfRq5W9C/PSFnN4JxX+WA==" - }, - "@firebase/util": { - "version": "0.2.48", - "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.2.48.tgz", - "integrity": "sha512-6Wzq6IBF//3mrMTmTQ+JmceM0PMQpxV2GVfXhZn/4sMMkkhB0MA908nPDnatoHwUKyWE3BMw+uTLkyBnkuTu5A==", - "requires": { - "tslib": "1.11.1" - } - } - } - }, - "@firebase/performance-types": { - "version": "0.0.13", - "resolved": "https://registry.npmjs.org/@firebase/performance-types/-/performance-types-0.0.13.tgz", - "integrity": "sha512-6fZfIGjQpwo9S5OzMpPyqgYAUZcFzZxHFqOyNtorDIgNXq33nlldTL/vtaUZA8iT9TT5cJlCrF/jthKU7X21EA==" - }, - "@firebase/polyfill": { - "version": "0.3.36", - "resolved": "https://registry.npmjs.org/@firebase/polyfill/-/polyfill-0.3.36.tgz", - "integrity": "sha512-zMM9oSJgY6cT2jx3Ce9LYqb0eIpDE52meIzd/oe/y70F+v9u1LDqk5kUF5mf16zovGBWMNFmgzlsh6Wj0OsFtg==", - "requires": { - "core-js": "3.6.5", - "promise-polyfill": "8.1.3", - "whatwg-fetch": "2.0.4" - }, - "dependencies": { - "whatwg-fetch": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-2.0.4.tgz", - "integrity": "sha512-dcQ1GWpOD/eEQ97k66aiEVpNnapVj90/+R+SXTPYGHpYBBypfKJEQjLrvMZ7YXbKm21gXd4NcuxUTjiv1YtLng==" - } - } - }, - "@firebase/remote-config": { - "version": "0.1.22", - "resolved": "https://registry.npmjs.org/@firebase/remote-config/-/remote-config-0.1.22.tgz", - "integrity": "sha512-xX/b+euI/RP1qAWqNI5YkZ4VFNL00CI7jBJmPzoBbSl1vVglR1ya7u1fbC5SeowxnD1/0QIOYbe5sdnqjmLsyg==", - "requires": { - "@firebase/component": "0.1.13", - "@firebase/installations": "0.4.11", - "@firebase/logger": "0.2.5", - "@firebase/remote-config-types": "0.1.9", - "@firebase/util": "0.2.48", - "tslib": "1.11.1" - }, - "dependencies": { - "@firebase/component": { - "version": "0.1.13", - "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.13.tgz", - "integrity": "sha512-DuSIM96NQkE3Yo77IOa5BWw8VBdvCR5cbMLNiFT4X3dTU15Dm0zHjncQHt/6rQpABGNYWAfOCJmSU1v6vc3DFA==", - "requires": { - "@firebase/util": "0.2.48", - "tslib": "1.11.1" - } - }, - "@firebase/logger": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.2.5.tgz", - "integrity": "sha512-qqw3m0tWs/qrg7axTZG/QZq24DIMdSY6dGoWuBn08ddq7+GLF5HiqkRj71XznYeUUbfRq5W9C/PSFnN4JxX+WA==" - }, - "@firebase/util": { - "version": "0.2.48", - "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.2.48.tgz", - "integrity": "sha512-6Wzq6IBF//3mrMTmTQ+JmceM0PMQpxV2GVfXhZn/4sMMkkhB0MA908nPDnatoHwUKyWE3BMw+uTLkyBnkuTu5A==", - "requires": { - "tslib": "1.11.1" - } - } - } - }, - "@firebase/remote-config-types": { - "version": "0.1.9", - "resolved": "https://registry.npmjs.org/@firebase/remote-config-types/-/remote-config-types-0.1.9.tgz", - "integrity": "sha512-G96qnF3RYGbZsTRut7NBX0sxyczxt1uyCgXQuH/eAfUCngxjEGcZQnBdy6mvSdqdJh5mC31rWPO4v9/s7HwtzA==" - }, - "@firebase/storage": { - "version": "0.3.35", - "resolved": "https://registry.npmjs.org/@firebase/storage/-/storage-0.3.35.tgz", - "integrity": "sha512-kS+P5X3lla9bpeWVwmRzJ5atMDPlhLPa8jgutN9vWXWfVnlj82U8VqeAqWc8ndHumHiV0TYLDk9DdGfs6rFL3w==", - "requires": { - "@firebase/component": "0.1.13", - "@firebase/storage-types": "0.3.12", - "@firebase/util": "0.2.48", - "tslib": "1.11.1" - }, - "dependencies": { - "@firebase/component": { - "version": "0.1.13", - "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.13.tgz", - "integrity": "sha512-DuSIM96NQkE3Yo77IOa5BWw8VBdvCR5cbMLNiFT4X3dTU15Dm0zHjncQHt/6rQpABGNYWAfOCJmSU1v6vc3DFA==", - "requires": { - "@firebase/util": "0.2.48", - "tslib": "1.11.1" - } - }, - "@firebase/util": { - "version": "0.2.48", - "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.2.48.tgz", - "integrity": "sha512-6Wzq6IBF//3mrMTmTQ+JmceM0PMQpxV2GVfXhZn/4sMMkkhB0MA908nPDnatoHwUKyWE3BMw+uTLkyBnkuTu5A==", - "requires": { - "tslib": "1.11.1" - } - } - } - }, - "@firebase/storage-types": { - "version": "0.3.12", - "resolved": "https://registry.npmjs.org/@firebase/storage-types/-/storage-types-0.3.12.tgz", - "integrity": "sha512-DDV6Fs6aYoGw3w/zZZTkqiipxihnsvHf6znbeZYjIIHit3tr1uLJdGPDPiCTfZcTGPpg2ux6ZmvNDvVgJdHALw==" - }, "@firebase/util": { "version": "0.2.47", "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.2.47.tgz", @@ -495,11 +105,6 @@ "tslib": "1.11.1" } }, - "@firebase/webchannel-wrapper": { - "version": "0.2.41", - "resolved": "https://registry.npmjs.org/@firebase/webchannel-wrapper/-/webchannel-wrapper-0.2.41.tgz", - "integrity": "sha512-XcdMT5PSZHiuf7LJIhzKIe+RyYa25S3LHRRvLnZc6iFjwXkrSDJ8J/HWO6VT8d2ZTbawp3VcLEjRF/VN8glCrA==" - }, "@google-cloud/common": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/@google-cloud/common/-/common-2.4.0.tgz", @@ -615,6 +220,7 @@ "version": "0.5.4", "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.5.4.tgz", "integrity": "sha512-HTM4QpI9B2XFkPz7pjwMyMgZchJ93TVkL3kWPW8GDMDKYxsMnmf4w2TNMJK7+KNiYHS5cJrCEAFlF+AwtXWVPA==", + "optional": true, "requires": { "lodash.camelcase": "^4.3.0", "protobufjs": "^6.8.6" @@ -623,27 +229,32 @@ "@protobufjs/aspromise": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", - "integrity": "sha1-m4sMxmPWaafY9vXQiToU00jzD78=" + "integrity": "sha1-m4sMxmPWaafY9vXQiToU00jzD78=", + "optional": true }, "@protobufjs/base64": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", - "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==" + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", + "optional": true }, "@protobufjs/codegen": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", - "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==" + "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==", + "optional": true }, "@protobufjs/eventemitter": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", - "integrity": "sha1-NVy8mLr61ZePntCV85diHx0Ga3A=" + "integrity": "sha1-NVy8mLr61ZePntCV85diHx0Ga3A=", + "optional": true }, "@protobufjs/fetch": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", "integrity": "sha1-upn7WYYUr2VwDBYZ/wbUVLDYTEU=", + "optional": true, "requires": { "@protobufjs/aspromise": "^1.1.1", "@protobufjs/inquire": "^1.1.0" @@ -652,27 +263,32 @@ "@protobufjs/float": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", - "integrity": "sha1-Xp4avctz/Ap8uLKR33jIy9l7h9E=" + "integrity": "sha1-Xp4avctz/Ap8uLKR33jIy9l7h9E=", + "optional": true }, "@protobufjs/inquire": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", - "integrity": "sha1-/yAOPnzyQp4tyvwRQIKOjMY48Ik=" + "integrity": "sha1-/yAOPnzyQp4tyvwRQIKOjMY48Ik=", + "optional": true }, "@protobufjs/path": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", - "integrity": "sha1-bMKyDFya1q0NzP0hynZz2Nf79o0=" + "integrity": "sha1-bMKyDFya1q0NzP0hynZz2Nf79o0=", + "optional": true }, "@protobufjs/pool": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", - "integrity": "sha1-Cf0V8tbTq/qbZbw2ZQbWrXhG/1Q=" + "integrity": "sha1-Cf0V8tbTq/qbZbw2ZQbWrXhG/1Q=", + "optional": true }, "@protobufjs/utf8": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", - "integrity": "sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA=" + "integrity": "sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA=", + "optional": true }, "@tootallnate/once": { "version": "1.1.2", @@ -736,7 +352,8 @@ "@types/long": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.1.tgz", - "integrity": "sha512-5tXH6Bx/kNGd3MgffdmP4dy2Z+G4eaXw0SE81Tq3BNadtnMR5/ySMzX4SLEzHJzSmPNn4HIdpQsBvXMUykr58w==" + "integrity": "sha512-5tXH6Bx/kNGd3MgffdmP4dy2Z+G4eaXw0SE81Tq3BNadtnMR5/ySMzX4SLEzHJzSmPNn4HIdpQsBvXMUykr58w==", + "optional": true }, "@types/mime": { "version": "2.0.2", @@ -1118,11 +735,6 @@ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" }, - "core-js": { - "version": "3.6.5", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.6.5.tgz", - "integrity": "sha512-vZVEEwZoIsI+vPEuoF9Iqf5H7/M3eeQqWlQnYa8FSKKePuYTf5MWnxb5SDAzCa60b3JBRS5g9b+Dq7b1y/RCrA==" - }, "core-util-is": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", @@ -1138,6 +750,26 @@ "vary": "^1" } }, + "cross-env": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.2.tgz", + "integrity": "sha512-KZP/bMEOJEDCkDQAyRhu3RL2ZO/SUVrxQVI0G3YEQ+OLbRA3c6zgixe8Mq8a/z7+HKlNEjo8oiLUs8iRijY2Rw==", + "dev": true, + "requires": { + "cross-spawn": "^7.0.1" + } + }, + "cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, "crypto-random-string": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz", @@ -1226,11 +858,6 @@ "resolved": "https://registry.npmjs.org/doctypes/-/doctypes-1.1.0.tgz", "integrity": "sha1-6oCxBqh1OHdOijpKWv4pPeSJ4Kk=" }, - "dom-storage": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/dom-storage/-/dom-storage-2.1.0.tgz", - "integrity": "sha512-g6RpyWXzl0RR6OTElHKBl7nwnK87GUyZMYC7JWsB/IA73vpqK2K6LT39x4VepLxlSsWBFrPVLnsSR5Jyty0+2Q==" - }, "dot-prop": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.2.0.tgz", @@ -1299,14 +926,6 @@ "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" }, - "encoding": { - "version": "0.1.12", - "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.12.tgz", - "integrity": "sha1-U4tm8+5izRq1HsMjgp0flIDHS+s=", - "requires": { - "iconv-lite": "~0.4.13" - } - }, "end-of-stream": { "version": "1.4.4", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", @@ -1499,65 +1118,6 @@ } } }, - "firebase": { - "version": "7.15.0", - "resolved": "https://registry.npmjs.org/firebase/-/firebase-7.15.0.tgz", - "integrity": "sha512-0t8w/TT+230/n/XWBw9jGMApCkIEb5K1b+6t4R+SAMOZHMJZvoAwMcwgQXoYeUBFOJOQpgDhIZK8PzApq4iUXw==", - "requires": { - "@firebase/analytics": "0.3.6", - "@firebase/app": "0.6.5", - "@firebase/app-types": "0.6.1", - "@firebase/auth": "0.14.6", - "@firebase/database": "0.6.4", - "@firebase/firestore": "1.15.0", - "@firebase/functions": "0.4.45", - "@firebase/installations": "0.4.11", - "@firebase/messaging": "0.6.17", - "@firebase/performance": "0.3.6", - "@firebase/polyfill": "0.3.36", - "@firebase/remote-config": "0.1.22", - "@firebase/storage": "0.3.35", - "@firebase/util": "0.2.48" - }, - "dependencies": { - "@firebase/component": { - "version": "0.1.13", - "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.13.tgz", - "integrity": "sha512-DuSIM96NQkE3Yo77IOa5BWw8VBdvCR5cbMLNiFT4X3dTU15Dm0zHjncQHt/6rQpABGNYWAfOCJmSU1v6vc3DFA==", - "requires": { - "@firebase/util": "0.2.48", - "tslib": "1.11.1" - } - }, - "@firebase/database": { - "version": "0.6.4", - "resolved": "https://registry.npmjs.org/@firebase/database/-/database-0.6.4.tgz", - "integrity": "sha512-m3jaElEEXhr3a9D+M/kbDuRCQG5EmrnSqyEq7iNk3s5ankIrALid0AYm2RZF764F/DIeMFtAzng4EyyEqsaQlQ==", - "requires": { - "@firebase/auth-interop-types": "0.1.5", - "@firebase/component": "0.1.13", - "@firebase/database-types": "0.5.1", - "@firebase/logger": "0.2.5", - "@firebase/util": "0.2.48", - "faye-websocket": "0.11.3", - "tslib": "1.11.1" - } - }, - "@firebase/logger": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.2.5.tgz", - "integrity": "sha512-qqw3m0tWs/qrg7axTZG/QZq24DIMdSY6dGoWuBn08ddq7+GLF5HiqkRj71XznYeUUbfRq5W9C/PSFnN4JxX+WA==" - }, - "@firebase/util": { - "version": "0.2.48", - "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.2.48.tgz", - "integrity": "sha512-6Wzq6IBF//3mrMTmTQ+JmceM0PMQpxV2GVfXhZn/4sMMkkhB0MA908nPDnatoHwUKyWE3BMw+uTLkyBnkuTu5A==", - "requires": { - "tslib": "1.11.1" - } - } - } - }, "firebase-admin": { "version": "8.12.1", "resolved": "https://registry.npmjs.org/firebase-admin/-/firebase-admin-8.12.1.tgz", @@ -1990,11 +1550,6 @@ "safer-buffer": ">= 2.1.2 < 3" } }, - "idb": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/idb/-/idb-3.0.2.tgz", - "integrity": "sha512-+FLa/0sTXqyux0o6C+i2lOR0VoS60LU/jzUo5xjfY6+7sEEgy4Gz1O7yFBXvjd7N0NyIGWIRg8DcQSLEG+VSPw==" - }, "imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", @@ -2157,30 +1712,11 @@ "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", "optional": true }, - "isomorphic-fetch": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz", - "integrity": "sha1-YRrhrPFPXoH3KVB0coGf6XM1WKk=", - "requires": { - "node-fetch": "^1.0.1", - "whatwg-fetch": ">=0.10.0" - }, - "dependencies": { - "is-stream": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=" - }, - "node-fetch": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-1.7.3.tgz", - "integrity": "sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ==", - "requires": { - "encoding": "^0.1.11", - "is-stream": "^1.0.1" - } - } - } + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true }, "js-stringify": { "version": "1.0.2", @@ -2294,7 +1830,8 @@ "lodash.camelcase": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", - "integrity": "sha1-soqmKIorn8ZRA1x3EfZathkDMaY=" + "integrity": "sha1-soqmKIorn8ZRA1x3EfZathkDMaY=", + "optional": true }, "lodash.has": { "version": "4.5.2", @@ -2340,7 +1877,8 @@ "long": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", - "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" + "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==", + "optional": true }, "lru-cache": { "version": "5.1.1", @@ -2568,6 +2106,12 @@ "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", "dev": true }, + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true + }, "path-parse": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", @@ -2597,15 +2141,11 @@ "asap": "~2.0.3" } }, - "promise-polyfill": { - "version": "8.1.3", - "resolved": "https://registry.npmjs.org/promise-polyfill/-/promise-polyfill-8.1.3.tgz", - "integrity": "sha512-MG5r82wBzh7pSKDRa9y+vllNHz3e3d4CNj1PQE4BQYxLme0gKYYBm9YENq+UkEikyZ0XbiGWxYlVw3Rl9O/U8g==" - }, "protobufjs": { "version": "6.9.0", "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.9.0.tgz", "integrity": "sha512-LlGVfEWDXoI/STstRDdZZKb/qusoAWUnmLg9R8OLSO473mBLWHowx8clbX5/+mKDEI+v7GzjoK9tRPZMMcoTrg==", + "optional": true, "requires": { "@protobufjs/aspromise": "^1.1.2", "@protobufjs/base64": "^1.1.2", @@ -2625,7 +2165,8 @@ "@types/node": { "version": "13.13.9", "resolved": "https://registry.npmjs.org/@types/node/-/node-13.13.9.tgz", - "integrity": "sha512-EPZBIGed5gNnfWCiwEIwTE2Jdg4813odnG8iNPMQGrqVxrI+wL68SPtPeCX+ZxGBaA6pKAVc6jaKgP/Q0QzfdQ==" + "integrity": "sha512-EPZBIGed5gNnfWCiwEIwTE2Jdg4813odnG8iNPMQGrqVxrI+wL68SPtPeCX+ZxGBaA6pKAVc6jaKgP/Q0QzfdQ==", + "optional": true } } }, @@ -2877,7 +2418,8 @@ "semver": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "optional": true }, "send": { "version": "0.17.1", @@ -2942,6 +2484,21 @@ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true + }, "side-channel": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.2.tgz", @@ -3242,10 +2799,14 @@ "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.3.tgz", "integrity": "sha512-nqHUnMXmBzT0w570r2JpJxfiSD1IzoI+HGVdd3aZ0yNi3ngvQ4jv1dtHt5VGxfI2yj5yqImPhOK4vmIh2xMbGg==" }, - "whatwg-fetch": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.0.0.tgz", - "integrity": "sha512-9GSJUgz1D4MfyKU7KRqwOjXCXTqWdFNvEr7eUBYchQiVc744mqK/MzXPNR2WsPkmkOa4ywfg8C2n8h+13Bey1Q==" + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } }, "which-boxed-primitive": { "version": "1.0.1", @@ -3320,11 +2881,6 @@ "integrity": "sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==", "optional": true }, - "xmlhttprequest": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/xmlhttprequest/-/xmlhttprequest-1.8.0.tgz", - "integrity": "sha1-Z/4HXFwk/vOfnWX197f+dRcZaPw=" - }, "xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", diff --git a/functions/package.json b/functions/package.json index 20489d1..04f2652 100644 --- a/functions/package.json +++ b/functions/package.json @@ -3,18 +3,18 @@ "scripts": { "lint": "tslint --project tsconfig.json", "build": "tsc", - "serve": "npm run build && firebase emulators:start --only functions", + "serve": "cross-env-shell RUN_SERVER=true RUN_DAEMON=true \"npm run build && firebase emulators:start --only functions\"", "shell": "npm run build && firebase functions:shell", "start": "npm run shell", "deploy": "firebase deploy --only functions", "logs": "firebase functions:log", - "server": "npm run build && node lib/bin/www.js", - "updater": "npm run build && firebase emulators:start --only functions" + "server": "cross-env-shell RUN_SERVER=true \"npm run build && firebase emulators:start --only functions\"", + "daemon": "cross-env-shell RUN_DAEMON=true \"npm run build && firebase emulators:start --only functions\"" }, "engines": { "node": "8" }, - "main": "lib/bin/www.js", + "main": "lib/bin/index.js", "dependencies": { "body-parser": "^1.19.0", "express": "^4.17.1", @@ -27,9 +27,10 @@ "pug": "^3.0.0" }, "devDependencies": { + "cross-env": "^7.0.2", + "firebase-functions-test": "^0.2.0", "tslint": "^5.12.0", - "typescript": "^3.8.0", - "firebase-functions-test": "^0.2.0" + "typescript": "^3.8.0" }, "private": true } diff --git a/functions/src/bin/daemon.ts b/functions/src/bin/daemon.ts new file mode 100644 index 0000000..e69de29 diff --git a/functions/src/bin/index.ts b/functions/src/bin/index.ts new file mode 100644 index 0000000..e04036a --- /dev/null +++ b/functions/src/bin/index.ts @@ -0,0 +1,12 @@ +const runServer = process.env.RUN_SERVER; +const runDaemon = process.env.RUN_DAEMON; + +if (runServer === undefined && runDaemon === undefined) { + exports.www = require('./www'); + exports.daemon = require('./daemon'); +} else { + if (runServer) + exports.www = require('./www'); + if (runDaemon) + exports.daemon = require('./daemon'); +} \ No newline at end of file diff --git a/functions/src/bin/www.ts b/functions/src/bin/www.ts index 4055b63..24a8147 100644 --- a/functions/src/bin/www.ts +++ b/functions/src/bin/www.ts @@ -18,7 +18,8 @@ const server = http.createServer(app); */ server.on('error', onError); server.on('listening', onListening); -export const webApi = functions.https.onRequest(app); +if (process.env.RUN_SERVER) + exports.webApi = functions.https.onRequest(app); /** * Normalize a port into a number, string or false diff --git a/functions/src/database.ts b/functions/src/database.ts new file mode 100644 index 0000000..8820fef --- /dev/null +++ b/functions/src/database.ts @@ -0,0 +1,13 @@ +class Database { + private readonly db: FirebaseFirestore.Firestore; + private readonly collectionName: string; + + get collection(): FirebaseFirestore.CollectionReference { + return this.db.collection(this.collectionName); + } + + constructor(db: FirebaseFirestore.Firestore, collectionName: string) { + this.db = db; + this.collectionName = collectionName; + } +} \ No newline at end of file From 2ccbb62a10a0f256ffcaeb5cd177e18aae1a0ca5 Mon Sep 17 00:00:00 2001 From: Javinator9889 Date: Mon, 8 Jun 2020 18:11:10 +0200 Subject: [PATCH 26/95] Moved updater logic out of Newsriver model --- functions/package.json | 3 +- functions/src/common/properties.ts | 14 ++++++++ functions/src/database.ts | 2 +- functions/src/models/newsriver.ts | 53 ++++++------------------------ functions/src/models/updater.ts | 48 +++++++++++++++++++++++++++ functions/src/updater.ts | 4 --- 6 files changed, 75 insertions(+), 49 deletions(-) create mode 100644 functions/src/common/properties.ts create mode 100644 functions/src/models/updater.ts diff --git a/functions/package.json b/functions/package.json index 04f2652..d39f13f 100644 --- a/functions/package.json +++ b/functions/package.json @@ -6,7 +6,8 @@ "serve": "cross-env-shell RUN_SERVER=true RUN_DAEMON=true \"npm run build && firebase emulators:start --only functions\"", "shell": "npm run build && firebase functions:shell", "start": "npm run shell", - "deploy": "firebase deploy --only functions", + "deploy": "cross-env-shell RUN_SERVER=true RUN_DAEMON=true firebase deploy --only functions", + "deploy-server": "cross-env-shell RUN_SERVER=true firebase deploy --only functions:www", "logs": "firebase functions:log", "server": "cross-env-shell RUN_SERVER=true \"npm run build && firebase emulators:start --only functions\"", "daemon": "cross-env-shell RUN_DAEMON=true \"npm run build && firebase emulators:start --only functions\"" diff --git a/functions/src/common/properties.ts b/functions/src/common/properties.ts new file mode 100644 index 0000000..aacd0c4 --- /dev/null +++ b/functions/src/common/properties.ts @@ -0,0 +1,14 @@ +import admin = require('firebase-admin'); +import functions = require('firebase-functions'); +import {ProjectProperties} from "../interfaces/projectProperties"; + + +export const languages = new Set(['es', 'en']); +export function projectProperties(app?: admin.app.App): ProjectProperties { + return { + collection: 'news', + database: admin.firestore(app), + authToken: functions.config().newsriver.key + } +} +export const databaseURL = 'https://handwashing.firebaseio.com'; \ No newline at end of file diff --git a/functions/src/database.ts b/functions/src/database.ts index 8820fef..f8f997a 100644 --- a/functions/src/database.ts +++ b/functions/src/database.ts @@ -1,4 +1,4 @@ -class Database { +export class Database { private readonly db: FirebaseFirestore.Firestore; private readonly collectionName: string; diff --git a/functions/src/models/newsriver.ts b/functions/src/models/newsriver.ts index a6c42b8..c747d45 100644 --- a/functions/src/models/newsriver.ts +++ b/functions/src/models/newsriver.ts @@ -1,58 +1,32 @@ import admin = require('firebase-admin'); -import functions = require("firebase-functions"); -import {ProjectProperties} from "../interfaces/projectProperties"; -import {Updater} from "../updater"; -import {RemoteConfigData} from "../rcdata"; import {NewsriverData} from "../newsriver"; +import {Database} from "../database"; +import properties = require('../common/properties'); -const serviceAccount = require('../../handwashing-firebase-adminsdk.json'); export const firebaseApp = admin.initializeApp({ - credential: admin.credential.cert(serviceAccount), - databaseURL: 'https://handwashing.firebaseio.com' + databaseURL: properties.databaseURL }); -const languages = new Set(['es', 'en']); -const projectProperties: ProjectProperties = { - collection: 'news', - database: admin.firestore(), - authToken: functions.config().newsriver.key -} -const updaters: Record = {}; -const remoteConfig = new RemoteConfigData(firebaseApp); -const timers = new Set(); +const databases: Record = {}; let initCalled = false; export async function initialize() { initCalled = true; - for (const language of languages) { - const terms = await remoteConfig.getSearchTermsForLanguage(language); - const updater = new Updater( + const projectProperties = properties.projectProperties(firebaseApp); + for (const language of properties.languages) { + databases[language] = new Database( projectProperties.database, - `${projectProperties.collection}_${language}`, - terms, - projectProperties.authToken, - language + `${projectProperties.collection}_${language}` ); - console.log('Created updater object'); - updaters[language] = updater; - } - remoteConfig.subscribeUpdaters(updaters); -} - -export async function scheduleUpdates() { - if (!initCalled) - throw new Error('`initialize` not called'); - for (const language of languages) { - timers.add(updaters[language].schedule()); } } export async function newsForLanguage(language: string) { if (!initCalled) throw new Error('`initialize` not called'); - if (language !in languages) + if (language ! in properties.languages) throw new RangeError(`invalid language "${language}"`); - const collection = updaters[language].collection; + const collection = databases[language].collection; const snapshot = await collection.get(); const data = new Array(); snapshot.forEach(item => { @@ -61,10 +35,3 @@ export async function newsForLanguage(language: string) { }); return data; } - -export async function stopScheduling() { - if (!initCalled) - throw new Error('`initialize` not called'); - for (const timer of timers) - clearInterval(timer); -} diff --git a/functions/src/models/updater.ts b/functions/src/models/updater.ts new file mode 100644 index 0000000..75e896d --- /dev/null +++ b/functions/src/models/updater.ts @@ -0,0 +1,48 @@ +import {Updater} from '../updater'; +import {RemoteConfigData} from "../rcdata"; +import admin = require('firebase-admin'); +import properties = require('../common/properties'); +import {languages} from "../common/properties"; + + +const serviceAccount = require('../../handwashing-firebase-adminsdk.json'); +export const firebaseApp = admin.initializeApp({ + credential: admin.credential.cert(serviceAccount), + databaseURL: properties.databaseURL +}); +const updaters: Record = {}; +const remoteConfig = new RemoteConfigData(firebaseApp); +const timers = new Set(); +let initCalled = false; + +export async function initialize() { + initCalled = true; + const projectProperties = properties.projectProperties(firebaseApp); + for (const language of properties.languages) { + const terms = await remoteConfig.getSearchTermsForLanguage(language); + updaters[language] = new Updater( + projectProperties.database, + `${projectProperties.collection}_${language}`, + terms, + projectProperties.authToken, + language + ); + } + remoteConfig.subscribeUpdaters(updaters); +} + +export async function scheduleUpdates() { + if (!initCalled) + throw new Error('`initialize` not called'); + for (const language of languages) { + timers.add(updaters[language].schedule()); + } +} + +export async function stopScheduling() { + if (!initCalled) + throw new Error('`initialize` not called'); + for (const timer of timers) { + clearInterval(timer); + } +} diff --git a/functions/src/updater.ts b/functions/src/updater.ts index 289f6b2..e9190ab 100644 --- a/functions/src/updater.ts +++ b/functions/src/updater.ts @@ -28,10 +28,6 @@ export class Updater { this._url = undefined; } - get collection(): FirebaseFirestore.CollectionReference { - return this.db.collection(this.collectionName); - } - constructor(db: FirebaseFirestore.Firestore | null, collectionName: string | null, searchTerms: Array, From 5cb35976d5f67e27fed624e88f4bdfa7f63b0fb8 Mon Sep 17 00:00:00 2001 From: Javinator9889 Date: Mon, 8 Jun 2020 18:44:59 +0200 Subject: [PATCH 27/95] Updated 'Updater' model - included a function for preventing Firebase killing the server --- functions/src/bin/daemon.ts | 13 +++++ functions/src/models/updater.ts | 39 +++++++++----- functions/src/rcdata.ts | 32 ++++++------ functions/src/routes/newsriver.ts | 12 ----- functions/src/updater.ts | 85 ++++++++++++++----------------- 5 files changed, 90 insertions(+), 91 deletions(-) diff --git a/functions/src/bin/daemon.ts b/functions/src/bin/daemon.ts index e69de29..2312f97 100644 --- a/functions/src/bin/daemon.ts +++ b/functions/src/bin/daemon.ts @@ -0,0 +1,13 @@ +import updater = require('../models/updater'); +import functions = require('firebase-functions'); + + +updater.initialize() + .then(_ => updater.scheduleUpdates()); + +process.on('SIGINT', () => { + updater.stopScheduling() + .then(process.exit(0)); +}); + +exports.updater = functions.https.onRequest((req, resp) => resp.sendStatus(200)); diff --git a/functions/src/models/updater.ts b/functions/src/models/updater.ts index 75e896d..979dcfb 100644 --- a/functions/src/models/updater.ts +++ b/functions/src/models/updater.ts @@ -2,7 +2,6 @@ import {Updater} from '../updater'; import {RemoteConfigData} from "../rcdata"; import admin = require('firebase-admin'); import properties = require('../common/properties'); -import {languages} from "../common/properties"; const serviceAccount = require('../../handwashing-firebase-adminsdk.json'); @@ -16,32 +15,44 @@ const timers = new Set(); let initCalled = false; export async function initialize() { - initCalled = true; - const projectProperties = properties.projectProperties(firebaseApp); - for (const language of properties.languages) { - const terms = await remoteConfig.getSearchTermsForLanguage(language); - updaters[language] = new Updater( - projectProperties.database, - `${projectProperties.collection}_${language}`, - terms, - projectProperties.authToken, - language - ); + try { + console.info('Updater is being initialized'); + initCalled = true; + const projectProperties = properties.projectProperties(firebaseApp); + for (const language of properties.languages) { + console.debug(`Creating updater for language ${language}`); + const terms = await remoteConfig.getSearchTermsForLanguage(language); + console.debug(`Updater terms: ${terms}`); + updaters[language] = new Updater( + projectProperties.database, + `${projectProperties.collection}_${language}`, + terms, + projectProperties.authToken, + language + ); + } + console.info('Updaters initialized') + remoteConfig.subscribeUpdaters(updaters); + } catch (e) { + console.error(`Error while initializing updaters - ${e}`); + console.error(e); } - remoteConfig.subscribeUpdaters(updaters); } export async function scheduleUpdates() { if (!initCalled) throw new Error('`initialize` not called'); - for (const language of languages) { + console.info('Updater is scheduling updates') + for (const language of properties.languages) { timers.add(updaters[language].schedule()); } + console.info('Schedules for updaters are done'); } export async function stopScheduling() { if (!initCalled) throw new Error('`initialize` not called'); + console.info('Updates are being cancelled'); for (const timer of timers) { clearInterval(timer); } diff --git a/functions/src/rcdata.ts b/functions/src/rcdata.ts index ccbe159..94f6f6c 100644 --- a/functions/src/rcdata.ts +++ b/functions/src/rcdata.ts @@ -12,23 +12,21 @@ export class RemoteConfigData { this.listenToRCChanges(); } - getSearchTermsForLanguage(language: string): Promise> { - return new Promise>(resolve => { - this.remoteConfig.getTemplate() - .then(template => { - let condition: string; - switch (language) { - case 'es': - condition = 'Spanish users'; - break; - default: - condition = 'Default language users'; - break; - } - const values = JSON.parse(template.parameters['search_terms'].conditionalValues[condition]['value']); - resolve(values); - }); - }); + async getSearchTermsForLanguage(language: string) { + console.info('Getting template...'); + const template = await this.remoteConfig.getTemplate(); + console.debug('Checking condition'); + let condition: string; + switch (language) { + case 'es': + condition = 'Spanish users'; + break; + default: + condition = 'Default language users'; + break; + } + console.debug('Parsing JSON'); + return JSON.parse(template.parameters['search_terms'].conditionalValues[condition]['value']); } subscribeUpdaters(updaters: Record) { diff --git a/functions/src/routes/newsriver.ts b/functions/src/routes/newsriver.ts index 8e14f4a..dd774c5 100644 --- a/functions/src/routes/newsriver.ts +++ b/functions/src/routes/newsriver.ts @@ -6,7 +6,6 @@ import admin = require("firebase-admin"); const router = express.Router(); apiController.apiModel.initialize() - .then(_ => apiController.apiModel.scheduleUpdates()) .catch(e => { console.error(e); throw e; @@ -32,16 +31,5 @@ router.get('/api/v1', (req, res, next) => { } }); router.get('/api/v1', apiController.queryNewsForLanguage); -router.get('/close', (req, res) => { - const authToken = req.get('Authorization'); - if (authToken === undefined) - return res.sendStatus(403); - if (authToken === process.env.ADMIN_TOKEN) { - return apiController.apiModel.stopScheduling() - .then(_ => res.sendStatus(200)) - .catch(_ => res.sendStatus(200)); - } else - return res.sendStatus(403); -}); export = router; diff --git a/functions/src/updater.ts b/functions/src/updater.ts index e9190ab..abd7df6 100644 --- a/functions/src/updater.ts +++ b/functions/src/updater.ts @@ -33,7 +33,7 @@ export class Updater { searchTerms: Array, auth: string, language: string = 'en', - intervalMins: number = 60) { + intervalMins: number = 15) { this.db = db; this.collectionName = collectionName; this.searchTerms = searchTerms; @@ -44,28 +44,32 @@ export class Updater { .then(response => { this.updateData(response) // tslint:disable-next-line:no-empty - .catch(ignored => {}); + .catch(ignored => { + }); }) } schedule(): NodeJS.Timer { - return setInterval(() => { - const that = this; - this.request() - .then(response => { - that.updateData(response) - .catch(error => console.error(`error occurred while updating firebase data ${error}`)); - }) - .catch(error => console.error(`error occurred during process ${error}`)); + return setInterval(async () => { + try { + const response = await this.request(); + await this.updateData(response); + } catch (e) { + console.error(`Got error ${e} while querying data`); + } }, this.interval); } async updateData(content: Array) { - console.log('Updating database content data'); try { content.forEach(element => { firebaseHelper.firestore.checkDocumentExists(this.db, this.collectionName, element.id) - .then(_ => firebaseHelper.firestore.createDocumentWithID(this.db, this.collectionName, element.id, element)) + .then(exists => { + if (!exists) + firebaseHelper.firestore.createDocumentWithID(this.db, this.collectionName, element.id, element); + else + firebaseHelper.firestore.updateDocument(this.db, this.collectionName, element.id, element); + }) console.log(`Created element with ID: ${element.id}`); }); } catch (error) { @@ -74,44 +78,29 @@ export class Updater { } async request(): Promise> { - try { - const requestUrl = await this.url; - const response = await fetch(requestUrl, { - method: 'GET', headers: new fetch.Headers({ - 'Authorization': this.auth, - 'Content-Type': 'application/json' - }) - }); - const body = await response.json(); - return body as Array; - } catch (e) { - console.error(`Captured error ${e}`); - throw e; - } + const requestUrl = await this.url; + const response = await fetch(requestUrl, { + method: 'GET', headers: new fetch.Headers({ + 'Authorization': this.auth, + 'Content-Type': 'application/json' + }) + }); + return await response.json() as Array; } - buildURL(): Promise { - return new Promise(resolve => { - const parts = ['https://api.newsriver.io/v2/search?query=']; - this.searchTerms.forEach((term, i, _) => { - if (i !== 0) - parts.push(encodeURI(' OR ')); - parts.push(encodeURI(`title:${term} OR text:${term}`)); - }); - let language: string; - switch (this.language) { - case 'es': - language = 'ES'; - break; - default: - language = 'EN'; - break; - } - parts.push(encodeURI(` AND language:${language}`)); - parts.push(encodeURI('&sortBy=discoverDate')); - parts.push(encodeURI('&sortOrder=DESC')); - parts.push(encodeURI('&limit=10')); - resolve(parts.join('')); + async buildURL(): Promise { + const parts = ['https://api.newsriver.io/v2/search?query=']; + this.searchTerms.forEach((term, i, _) => { + if (i !== 0) + parts.push(encodeURI(' OR ')); + parts.push(encodeURI(`title:${term} OR text:${term}`)); }); + + parts.push(encodeURI(` AND language:${this.language.toUpperCase()}`)); + parts.push(encodeURI('&sortBy=discoverDate')); + parts.push(encodeURI('&sortOrder=DESC')); + parts.push(encodeURI('&limit=100')); + + return parts.join(''); } } \ No newline at end of file From 39a02094dae298c12f141cf45daf96738403932e Mon Sep 17 00:00:00 2001 From: Javinator9889 Date: Mon, 8 Jun 2020 18:52:24 +0200 Subject: [PATCH 28/95] Included query params for limiting the amount of items that can be queried --- functions/src/controllers/newsriver.ts | 30 +++++++++++++++++--------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/functions/src/controllers/newsriver.ts b/functions/src/controllers/newsriver.ts index 538845b..893f16c 100644 --- a/functions/src/controllers/newsriver.ts +++ b/functions/src/controllers/newsriver.ts @@ -4,14 +4,24 @@ import {Request, Response} from "express"; export async function queryNewsForLanguage(req: Request, res: Response) { const language = req.query.lang as string; - apiModel.newsForLanguage(language) - .then(newsData => res.json(newsData)) - .catch(err => { - if (err !instanceof RangeError) { - console.error(`Error while getting news data: ${err}`); - res.sendStatus(500); - } - else - res.status(403).send(err); - }) + const fromParam = req.query.from; + const amountParam = req.query.amount; + try { + const newsData = await apiModel.newsForLanguage(language); + if (fromParam === undefined && amountParam === undefined) { + res.json(newsData); + } else { + const from = fromParam === undefined ? Number(fromParam) : 0; + const amount = amountParam === undefined ? Number(amountParam) : 0; + + res.json(newsData.slice(from, from + amount)); + } + } catch (err) { + if (err !instanceof RangeError) { + console.error(`Error while getting news data: ${err}`); + res.sendStatus(500); + } + else + res.status(403).send(err); + } } \ No newline at end of file From 3bc7d59ec19a549d3fea53ad12f37a615253f84d Mon Sep 17 00:00:00 2001 From: Javinator9889 Date: Mon, 8 Jun 2020 19:40:34 +0200 Subject: [PATCH 29/95] Moved working Dockerfile and removed unused files from Fastlane (needs more cleaning) --- .dockerignore | 40 ++++++ .gitlab-ci.yml | 128 ------------------ Dockerfile | 110 ++------------- Dockerfile.old | 31 ----- fastlane/Appfile | 2 - fastlane/Fastfile | 58 -------- fastlane/README.md | 59 -------- .../android/es-ES/full_description.txt | 1 - .../android/es-ES/images/featureGraphic.png | Bin 79378 -> 0 bytes .../metadata/android/es-ES/images/icon.png | Bin 46053 -> 0 bytes .../es-ES/images/phoneScreenshots/1_es-ES.png | Bin 63248 -> 0 bytes .../es-ES/images/phoneScreenshots/2_es-ES.png | Bin 24149 -> 0 bytes .../android/es-ES/short_description.txt | 1 - fastlane/metadata/android/es-ES/title.txt | 1 - fastlane/metadata/android/es-ES/video.txt | 0 fastlane/report.xml | 21 --- functions/.dockerignore | 2 + functions/daemon.json | 2 +- functions/package.json | 4 +- 19 files changed, 58 insertions(+), 402 deletions(-) create mode 100644 .dockerignore delete mode 100644 .gitlab-ci.yml delete mode 100644 Dockerfile.old delete mode 100644 fastlane/Appfile delete mode 100644 fastlane/Fastfile delete mode 100644 fastlane/README.md delete mode 100644 fastlane/metadata/android/es-ES/full_description.txt delete mode 100644 fastlane/metadata/android/es-ES/images/featureGraphic.png delete mode 100644 fastlane/metadata/android/es-ES/images/icon.png delete mode 100644 fastlane/metadata/android/es-ES/images/phoneScreenshots/1_es-ES.png delete mode 100644 fastlane/metadata/android/es-ES/images/phoneScreenshots/2_es-ES.png delete mode 100644 fastlane/metadata/android/es-ES/short_description.txt delete mode 100644 fastlane/metadata/android/es-ES/title.txt delete mode 100644 fastlane/metadata/android/es-ES/video.txt delete mode 100644 fastlane/report.xml create mode 100644 functions/.dockerignore diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..57066b3 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,40 @@ +ads +android-signing-keystore.jks +api-5245190277294621651-718463-f914fb7573c6.json +app +appintro +build +build.gradle +bundledemoji +CHANGELOG +Dockerfile +.dockerignore +extras +Gemfile +Gemfile.lock +.git +.gitignore +.gitlab-ci.yml +.gitmodules +.gradle +gradle +gradle.properties +gradlew +gradlew.bat +handwashing_app_logo.png +handwashing_app_logo.webp +handwashing_icon.png +handwashing_icon.webp +.idea +keystore.properties +LICENSE +local.properties +public +README.md +secrets.properties +settings.gradle +test-app-js +tgs +.vscode +functions/node_modules +functions/ui-debug.log diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml deleted file mode 100644 index 808e28c..0000000 --- a/.gitlab-ci.yml +++ /dev/null @@ -1,128 +0,0 @@ -# This file is a template, and might need editing before it works on your project. -# Read more about how to use this script on this blog post https://about.gitlab.com/2019/01/28/android-publishing-with-gitlab-and-fastlane/ -# You will also need to configure your build.gradle, Dockerfile, and fastlane configuration to make this work. -# If you are looking for a simpler template that does not publish, see the Android template. - -stages: - - environment - - build - - test - - deploy - - internal - - alpha - - beta - - production - - -.updateContainerJob: - image: docker:19.03.0 - variables: - DOCKER_DRIVER: overlay2 - DOCKER_TLS_CERTDIR: "/certs" - # CI_REGISTRY: registry.gitlab.com - stage: environment - services: - - docker:19.03.0-dind - before_script: - - docker info - script: - - docker login -u gitlab-ci-token -p $CI_JOB_TOKEN $CI_REGISTRY - - docker pull $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG || true - - docker build --cache-from $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG -t $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG . - - docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG - -updateContainer: - extends: .updateContainerJob - only: - changes: - - Dockerfile - -ensureContainer: - extends: .updateContainerJob - allow_failure: true - before_script: - - "mkdir -p ~/.docker && echo '{\"experimental\": \"enabled\"}' > ~/.docker/config.json" - - docker login -u gitlab-ci-token -p $CI_JOB_TOKEN $CI_REGISTRY - # Skip update container `script` if the container already exists - # via https://gitlab.com/gitlab-org/gitlab-foss/issues/26866#note_97609397 -> https://stackoverflow.com/a/52077071/796832 - - docker manifest inspect $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG > /dev/null && exit || true - - -.build_job: - image: $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG - stage: build - before_script: - # We store this binary file in a variable as hex with this command: `xxd -p android-app.jks` - # Then we convert the hex back to a binary file - - echo "$signing_jks_file_hex" | xxd -r -p - > android-signing-keystore.jks - - "export VERSION_CODE=$CI_PIPELINE_IID && echo $VERSION_CODE" - - "export VERSION_SHA=`echo ${CI_COMMIT_SHA:0:8}` && echo $VERSION_SHA" - after_script: - - rm -f android-signing-keystore.jks || true - artifacts: - paths: - - app/build/outputs - -buildDebug: - extends: .build_job - script: - - bundle exec fastlane buildDebug - -buildRelease: - extends: .build_job - script: - - bundle exec fastlane buildRelease - environment: - name: production - -testDebug: - image: $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG - stage: test - dependencies: - - buildDebug - script: - - bundle exec fastlane test - -publishInternal: - image: $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG - stage: internal - dependencies: - - buildRelease - when: manual - before_script: - - echo $google_play_service_account_api_key_json > ~/google_play_api_key.json - after_script: - - rm ~/google_play_api_key.json - script: - - bundle exec fastlane internal - -.promote_job: - image: $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG - when: manual - dependencies: [] - before_script: - - echo $google_play_service_account_api_key_json > ~/google_play_api_key.json - after_script: - - rm ~/google_play_api_key.json - -promoteAlpha: - extends: .promote_job - stage: alpha - script: - - bundle exec fastlane promote_internal_to_alpha - -promoteBeta: - extends: .promote_job - stage: beta - script: - - bundle exec fastlane promote_alpha_to_beta - -promoteProduction: - extends: .promote_job - stage: production - # We only allow production promotion on `master` because - # it has its own production scoped secret variables - only: - - master - script: - - bundle exec fastlane promote_beta_to_production diff --git a/Dockerfile b/Dockerfile index 33c11ae..ce2a977 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,97 +1,13 @@ -# ====================================================================== # -# Android SDK Docker Image -# ====================================================================== # - -# Base image -# ---------------------------------------------------------------------- # -FROM ubuntu:18.04 - -# Author -# ---------------------------------------------------------------------- # -LABEL maintainer "thyrlian@gmail.com" - -# support multiarch: i386 architecture -# install Java -# install essential tools -# install Qt -RUN dpkg --add-architecture i386 && \ - apt update -y && \ - apt install -y --no-install-recommends libncurses5:i386 libc6:i386 libstdc++6:i386 lib32gcc1 lib32ncurses5 lib32z1 zlib1g:i386 && \ - apt install -y --no-install-recommends openjdk-8-jdk && \ - apt install -y --no-install-recommends git wget unzip && \ - apt install -y --no-install-recommends qt5-default - -# download and install Gradle -# https://services.gradle.org/distributions/ -ARG GRADLE_VERSION=6.1.1 -ARG GRADLE_DIST=bin -RUN cd /opt && \ - wget -q https://services.gradle.org/distributions/gradle-${GRADLE_VERSION}-${GRADLE_DIST}.zip && \ - unzip gradle*.zip && \ - ls -d */ | sed 's/\/*$//g' | xargs -I{} mv {} gradle && \ - rm gradle*.zip - -# download and install Kotlin compiler -# https://github.com/JetBrains/kotlin/releases/latest -ARG KOTLIN_VERSION=1.3.71 -RUN cd /opt && \ - wget -q https://github.com/JetBrains/kotlin/releases/download/v${KOTLIN_VERSION}/kotlin-compiler-${KOTLIN_VERSION}.zip && \ - unzip *kotlin*.zip && \ - rm *kotlin*.zip - -# download and install Android SDK -# https://developer.android.com/studio/#downloads -ARG ANDROID_SDK_VERSION=4333796 -ENV ANDROID_HOME /opt/android-sdk -RUN mkdir -p ${ANDROID_HOME} && cd ${ANDROID_HOME} && \ - wget -q https://dl.google.com/android/repository/sdk-tools-linux-${ANDROID_SDK_VERSION}.zip && \ - unzip *tools*linux*.zip && \ - rm *tools*linux*.zip - -# set the environment variables -ENV JAVA_HOME /usr/lib/jvm/java-8-openjdk-amd64 -ENV GRADLE_HOME /opt/gradle -ENV KOTLIN_HOME /opt/kotlinc -ENV PATH ${PATH}:${GRADLE_HOME}/bin:${KOTLIN_HOME}/bin:${ANDROID_HOME}/emulator:${ANDROID_HOME}/tools:${ANDROID_HOME}/platform-tools:${ANDROID_HOME}/tools/bin -ENV _JAVA_OPTIONS -XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap -# WORKAROUND: for issue https://issuetracker.google.com/issues/37137213 -ENV LD_LIBRARY_PATH ${ANDROID_HOME}/emulator/lib64:${ANDROID_HOME}/emulator/lib64/qt/lib - -# accept the license agreements of the SDK components -ADD license_accepter.sh /opt/ -RUN chmod +x /opt/license_accepter.sh && /opt/license_accepter.sh $ANDROID_HOME - -# setup adb server -EXPOSE 5037 - -# install and configure SSH server -EXPOSE 22 -ADD sshd-banner /etc/ssh/ -ADD authorized_keys /tmp/ -RUN apt-get update -y && \ - apt-get install -y --no-install-recommends openssh-server supervisor locales && \ - mkdir -p /var/run/sshd /var/log/supervisord && \ - locale-gen en en_US en_US.UTF-8 && \ - apt-get remove -y locales && apt-get autoremove -y && \ - FILE_SSHD_CONFIG="/etc/ssh/sshd_config" && \ - echo "\nBanner /etc/ssh/sshd-banner" >> $FILE_SSHD_CONFIG && \ - echo "\nPermitUserEnvironment=yes" >> $FILE_SSHD_CONFIG && \ - ssh-keygen -q -N "" -f /root/.ssh/id_rsa && \ - FILE_SSH_ENV="/root/.ssh/environment" && \ - touch $FILE_SSH_ENV && chmod 600 $FILE_SSH_ENV && \ - printenv | grep "JAVA_HOME\|GRADLE_HOME\|KOTLIN_HOME\|ANDROID_HOME\|LD_LIBRARY_PATH\|PATH" >> $FILE_SSH_ENV && \ - FILE_AUTH_KEYS="/root/.ssh/authorized_keys" && \ - touch $FILE_AUTH_KEYS && chmod 600 $FILE_AUTH_KEYS && \ - for file in /tmp/*.pub; \ - do if [ -f "$file" ]; then echo "\n" >> $FILE_AUTH_KEYS && cat $file >> $FILE_AUTH_KEYS && echo "\n" >> $FILE_AUTH_KEYS; fi; \ - done && \ - (rm /tmp/*.pub 2> /dev/null || true) - -ADD supervisord.conf /etc/supervisor/conf.d/ -CMD ["/usr/bin/supervisord"] - -# install Fastlane -COPY Gemfile.lock . -COPY Gemfile . -RUN gem install bundle -RUN bundle install \ No newline at end of file +FROM node:10 +# app directory +WORKDIR /usr/src/app +# Copy neccessary files +COPY ./functions ./functions +COPY ./functions ./functions +COPY ./firebase.json ./ +COPY ./.firebaserc ./ +COPY ./.firebase ./ +WORKDIR /usr/src/app/functions +RUN npm i --only=production -g pm2@latest firebase-tools cross-env typescript +RUN npm ci --only=production +CMD ["pm2-runtime", "start", "daemon.json"] diff --git a/Dockerfile.old b/Dockerfile.old deleted file mode 100644 index 64b8d2e..0000000 --- a/Dockerfile.old +++ /dev/null @@ -1,31 +0,0 @@ -FROM openjdk:8-jdk - -# Just matched `app/build.gradle` -ENV ANDROID_COMPILE_SDK "29" -# Just matched `app/build.gradle` -ENV ANDROID_BUILD_TOOLS "29.0.3" -# Version from https://developer.android.com/studio/releases/sdk-tools -ENV ANDROID_SDK_TOOLS "26.1.1" - -ENV ANDROID_HOME /android-sdk-linux -ENV PATH="${PATH}:/android-sdk-linux/platform-tools/" - -# install OS packages -RUN apt --quiet update --yes -RUN apt --quiet install --yes wget tar unzip lib32stdc++6 lib32z1 build-essential ruby ruby-dev -# We use this for xxd hex->binary -RUN apt --quiet install --yes vim-common -# install Android SDK -RUN wget --quiet --output-document=android-sdk.tgz https://dl.google.com/android/android-sdk_r${ANDROID_SDK_TOOLS}-linux.tgz -RUN tar --extract --gzip --file=android-sdk.tgz -RUN echo y | android-sdk-linux/tools/android --silent update sdk --no-ui --all --filter android-${ANDROID_COMPILE_SDK} -RUN echo y | android-sdk-linux/tools/android --silent update sdk --no-ui --all --filter platform-tools -RUN echo y | android-sdk-linux/tools/android --silent update sdk --no-ui --all --filter build-tools-${ANDROID_BUILD_TOOLS} -RUN echo y | android-sdk-linux/tools/android --silent update sdk --no-ui --all --filter extra-android-m2repository -RUN echo y | android-sdk-linux/tools/android --silent update sdk --no-ui --all --filter extra-google-google_play_services -RUN echo y | android-sdk-linux/tools/android --silent update sdk --no-ui --all --filter extra-google-m2repository -# install Fastlane -COPY Gemfile.lock . -COPY Gemfile . -RUN gem install bundle -RUN bundle install \ No newline at end of file diff --git a/fastlane/Appfile b/fastlane/Appfile deleted file mode 100644 index 620a499..0000000 --- a/fastlane/Appfile +++ /dev/null @@ -1,2 +0,0 @@ -json_key_file("api-5245190277294621651-718463-f914fb7573c6.json") # Path to the json secret file - Follow https://docs.fastlane.tools/actions/supply/#setup to get one -package_name("com.javinator9889.handwashingreminder") # e.g. com.krausefx.app diff --git a/fastlane/Fastfile b/fastlane/Fastfile deleted file mode 100644 index ce0f9bd..0000000 --- a/fastlane/Fastfile +++ /dev/null @@ -1,58 +0,0 @@ -# This file contains the fastlane.tools configuration -# You can find the documentation at https://docs.fastlane.tools -# -# For a list of all available actions, check out -# -# https://docs.fastlane.tools/actions -# -# For a list of all available plugins, check out -# -# https://docs.fastlane.tools/plugins/available-plugins -# - -# Uncomment the line if you want fastlane to automatically update itself -# update_fastlane - -default_platform(:android) - -platform :android do - - desc "Builds the debug code" - lane :buildDebug do - gradle(task: "assembleDebug") - end - - desc "Builds the release code" - lane :buildRelease do - gradle(task: "assembleRelease") - end - - desc "Runs all the tests" - lane :test do - gradle(task: "test") - end - - desc "Submit a new Internal Build to Play Store" - lane :internal do - upload_to_play_store(track: 'internal', apk: 'app/build/outputs/apk/release/app-release.apk') - end - - # `skip_upload_changelogs` from https://github.com/fastlane/fastlane/issues/15681#issuecomment-556617987 - desc "Promote Internal to Alpha" - lane :promote_internal_to_alpha do - upload_to_play_store(track: 'internal', skip_upload_changelogs: true, track_promote_to: 'alpha') - end - - # `skip_upload_changelogs` from https://github.com/fastlane/fastlane/issues/15681#issuecomment-556617987 - desc "Promote Alpha to Beta" - lane :promote_alpha_to_beta do - upload_to_play_store(track: 'alpha', skip_upload_changelogs: true, track_promote_to: 'beta') - end - - # `skip_upload_changelogs` from https://github.com/fastlane/fastlane/issues/15681#issuecomment-556617987 - desc "Promote Beta to Production" - lane :promote_beta_to_production do - upload_to_play_store(track: 'beta', skip_upload_changelogs: true, track_promote_to: 'production') - end -end - diff --git a/fastlane/README.md b/fastlane/README.md deleted file mode 100644 index dc840ad..0000000 --- a/fastlane/README.md +++ /dev/null @@ -1,59 +0,0 @@ -fastlane documentation -================ -# Installation - -Make sure you have the latest version of the Xcode command line tools installed: - -``` -xcode-select --install -``` - -Install _fastlane_ using -``` -[sudo] gem install fastlane -NV -``` -or alternatively using `brew cask install fastlane` - -# Available Actions -## Android -### android buildDebug -``` -fastlane android buildDebug -``` -Builds the debug code -### android buildRelease -``` -fastlane android buildRelease -``` -Builds the release code -### android test -``` -fastlane android test -``` -Runs all the tests -### android internal -``` -fastlane android internal -``` -Submit a new Internal Build to Play Store -### android promote_internal_to_alpha -``` -fastlane android promote_internal_to_alpha -``` -Promote Internal to Alpha -### android promote_alpha_to_beta -``` -fastlane android promote_alpha_to_beta -``` -Promote Alpha to Beta -### android promote_beta_to_production -``` -fastlane android promote_beta_to_production -``` -Promote Beta to Production - ----- - -This README.md is auto-generated and will be re-generated every time [fastlane](https://fastlane.tools) is run. -More information about fastlane can be found on [fastlane.tools](https://fastlane.tools). -The documentation of fastlane can be found on [docs.fastlane.tools](https://docs.fastlane.tools). diff --git a/fastlane/metadata/android/es-ES/full_description.txt b/fastlane/metadata/android/es-ES/full_description.txt deleted file mode 100644 index 466eded..0000000 --- a/fastlane/metadata/android/es-ES/full_description.txt +++ /dev/null @@ -1 +0,0 @@ -Una aplicación que te recuerda que te laves las manos de forma inteligente \ No newline at end of file diff --git a/fastlane/metadata/android/es-ES/images/featureGraphic.png b/fastlane/metadata/android/es-ES/images/featureGraphic.png deleted file mode 100644 index b6861fbccab0d13a32605d535e033dac4a9cdba7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 79378 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 Cb zZ;ArdkK_?BBP#6Wt(mmx?7zgN-Mj-{j2bo@+rECf-|Enx*()QchI~TAaFy3*?%`>*C~p}(5M2X0Wx=8{_N8<#ULHJ zA-UI!s-DTvpbcjT-uKuU7}fRZ6KW52#s{bV1I6_4m+bLN9>&}DKyFFDJ!Iau-=O6; zpf54jwFsIHAU4AUZzAmnY#<4{YU1o0cCTRgdGYeQ1C4^Cs&>`Cf%d0+$RL??F73D_ zoorxT8eD8l6eg?7@B8iNdxnuCy{O)ue3^Eh@0QlXx}Rk)O7MSlH; zQV3u?mk4-4DTDEb@C`;gXL`jc<4<+oE}GqbMxRb079b+*Gx?Efa$gCO5U}kZqHi?y zK{+wUC2Op?{LL}LxH~64RpdKGxgVGj68a6$5nU()BCjGsjdMS&257{Oj}p||rTC=z z%hY1lwdyp)n{q#4h@9X#--5n}Y+=i<)C+=YVy-xmMyIu*585;iq+AR`+jfaH(L9^J z;&n~PCU&HRwrcKEqkOea((--Meg`<7offb}=dMfCj^jn4I(U;Cj}mu4v>h}pv1LYn z@0J$qX^X>;Yshor1#`kMPOUNu%@MlBOm+Iku74n-I4im7#^ewCTE1EQ7G>|wwV0lb zo)U7*jyOk=kqVY@iv?i&r_;({fCy!*H@9N=iZ33}#|MCg2=_A7?H_8lGl|c)R(=7y zeL{+h@!0m`)n*TQoEm|M3YLH_`z|H>OjqngVPZ@bU=4ce7qam|T6dcgN2>_m4~E(! zDQ2Ne;Tm6^$ng?3H0@5w{WA(6g|!6#m*gP-b>Z;PU;=6}^(?r)t|Wu%H&YJ>P_)q- zw#lFdjz>;#9N(Ln-mqjv+&~Z$JnS(|d6s)Qk7$vjtc2(K2O{Gbgf`a4_?cpPpLZcK z#`q*+=bz`%y=JT6%z0X{as1-s~R}zDHl!Xj7k4)goAm6q`Va4nmWVy$Adaun)#6^%x zR9roN(apdSJp8%Vo;?+G^NdxWi*)@cI%#*6dqAA{y9P(l0kq5^>)N3kn+k$hST^kN z&iG5@o6mPwtp%NL^msJ#gTg1|d6UJaG<*T-U$45o8@UlgQ1J<~s7EyT7$+u&zKC}* zf@{{fFn#QSgJ6Z(_k|p;TQD>yGy;fXX$54Ae-y5_(7`gk60hm;8 zA~snW1Le}rmou~nEoT$DV6eD6uA1~0;TW+>K^(g+T^@&SIYd%e%UuzS(Zr9QM&?}BDiqOoi>xMn;SP+{1 z?&$KdR+gWEvVEliUZG4JUD$C8u4})|@7i|ok57FaGHr<^M&kZyBK`9IANXEVSg43- zGu&ZZccilKXr^i|c^msqo1(TKz?z*DCKkR3r20Ryr=+YTkTfXyq8JXqRMR$Qvo+*NNg4I)>#4>qX!@a% z2X+?S5O30r)FD_VRAxomiOl9vos)H)(esN(J?iHa80s(uxOi*SJ9x5oRNIMh=ogX> zyHc4Ev+C=%UA22b4hqa_)e`ZQ0`VROk)8p=LX&qo*Ih2z`;(?#hg5B! zLg72)3jHDdFUFcLeIic~>x}nmBNRAtrbF=$jPI4N=MR2*=I?6jGhI4LReeHFqO-w_+tB`mJrb2DF4V2djhzVUo{$RNbJ?vlcVe3=jj zLio(2$k-9HW*D4l6;iiXf<9JeW@L_W)zoj0`U{G;%s2^hp6FVnHoyco&W>adimY5B z@K2tu&{(UIV@vy<;Iz;oQz~vfGzz(>uB<0W0=gt7@s&|N!q5-P`&MUeZE6vWNos7h?X9t45&4r8ZiulvumE7z8kG7i z&n7Q@0Uc5kN;5H^9nn-3(y2H=abJ*YXPGQ;=f@@{)36_bBL>1HV>L5NYml{Xz(aTN ztU91wFsOwkWR9mM4JRUgg{MBZ{KDxQDlmB5a^cD5oC#2SjEOJ*su4`r%gfk&n zVv!fP#j_gOMORdfkv(@XIrj^=&|2Be0gqXAxAXnYdTXWg8L`UXw9 zm{>0wAi7S1d|j8O1w#8}N9v?BqS>$AnfmRXKN|lK7XtKK;sXs^SHkuiK|E*4c$f|D zHXzfG#fvWmmuS(It3D#o!B80hg{f3%nY>)#?V*Pjhk~M;vb>UfIL1nGk*h zqq$58F~gw~9AbHgs<7@Gs5$bE^BPnQ_LVv0eh4mDm3*>2oyap?qHfp2pW5ToV(UnB zed*V{F|J(Tggi$T?0ctm^i~7(21d?sVZs=sYOZ^w_PyQUH=d=j& zQIseGa-z0c@wEVDcbz;Hn7E#M(c#N7EkJ`eKu4MUQZoUJM(7$Pv3%p^3t1rFLuLtd zAte&f3J1QrHJc;Hm^v*~V^KmE&b`x?x7b^x9STyV*eo#MGA;tffUpWvUp;BsDie`} zjYdj!EE{A5w+L2RyhGU-!GwT{QJBdJdqj(%QbLV*(nb!>Hf@liKiEJRLRRCsk@5PKhr6n=%moOQq5jM(r zoQgQxN1R5n26c@R5c(gf&y~mGA}JaGcl(QQw5&4U99H0Z=p$@jF6IZKCZ+rz;)3## z8`#2c;scIrD~3kLIN6X^Ch@ckN?WK)I6ygw3bLM$(L&K;`q`$43nyhm0Hlg=(WWDgC<`$zjTe6bb;{bYdt6UK|b+Q$wK z;QBpMw=d)}I8tRMRNEoD+l-^`W1|nwj3z1Z;PqO{VyZFXc2dm*iE`piK|N0Pw!z(1i@-z|FU`W;^FEoOWRHZaXy2$o4@G&Vx}GY8CZzO=T3ot z-+{v1+N~iYw(tC5cEZ(E60lsQ4Fn~jL4mayWKNmu_@ z$su>KgCvuTqq>OooXb$dMkFWsG6Elux54ou0z0W!~vi>e(tXytnm-52tk-R-De_whzo0tGr?I=n< zvnh3o)v=0kfG!l5f(6sFBSj79HY6m(BsrheJrtv{k^SurR$;+Om`?~S23WUb&=u(* zK8cJ!sfq^%Pbf}1ZI2A8 z(Lt3r01!4c;!ee+p|yJrJi68Y2POrAZMV9_4opzs0?fFXrx^wso~=#&!5nx2@CN`X z8ANKKeaDtonk(H@A-`;(_jM85b@S~^+ppNG=6959sE2+uOH8kV>F{za^KbHW4_NIB zHo*n6TmU62Q-w;-O8k}^StjmAr9tpU_B@~PzN=8fqZ$k7Sc?pMpeptvnD+&l=f|(I z@`|_oUEvp4;p@Ng$`{4Ν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 Bn#TYD diff --git a/fastlane/metadata/android/es-ES/images/icon.png b/fastlane/metadata/android/es-ES/images/icon.png deleted file mode 100644 index face3ced8a9ab76f66b04c56b52f316730ea5d3a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 46053 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 fA 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 diff --git a/fastlane/metadata/android/es-ES/images/phoneScreenshots/1_es-ES.png b/fastlane/metadata/android/es-ES/images/phoneScreenshots/1_es-ES.png deleted file mode 100644 index cd5ff86cc4e268c085c12e1277789dca576c9098..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 63248 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

+g%zTNL6Q^$ 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 diff --git a/fastlane/metadata/android/es-ES/images/phoneScreenshots/2_es-ES.png b/fastlane/metadata/android/es-ES/images/phoneScreenshots/2_es-ES.png deleted file mode 100644 index d5b7eaa446d6d00203459fb653e868057d39528b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 24149 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 - - - - - - - - - - - - - - - - - diff --git a/functions/.dockerignore b/functions/.dockerignore new file mode 100644 index 0000000..730cbec --- /dev/null +++ b/functions/.dockerignore @@ -0,0 +1,2 @@ +node_modules +ui-debug.log \ No newline at end of file diff --git a/functions/daemon.json b/functions/daemon.json index 2c41e72..32b4eb2 100644 --- a/functions/daemon.json +++ b/functions/daemon.json @@ -3,7 +3,7 @@ { "name": "Newsriver-daemon", "script": "npm", - "args": "run updater" + "args": "run daemon" } ] } \ No newline at end of file diff --git a/functions/package.json b/functions/package.json index d39f13f..5c04d26 100644 --- a/functions/package.json +++ b/functions/package.json @@ -13,7 +13,7 @@ "daemon": "cross-env-shell RUN_DAEMON=true \"npm run build && firebase emulators:start --only functions\"" }, "engines": { - "node": "8" + "node": "10" }, "main": "lib/bin/index.js", "dependencies": { @@ -25,10 +25,10 @@ "http-errors": "^1.7.3", "morgan": "^1.10.0", "node-fetch": "^2.6.0", + "cross-env": "^7.0.2", "pug": "^3.0.0" }, "devDependencies": { - "cross-env": "^7.0.2", "firebase-functions-test": "^0.2.0", "tslint": "^5.12.0", "typescript": "^3.8.0" From e4d6d4b26392a0ff17993623acfe0bc35fb810fc Mon Sep 17 00:00:00 2001 From: Javinator9889 Date: Tue, 9 Jun 2020 10:12:18 +0200 Subject: [PATCH 30/95] Corrected unhandled exception on updater class --- Dockerfile | 2 -- functions/src/app.ts | 11 ----------- functions/src/updater.ts | 1 + 3 files changed, 1 insertion(+), 13 deletions(-) diff --git a/Dockerfile b/Dockerfile index ce2a977..cfa0aad 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,10 +3,8 @@ FROM node:10 WORKDIR /usr/src/app # Copy neccessary files COPY ./functions ./functions -COPY ./functions ./functions COPY ./firebase.json ./ COPY ./.firebaserc ./ -COPY ./.firebase ./ WORKDIR /usr/src/app/functions RUN npm i --only=production -g pm2@latest firebase-tools cross-env typescript RUN npm ci --only=production diff --git a/functions/src/app.ts b/functions/src/app.ts index 748ecb9..45a8d7a 100644 --- a/functions/src/app.ts +++ b/functions/src/app.ts @@ -16,17 +16,6 @@ app.use(express.json()); app.use(express.urlencoded({extended: false})); app.use(express.static(path.join(__dirname, 'public'))); -/** - * ------------- - * Test purposes - * ------------- - const testUpdater = new updater.Updater(null, null, ['covid-19', 'enfermedad'], functions.config().newsriver.key, 'es'); - app.get('/api', (req, res) => - testUpdater.request() - .then(it => res.json(it)) - ); - * --------------- - */ app.use(router); // catch 404 and forward to error handler app.use((req, res, next) => next(createError(404))); diff --git a/functions/src/updater.ts b/functions/src/updater.ts index abd7df6..344f031 100644 --- a/functions/src/updater.ts +++ b/functions/src/updater.ts @@ -47,6 +47,7 @@ export class Updater { .catch(ignored => { }); }) + .catch(err => console.warn(`Unable to update data due to exception: ${err}`)); } schedule(): NodeJS.Timer { From 251d65fffe956430ee87571c0d2279e3edf82cb2 Mon Sep 17 00:00:00 2001 From: Javinator9889 Date: Tue, 9 Jun 2020 11:12:56 +0200 Subject: [PATCH 31/95] Updated model for accessing only the Cloud Function --- firebase.json | 4 +-- functions/package-lock.json | 13 ++------ functions/package.json | 2 +- functions/src/app.ts | 2 +- functions/src/bin/index.ts | 6 ++-- functions/src/bin/www.ts | 3 +- public/404.html | 33 +++++++++++++++++++ public/index.html | 65 +++++++++++++++++++++++++++++++++++++ 8 files changed, 110 insertions(+), 18 deletions(-) create mode 100644 public/404.html create mode 100644 public/index.html diff --git a/firebase.json b/firebase.json index 01f98cc..acb4a4f 100644 --- a/firebase.json +++ b/firebase.json @@ -13,10 +13,10 @@ "**/.*", "**/node_modules/**" ], - "rewrites": [ + "rewrites": [ { "source": "/api/v1/**", - "function": "wwww.webApi" + "function": "www-webApi" } ] } diff --git a/functions/package-lock.json b/functions/package-lock.json index 70c10b2..1365c31 100644 --- a/functions/package-lock.json +++ b/functions/package-lock.json @@ -754,7 +754,6 @@ "version": "7.0.2", "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.2.tgz", "integrity": "sha512-KZP/bMEOJEDCkDQAyRhu3RL2ZO/SUVrxQVI0G3YEQ+OLbRA3c6zgixe8Mq8a/z7+HKlNEjo8oiLUs8iRijY2Rw==", - "dev": true, "requires": { "cross-spawn": "^7.0.1" } @@ -763,7 +762,6 @@ "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, "requires": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", @@ -1715,8 +1713,7 @@ "isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", - "dev": true + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" }, "js-stringify": { "version": "1.0.2", @@ -2109,8 +2106,7 @@ "path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==" }, "path-parse": { "version": "1.0.6", @@ -2488,7 +2484,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, "requires": { "shebang-regex": "^3.0.0" } @@ -2496,8 +2491,7 @@ "shebang-regex": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==" }, "side-channel": { "version": "1.0.2", @@ -2803,7 +2797,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, "requires": { "isexe": "^2.0.0" } diff --git a/functions/package.json b/functions/package.json index 5c04d26..da07624 100644 --- a/functions/package.json +++ b/functions/package.json @@ -7,7 +7,7 @@ "shell": "npm run build && firebase functions:shell", "start": "npm run shell", "deploy": "cross-env-shell RUN_SERVER=true RUN_DAEMON=true firebase deploy --only functions", - "deploy-server": "cross-env-shell RUN_SERVER=true firebase deploy --only functions:www", + "deploy-server": "cross-env-shell RUN_SERVER=true \"firebase deploy --only functions\"", "logs": "firebase functions:log", "server": "cross-env-shell RUN_SERVER=true \"npm run build && firebase emulators:start --only functions\"", "daemon": "cross-env-shell RUN_DAEMON=true \"npm run build && firebase emulators:start --only functions\"" diff --git a/functions/src/app.ts b/functions/src/app.ts index 45a8d7a..a0a1b7b 100644 --- a/functions/src/app.ts +++ b/functions/src/app.ts @@ -11,7 +11,7 @@ const app = express(); app.set('views', path.join(__dirname, 'views')); app.set('view engine', 'pug'); -app.use(logger('dev')); +app.use(logger('combined')); app.use(express.json()); app.use(express.urlencoded({extended: false})); app.use(express.static(path.join(__dirname, 'public'))); diff --git a/functions/src/bin/index.ts b/functions/src/bin/index.ts index e04036a..2f70335 100644 --- a/functions/src/bin/index.ts +++ b/functions/src/bin/index.ts @@ -1,5 +1,7 @@ -const runServer = process.env.RUN_SERVER; -const runDaemon = process.env.RUN_DAEMON; +import functions = require('firebase-functions'); + +const runServer = process.env.RUN_SERVER || functions.config().execution.run_server; +const runDaemon = process.env.RUN_DAEMON || functions.config().execution.run_daemon; if (runServer === undefined && runDaemon === undefined) { exports.www = require('./www'); diff --git a/functions/src/bin/www.ts b/functions/src/bin/www.ts index 24a8147..65cc5be 100644 --- a/functions/src/bin/www.ts +++ b/functions/src/bin/www.ts @@ -18,8 +18,7 @@ const server = http.createServer(app); */ server.on('error', onError); server.on('listening', onListening); -if (process.env.RUN_SERVER) - exports.webApi = functions.https.onRequest(app); +exports.webApi = functions.https.onRequest(app); /** * Normalize a port into a number, string or false diff --git a/public/404.html b/public/404.html new file mode 100644 index 0000000..829eda8 --- /dev/null +++ b/public/404.html @@ -0,0 +1,33 @@ + + + + + + Page Not Found + + + + +

+ + diff --git a/public/index.html b/public/index.html new file mode 100644 index 0000000..329f456 --- /dev/null +++ b/public/index.html @@ -0,0 +1,65 @@ + + + + + + Welcome to Firebase Hosting + + + + + + + + + + + + + + + +

Firebase SDK Loading…

+ + + + From f10a6b9a7c3e490c012abcffb1f10df0888525fd Mon Sep 17 00:00:00 2001 From: Javinator9889 Date: Tue, 9 Jun 2020 11:35:32 +0200 Subject: [PATCH 32/95] Correctly handled promises (end with a catch block) and some minor optimizations --- firebase.json | 6 +++++- functions/package.json | 2 +- functions/src/bin/daemon.ts | 10 ++++++++-- functions/src/models/updater.ts | 4 +--- functions/src/rcdata.ts | 4 +++- functions/src/updater.ts | 21 +++++++++++---------- functions/tsconfig.json | 3 ++- 7 files changed, 31 insertions(+), 19 deletions(-) diff --git a/firebase.json b/firebase.json index acb4a4f..88da25d 100644 --- a/firebase.json +++ b/firebase.json @@ -15,7 +15,11 @@ ], "rewrites": [ { - "source": "/api/v1/**", + "source": "/{,**}", + "destination": "/api/v1" + }, + { + "source": "/api/v1**", "function": "www-webApi" } ] diff --git a/functions/package.json b/functions/package.json index da07624..4499a69 100644 --- a/functions/package.json +++ b/functions/package.json @@ -13,7 +13,7 @@ "daemon": "cross-env-shell RUN_DAEMON=true \"npm run build && firebase emulators:start --only functions\"" }, "engines": { - "node": "10" + "node": "8" }, "main": "lib/bin/index.js", "dependencies": { diff --git a/functions/src/bin/daemon.ts b/functions/src/bin/daemon.ts index 2312f97..fad031d 100644 --- a/functions/src/bin/daemon.ts +++ b/functions/src/bin/daemon.ts @@ -3,11 +3,17 @@ import functions = require('firebase-functions'); updater.initialize() - .then(_ => updater.scheduleUpdates()); + .then(_ => updater.scheduleUpdates() + .catch(err => console.warn(`Error while scheduling updates - ${err}`))) + .catch(err => { + console.error(`Error while initializing the updater - ${err}`); + process.exit(1); + }); process.on('SIGINT', () => { updater.stopScheduling() - .then(process.exit(0)); + .finally(process.exit(0)) + .catch(err => console.warn(`Error while finishing the schedules - ${err}`)); }); exports.updater = functions.https.onRequest((req, resp) => resp.sendStatus(200)); diff --git a/functions/src/models/updater.ts b/functions/src/models/updater.ts index 979dcfb..7c756d3 100644 --- a/functions/src/models/updater.ts +++ b/functions/src/models/updater.ts @@ -20,9 +20,7 @@ export async function initialize() { initCalled = true; const projectProperties = properties.projectProperties(firebaseApp); for (const language of properties.languages) { - console.debug(`Creating updater for language ${language}`); const terms = await remoteConfig.getSearchTermsForLanguage(language); - console.debug(`Updater terms: ${terms}`); updaters[language] = new Updater( projectProperties.database, `${projectProperties.collection}_${language}`, @@ -42,7 +40,7 @@ export async function initialize() { export async function scheduleUpdates() { if (!initCalled) throw new Error('`initialize` not called'); - console.info('Updater is scheduling updates') + console.info('Updaters are scheduling updates') for (const language of properties.languages) { timers.add(updaters[language].schedule()); } diff --git a/functions/src/rcdata.ts b/functions/src/rcdata.ts index 94f6f6c..86282ec 100644 --- a/functions/src/rcdata.ts +++ b/functions/src/rcdata.ts @@ -36,6 +36,7 @@ export class RemoteConfigData { listenToRCChanges() { functions.remoteConfig.onUpdate(_ => { return admin.credential.applicationDefault().getAccessToken() + // tslint:disable-next-line:no-shadowed-variable .then(_ => { this.remoteConfig.getTemplate() .then(template => { @@ -51,7 +52,8 @@ export class RemoteConfigData { console.warn(`Updaters are not set yet - ${e}`); } } - }); + }) + .catch(err => console.warn(`Error while obtaining the template - ${err}`)); }) .catch(err => console.error(`Error while obtaining data from RC: ${err}`)); }); diff --git a/functions/src/updater.ts b/functions/src/updater.ts index 344f031..8710c91 100644 --- a/functions/src/updater.ts +++ b/functions/src/updater.ts @@ -63,16 +63,17 @@ export class Updater { async updateData(content: Array) { try { - content.forEach(element => { - firebaseHelper.firestore.checkDocumentExists(this.db, this.collectionName, element.id) - .then(exists => { - if (!exists) - firebaseHelper.firestore.createDocumentWithID(this.db, this.collectionName, element.id, element); - else - firebaseHelper.firestore.updateDocument(this.db, this.collectionName, element.id, element); - }) - console.log(`Created element with ID: ${element.id}`); - }); + for (const element of content) { + try { + const exists = await firebaseHelper.firestore.checkDocumentExists(this.db, this.collectionName, element.id); + if (!exists) + await firebaseHelper.firestore.createDocumentWithID(this.db, this.collectionName, element.id, element); + else + await firebaseHelper.firestore.updateDocument(this.db, this.collectionName, element.id, element); + } catch (err) { + console.warn(`Error while creating/updating document - ${err}`); + } + } } catch (error) { console.error(`Unhandled error ${error}`); } diff --git a/functions/tsconfig.json b/functions/tsconfig.json index 872b7f9..8ef9748 100644 --- a/functions/tsconfig.json +++ b/functions/tsconfig.json @@ -6,7 +6,8 @@ "outDir": "lib", "sourceMap": true, "strict": false, - "target": "es2017" + "target": "es2017", + "lib": ["es2015", "es2016", "dom", "es2018.promise"] }, "compileOnSave": true, "include": [ From f26cbc107b223a0cdcde9622d323d45c5bb338a5 Mon Sep 17 00:00:00 2001 From: Javinator9889 Date: Tue, 9 Jun 2020 11:45:27 +0200 Subject: [PATCH 33/95] Removed unnecessary files --- Gemfile | 3 - Gemfile.lock | 179 --------------------------------------------------- 2 files changed, 182 deletions(-) delete mode 100644 Gemfile delete mode 100644 Gemfile.lock diff --git a/Gemfile b/Gemfile deleted file mode 100644 index 7a118b4..0000000 --- a/Gemfile +++ /dev/null @@ -1,3 +0,0 @@ -source "https://rubygems.org" - -gem "fastlane" diff --git a/Gemfile.lock b/Gemfile.lock deleted file mode 100644 index 8674c5e..0000000 --- a/Gemfile.lock +++ /dev/null @@ -1,179 +0,0 @@ -GEM - remote: https://rubygems.org/ - specs: - CFPropertyList (3.0.2) - addressable (2.7.0) - public_suffix (>= 2.0.2, < 5.0) - atomos (0.1.3) - aws-eventstream (1.0.3) - aws-partitions (1.295.0) - aws-sdk-core (3.93.0) - aws-eventstream (~> 1.0, >= 1.0.2) - aws-partitions (~> 1, >= 1.239.0) - aws-sigv4 (~> 1.1) - jmespath (~> 1.0) - aws-sdk-kms (1.30.0) - aws-sdk-core (~> 3, >= 3.71.0) - aws-sigv4 (~> 1.1) - aws-sdk-s3 (1.61.2) - aws-sdk-core (~> 3, >= 3.83.0) - aws-sdk-kms (~> 1) - aws-sigv4 (~> 1.1) - aws-sigv4 (1.1.1) - aws-eventstream (~> 1.0, >= 1.0.2) - babosa (1.0.3) - claide (1.0.3) - colored (1.2) - colored2 (3.1.2) - commander-fastlane (4.4.6) - highline (~> 1.7.2) - declarative (0.0.10) - declarative-option (0.1.0) - digest-crc (0.5.1) - domain_name (0.5.20190701) - unf (>= 0.0.5, < 1.0.0) - dotenv (2.7.5) - emoji_regex (1.0.1) - excon (0.73.0) - faraday (0.17.3) - multipart-post (>= 1.2, < 3) - faraday-cookie_jar (0.0.6) - faraday (>= 0.7.4) - http-cookie (~> 1.0.0) - faraday_middleware (0.13.1) - faraday (>= 0.7.4, < 1.0) - fastimage (2.1.7) - fastlane (2.145.0) - CFPropertyList (>= 2.3, < 4.0.0) - addressable (>= 2.3, < 3.0.0) - aws-sdk-s3 (~> 1.0) - babosa (>= 1.0.2, < 2.0.0) - bundler (>= 1.12.0, < 3.0.0) - colored - commander-fastlane (>= 4.4.6, < 5.0.0) - dotenv (>= 2.1.1, < 3.0.0) - emoji_regex (>= 0.1, < 2.0) - excon (>= 0.71.0, < 1.0.0) - faraday (~> 0.17) - faraday-cookie_jar (~> 0.0.6) - faraday_middleware (~> 0.13.1) - fastimage (>= 2.1.0, < 3.0.0) - gh_inspector (>= 1.1.2, < 2.0.0) - google-api-client (>= 0.29.2, < 0.37.0) - google-cloud-storage (>= 1.15.0, < 2.0.0) - highline (>= 1.7.2, < 2.0.0) - json (< 3.0.0) - jwt (~> 2.1.0) - mini_magick (>= 4.9.4, < 5.0.0) - multi_xml (~> 0.5) - multipart-post (~> 2.0.0) - plist (>= 3.1.0, < 4.0.0) - public_suffix (~> 2.0.0) - rubyzip (>= 1.3.0, < 2.0.0) - security (= 0.1.3) - simctl (~> 1.6.3) - slack-notifier (>= 2.0.0, < 3.0.0) - terminal-notifier (>= 2.0.0, < 3.0.0) - terminal-table (>= 1.4.5, < 2.0.0) - tty-screen (>= 0.6.3, < 1.0.0) - tty-spinner (>= 0.8.0, < 1.0.0) - word_wrap (~> 1.0.0) - xcodeproj (>= 1.13.0, < 2.0.0) - xcpretty (~> 0.3.0) - xcpretty-travis-formatter (>= 0.0.3) - gh_inspector (1.1.3) - google-api-client (0.36.4) - addressable (~> 2.5, >= 2.5.1) - googleauth (~> 0.9) - httpclient (>= 2.8.1, < 3.0) - mini_mime (~> 1.0) - representable (~> 3.0) - retriable (>= 2.0, < 4.0) - signet (~> 0.12) - google-cloud-core (1.5.0) - google-cloud-env (~> 1.0) - google-cloud-errors (~> 1.0) - google-cloud-env (1.3.1) - faraday (>= 0.17.3, < 2.0) - google-cloud-errors (1.0.0) - google-cloud-storage (1.26.0) - addressable (~> 2.5) - digest-crc (~> 0.4) - google-api-client (~> 0.33) - google-cloud-core (~> 1.2) - googleauth (~> 0.9) - mini_mime (~> 1.0) - googleauth (0.11.0) - faraday (>= 0.17.3, < 2.0) - jwt (>= 1.4, < 3.0) - memoist (~> 0.16) - multi_json (~> 1.11) - os (>= 0.9, < 2.0) - signet (~> 0.12) - highline (1.7.10) - http-cookie (1.0.3) - domain_name (~> 0.5) - httpclient (2.8.3) - jmespath (1.4.0) - json (2.1.0) - jwt (2.1.0) - memoist (0.16.2) - mini_magick (4.10.1) - mini_mime (1.0.2) - multi_json (1.14.1) - multi_xml (0.6.0) - multipart-post (2.0.0) - nanaimo (0.2.6) - naturally (2.2.0) - os (1.1.0) - plist (3.5.0) - public_suffix (2.0.5) - representable (3.0.4) - declarative (< 0.1.0) - declarative-option (< 0.2.0) - uber (< 0.2.0) - retriable (3.1.2) - rouge (2.0.7) - rubyzip (1.3.0) - security (0.1.3) - signet (0.14.0) - addressable (~> 2.3) - faraday (>= 0.17.3, < 2.0) - jwt (>= 1.5, < 3.0) - multi_json (~> 1.10) - simctl (1.6.8) - CFPropertyList - naturally - slack-notifier (2.3.2) - terminal-notifier (2.0.0) - terminal-table (1.8.0) - unicode-display_width (~> 1.1, >= 1.1.1) - tty-cursor (0.7.1) - tty-screen (0.7.1) - tty-spinner (0.9.3) - tty-cursor (~> 0.7) - uber (0.1.0) - unf (0.1.4) - unf_ext - unf_ext (0.0.7.7) - unicode-display_width (1.7.0) - word_wrap (1.0.0) - xcodeproj (1.15.0) - CFPropertyList (>= 2.3.3, < 4.0) - atomos (~> 0.1.3) - claide (>= 1.0.2, < 2.0) - colored2 (~> 3.1) - nanaimo (~> 0.2.6) - xcpretty (0.3.0) - rouge (~> 2.0.7) - xcpretty-travis-formatter (1.0.0) - xcpretty (~> 0.2, >= 0.0.7) - -PLATFORMS - ruby - -DEPENDENCIES - fastlane - -BUNDLED WITH - 2.1.4 From 1635ad5cc0115ba83c8d197fbc1132cdc634176f Mon Sep 17 00:00:00 2001 From: Javinator9889 Date: Tue, 9 Jun 2020 11:46:59 +0200 Subject: [PATCH 34/95] Updated NodeJS docker version --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index cfa0aad..9946244 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM node:10 +FROM node:lts-alpine # app directory WORKDIR /usr/src/app # Copy neccessary files From 2e464b01671a6e10d236702e59732049918b3358 Mon Sep 17 00:00:00 2001 From: Javinator9889 Date: Tue, 9 Jun 2020 11:52:13 +0200 Subject: [PATCH 35/95] undefined/nullability type checking for avoiding possible "TypeErrors" --- functions/src/bin/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/functions/src/bin/index.ts b/functions/src/bin/index.ts index 2f70335..e036f8c 100644 --- a/functions/src/bin/index.ts +++ b/functions/src/bin/index.ts @@ -1,7 +1,7 @@ import functions = require('firebase-functions'); -const runServer = process.env.RUN_SERVER || functions.config().execution.run_server; -const runDaemon = process.env.RUN_DAEMON || functions.config().execution.run_daemon; +const runServer = process.env.RUN_SERVER || functions.config().execution?.run_server; +const runDaemon = process.env.RUN_DAEMON || functions.config().execution?.run_daemon; if (runServer === undefined && runDaemon === undefined) { exports.www = require('./www'); From 9ef15d42975e795d6d9fdf6e2ed8627bc6a3b60e Mon Sep 17 00:00:00 2001 From: Javinator9889 Date: Tue, 9 Jun 2020 11:57:11 +0200 Subject: [PATCH 36/95] Show information when elements were updated --- functions/src/updater.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/functions/src/updater.ts b/functions/src/updater.ts index 8710c91..29512af 100644 --- a/functions/src/updater.ts +++ b/functions/src/updater.ts @@ -74,6 +74,7 @@ export class Updater { console.warn(`Error while creating/updating document - ${err}`); } } + console.info(`Updated approximately ${content.length} element(s)`); } catch (error) { console.error(`Unhandled error ${error}`); } From 07381ba58d0f0d452cfcd1178b19c016906c1fa6 Mon Sep 17 00:00:00 2001 From: Javinator9889 Date: Tue, 9 Jun 2020 11:57:11 +0200 Subject: [PATCH 37/95] Show information when elements were updated --- functions/src/updater.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/functions/src/updater.ts b/functions/src/updater.ts index 8710c91..1716e1e 100644 --- a/functions/src/updater.ts +++ b/functions/src/updater.ts @@ -33,7 +33,7 @@ export class Updater { searchTerms: Array, auth: string, language: string = 'en', - intervalMins: number = 15) { + intervalMins: number = 30) { this.db = db; this.collectionName = collectionName; this.searchTerms = searchTerms; @@ -74,6 +74,7 @@ export class Updater { console.warn(`Error while creating/updating document - ${err}`); } } + console.info(`Updated approximately ${content.length} element(s)`); } catch (error) { console.error(`Unhandled error ${error}`); } From 78b8dd96b3e9c43337cccb28c3c258cda341691d Mon Sep 17 00:00:00 2001 From: Javinator9889 Date: Tue, 9 Jun 2020 14:03:58 +0200 Subject: [PATCH 38/95] Moved FirebaseApp logic to 'common' file and updated token verification method --- .../handwashingreminder/firebase/Auth.kt | 78 +++++++++++++++++++ functions/package.json | 4 +- functions/src/common/properties.ts | 11 ++- functions/src/controllers/newsriver.ts | 4 +- functions/src/models/newsriver.ts | 6 +- functions/src/models/updater.ts | 10 +-- functions/src/routes/newsriver.ts | 5 +- 7 files changed, 98 insertions(+), 20 deletions(-) create mode 100644 app/src/main/java/com/javinator9889/handwashingreminder/firebase/Auth.kt diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/firebase/Auth.kt b/app/src/main/java/com/javinator9889/handwashingreminder/firebase/Auth.kt new file mode 100644 index 0000000..59cd5e9 --- /dev/null +++ b/app/src/main/java/com/javinator9889/handwashingreminder/firebase/Auth.kt @@ -0,0 +1,78 @@ +/* + * Copyright © 2020 - present | Handwashing reminder by Javinator9889 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see https://www.gnu.org/licenses/. + * + * Created by Javinator9889 on 9/06/20 - Handwashing reminder. + */ +package com.javinator9889.handwashingreminder.firebase + +import com.google.firebase.auth.FirebaseAuth +import com.google.firebase.auth.FirebaseUser +import kotlinx.coroutines.channels.Channel +import timber.log.Timber +import kotlin.IllegalStateException + + +object Auth { + private lateinit var auth: FirebaseAuth + private lateinit var currentUser: FirebaseUser + private lateinit var privateToken: String + + suspend fun init() { + if (!::auth.isInitialized) + auth = FirebaseAuth.getInstance() + val currentUser = auth.currentUser + val signedIn = Channel(0) + if (currentUser == null) { + Timber.d("No user defined lo signing-in") + auth.signInAnonymously() + .addOnCompleteListener { task -> + signedIn.offer(task.isSuccessful) + if (!task.isSuccessful) + Timber.e(task.exception, "Error while logging") + } + if (signedIn.receive()) { + Timber.d("Signing-in successful so saving user") + this.currentUser = auth.currentUser!! + } + } else this.currentUser = currentUser + } + + suspend fun token(): String { + if (!::auth.isInitialized) + throw IllegalStateException("init must be called first") + if (::privateToken.isInitialized) { + Timber.d("Obtaining previous generated token") + return privateToken + } + val tokenChannel = Channel(0) + currentUser.getIdToken(true) + .addOnCompleteListener { task -> + if (task.isSuccessful) + tokenChannel.offer(task.result!!.token!!) + else + tokenChannel.offer(null) + } + privateToken = tokenChannel.receive() + ?: throw IllegalStateException("token not valid") + return privateToken + } + + fun logout() { + if (!::auth.isInitialized) + throw IllegalStateException("init must be called first") + auth.signOut() + } +} \ No newline at end of file diff --git a/functions/package.json b/functions/package.json index 4499a69..df0ee08 100644 --- a/functions/package.json +++ b/functions/package.json @@ -9,8 +9,8 @@ "deploy": "cross-env-shell RUN_SERVER=true RUN_DAEMON=true firebase deploy --only functions", "deploy-server": "cross-env-shell RUN_SERVER=true \"firebase deploy --only functions\"", "logs": "firebase functions:log", - "server": "cross-env-shell RUN_SERVER=true \"npm run build && firebase emulators:start --only functions\"", - "daemon": "cross-env-shell RUN_DAEMON=true \"npm run build && firebase emulators:start --only functions\"" + "server": "cross-env-shell RUN_SERVER=true RUNNING_LOCAL=true \"npm run build && firebase emulators:start --only functions\"", + "daemon": "cross-env-shell RUN_DAEMON=true RUNNING_LOCAL=true \"npm run build && firebase emulators:start --only functions\"" }, "engines": { "node": "8" diff --git a/functions/src/common/properties.ts b/functions/src/common/properties.ts index aacd0c4..73e1e8f 100644 --- a/functions/src/common/properties.ts +++ b/functions/src/common/properties.ts @@ -11,4 +11,13 @@ export function projectProperties(app?: admin.app.App): ProjectProperties { authToken: functions.config().newsriver.key } } -export const databaseURL = 'https://handwashing.firebaseio.com'; \ No newline at end of file +export const databaseURL = 'https://handwashing.firebaseio.com'; + +let serviceAccount = undefined +if (process.env.RUNNING_LOCAL) + serviceAccount = require('../../handwashing-firebase-adminsdk.json'); + +export const firebaseApp = admin.initializeApp({ + credential: process.env.RUNNING_LOCAL ? admin.credential.cert(serviceAccount) : admin.credential.applicationDefault(), + databaseURL: databaseURL +}); \ No newline at end of file diff --git a/functions/src/controllers/newsriver.ts b/functions/src/controllers/newsriver.ts index 893f16c..95f0f83 100644 --- a/functions/src/controllers/newsriver.ts +++ b/functions/src/controllers/newsriver.ts @@ -11,8 +11,8 @@ export async function queryNewsForLanguage(req: Request, res: Response) { if (fromParam === undefined && amountParam === undefined) { res.json(newsData); } else { - const from = fromParam === undefined ? Number(fromParam) : 0; - const amount = amountParam === undefined ? Number(amountParam) : 0; + const from = fromParam !== undefined ? Number(fromParam) : 0; + const amount = amountParam !== undefined ? Number(amountParam) : newsData.length; res.json(newsData.slice(from, from + amount)); } diff --git a/functions/src/models/newsriver.ts b/functions/src/models/newsriver.ts index c747d45..6c8c690 100644 --- a/functions/src/models/newsriver.ts +++ b/functions/src/models/newsriver.ts @@ -1,18 +1,14 @@ -import admin = require('firebase-admin'); import {NewsriverData} from "../newsriver"; import {Database} from "../database"; import properties = require('../common/properties'); -export const firebaseApp = admin.initializeApp({ - databaseURL: properties.databaseURL -}); const databases: Record = {}; let initCalled = false; export async function initialize() { initCalled = true; - const projectProperties = properties.projectProperties(firebaseApp); + const projectProperties = properties.projectProperties(properties.firebaseApp); for (const language of properties.languages) { databases[language] = new Database( projectProperties.database, diff --git a/functions/src/models/updater.ts b/functions/src/models/updater.ts index 7c756d3..d7bce2d 100644 --- a/functions/src/models/updater.ts +++ b/functions/src/models/updater.ts @@ -1,16 +1,10 @@ import {Updater} from '../updater'; import {RemoteConfigData} from "../rcdata"; -import admin = require('firebase-admin'); import properties = require('../common/properties'); -const serviceAccount = require('../../handwashing-firebase-adminsdk.json'); -export const firebaseApp = admin.initializeApp({ - credential: admin.credential.cert(serviceAccount), - databaseURL: properties.databaseURL -}); const updaters: Record = {}; -const remoteConfig = new RemoteConfigData(firebaseApp); +const remoteConfig = new RemoteConfigData(properties.firebaseApp); const timers = new Set(); let initCalled = false; @@ -18,7 +12,7 @@ export async function initialize() { try { console.info('Updater is being initialized'); initCalled = true; - const projectProperties = properties.projectProperties(firebaseApp); + const projectProperties = properties.projectProperties(properties.firebaseApp); for (const language of properties.languages) { const terms = await remoteConfig.getSearchTermsForLanguage(language); updaters[language] = new Updater( diff --git a/functions/src/routes/newsriver.ts b/functions/src/routes/newsriver.ts index dd774c5..c75f269 100644 --- a/functions/src/routes/newsriver.ts +++ b/functions/src/routes/newsriver.ts @@ -1,6 +1,7 @@ import apiController = require("../controllers/newsriver"); import express = require("express"); import admin = require("firebase-admin"); +import properties = require('../common/properties'); const router = express.Router(); @@ -14,8 +15,8 @@ apiController.apiModel.initialize() router.get('/api/v1', (req, res, next) => { try { const language = req.query.lang; - const tokenId = req.get('Authorization').split('Bearer')[0]; - admin.auth(apiController.apiModel.firebaseApp).verifyIdToken(tokenId) + const tokenId = req.headers.authorization.split(' ')[1]; + admin.auth(properties.firebaseApp).verifyIdToken(tokenId) .then(_ => { if (language === undefined) res.status(403).send('lang must be given [?lang=...]'); From 15aaaa4204be2706438f9a8d768c85fc9451e579 Mon Sep 17 00:00:00 2001 From: Javinator9889 Date: Tue, 9 Jun 2020 14:34:35 +0200 Subject: [PATCH 39/95] Updated model for using Firebase Auth and requesting news --- app/build.gradle | 5 +- .../activities/LauncherActivity.kt | 19 +- .../activities/MainActivity.kt | 9 + .../views/fragments/news/NewsFragment.kt | 6 +- .../viewmodels/DiseaseInformationViewModel.kt | 2 +- .../views/viewmodels/NewsViewModel.kt | 17 +- .../activities/views/viewmodels/VideoModel.kt | 8 +- .../data/UserProperties.kt | 40 ++++ .../network/HttpDownloader.kt | 4 +- .../handwashingreminder/utils/Constants.kt | 215 +++++++----------- 10 files changed, 174 insertions(+), 151 deletions(-) create mode 100644 app/src/main/java/com/javinator9889/handwashingreminder/data/UserProperties.kt diff --git a/app/build.gradle b/app/build.gradle index ebedb75..591cac2 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -146,9 +146,10 @@ dependencies { implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version" // https://firebase.google.com/docs/android/setup#add-sdks api 'com.google.firebase:firebase-common-ktx:19.3.0' - api 'com.google.firebase:firebase-analytics:17.4.2' - api 'com.google.firebase:firebase-crashlytics:17.0.0' + api 'com.google.firebase:firebase-analytics:17.4.3' + api 'com.google.firebase:firebase-crashlytics:17.0.1' api 'com.google.firebase:firebase-perf:19.0.7' + implementation 'com.google.firebase:firebase-auth:19.3.1' // http://airbnb.io/lottie/#/android?id=getting-started api "com.airbnb.android:lottie:3.4.0" // https://firebase.google.com/docs/remote-config/use-config-android diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/activities/LauncherActivity.kt b/app/src/main/java/com/javinator9889/handwashingreminder/activities/LauncherActivity.kt index 67e11f0..dbe97ba 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/activities/LauncherActivity.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/activities/LauncherActivity.kt @@ -40,23 +40,22 @@ import com.google.firebase.remoteconfig.FirebaseRemoteConfigSettings import com.google.firebase.remoteconfig.ktx.remoteConfig import com.javinator9889.handwashingreminder.R import com.javinator9889.handwashingreminder.application.HandwashingApplication +import com.javinator9889.handwashingreminder.data.UserProperties import com.javinator9889.handwashingreminder.emoji.EmojiLoader import com.javinator9889.handwashingreminder.gms.ads.AdLoader import com.javinator9889.handwashingreminder.gms.ads.AdsEnabler import com.javinator9889.handwashingreminder.jobs.alarms.AlarmHandler import com.javinator9889.handwashingreminder.utils.* -import com.javinator9889.handwashingreminder.utils.Preferences.Companion.ADS_ENABLED -import com.javinator9889.handwashingreminder.utils.Preferences.Companion.APP_INIT_KEY -import com.javinator9889.handwashingreminder.utils.RemoteConfig.Keys.SPECIAL_EVENT +import com.javinator9889.handwashingreminder.utils.Preferences.ADS_ENABLED +import com.javinator9889.handwashingreminder.utils.Preferences.APP_INIT_KEY +import com.javinator9889.handwashingreminder.utils.RemoteConfig.SPECIAL_EVENT import com.mikepenz.iconics.Iconics -import javinator9889.localemanager.utils.languagesupport.LanguagesSupport +import javinator9889.localemanager.utils.languagesupport.LanguagesSupport.Language import kotlinx.android.synthetic.main.splash_screen.* import kotlinx.coroutines.* import org.conscrypt.Conscrypt import timber.log.Timber import java.security.Security -import java.util.* -import kotlin.collections.ArrayList import com.javinator9889.handwashingreminder.utils.Firebase as FirebaseConf internal const val FAST_START_KEY = "intent:fast_start" @@ -283,18 +282,18 @@ class LauncherActivity : AppCompatActivity() { Timber.d("Initializing Firebase Remote Config") setConfigSettingsAsync(config) setDefaultsAsync( - when (Locale.getDefault().language) { - Locale(LanguagesSupport.Language.SPANISH).language -> { + when (UserProperties.language) { + Language.SPANISH -> { firebaseAnalytics.setUserProperty( FirebaseConf.Properties.LANGUAGE, - LanguagesSupport.Language.SPANISH + Language.SPANISH ) R.xml.remote_config_defaults_es } else -> { firebaseAnalytics.setUserProperty( FirebaseConf.Properties.LANGUAGE, - LanguagesSupport.Language.ENGLISH + Language.ENGLISH ) R.xml.remote_config_defaults } diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/activities/MainActivity.kt b/app/src/main/java/com/javinator9889/handwashingreminder/activities/MainActivity.kt index 1219b03..58c7fa8 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/activities/MainActivity.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/activities/MainActivity.kt @@ -41,6 +41,7 @@ import com.javinator9889.handwashingreminder.activities.views.fragments.news.New import com.javinator9889.handwashingreminder.activities.views.fragments.settings.SettingsView import com.javinator9889.handwashingreminder.activities.views.fragments.washinghands.WashingHandsFragment import com.javinator9889.handwashingreminder.custom.libraries.AppRate +import com.javinator9889.handwashingreminder.firebase.Auth import com.javinator9889.handwashingreminder.utils.Preferences import com.javinator9889.handwashingreminder.utils.isDebuggable import com.mikepenz.iconics.IconicsDrawable @@ -95,6 +96,14 @@ class MainActivity : ActionBarBase(), } } + override fun onDestroy() { + try { + Auth.logout() + } finally { + super.onDestroy() + } + } + protected fun delegateMenuIcons(menu: BottomNavigationView) { thread(start = true) { menu.menu.forEach { item -> diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/news/NewsFragment.kt b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/news/NewsFragment.kt index 0b5645a..e80bd23 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/news/NewsFragment.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/news/NewsFragment.kt @@ -32,6 +32,7 @@ import com.javinator9889.handwashingreminder.R import com.javinator9889.handwashingreminder.activities.base.BaseFragmentView import com.javinator9889.handwashingreminder.activities.views.fragments.news.adapter.News import com.javinator9889.handwashingreminder.activities.views.viewmodels.NewsViewModel +import com.javinator9889.handwashingreminder.data.UserProperties import com.mikepenz.fastadapter.FastAdapter import com.mikepenz.fastadapter.GenericItem import com.mikepenz.fastadapter.adapters.GenericItemAdapter @@ -57,7 +58,7 @@ class NewsFragment : BaseFragmentView() { lifecycleScope.launch { whenStarted { loading.visibility = View.VISIBLE - launch { newsViewModel.populateData() } + launch { newsViewModel.populateData(language = UserProperties.language) } newsViewModel.newsData.observe(viewLifecycleOwner, Observer { if (::footerAdapter.isInitialized) footerAdapter.clear() @@ -99,7 +100,8 @@ class NewsFragment : BaseFragmentView() { lifecycleScope.launch { newsViewModel.populateData( from = newsAdapter.adapterItemCount, - amount = 20 + amount = 20, + language = UserProperties.language ) } } diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/viewmodels/DiseaseInformationViewModel.kt b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/viewmodels/DiseaseInformationViewModel.kt index e978ada..3e8975d 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/viewmodels/DiseaseInformationViewModel.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/viewmodels/DiseaseInformationViewModel.kt @@ -32,7 +32,7 @@ import com.google.firebase.remoteconfig.ktx.remoteConfig import com.javinator9889.handwashingreminder.collections.DiseasesInformation import com.javinator9889.handwashingreminder.collections.DiseasesList import com.javinator9889.handwashingreminder.collections.DiseasesListWrapper -import com.javinator9889.handwashingreminder.utils.RemoteConfig.Keys.DISEASES_JSON +import com.javinator9889.handwashingreminder.utils.RemoteConfig.DISEASES_JSON import kotlinx.coroutines.* import org.sufficientlysecure.htmltextview.HtmlFormatter import org.sufficientlysecure.htmltextview.HtmlFormatterBuilder diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/viewmodels/NewsViewModel.kt b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/viewmodels/NewsViewModel.kt index 3205f44..6858706 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/viewmodels/NewsViewModel.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/viewmodels/NewsViewModel.kt @@ -23,15 +23,24 @@ import androidx.lifecycle.ViewModel import com.beust.klaxon.JsonReader import com.beust.klaxon.Klaxon import com.javinator9889.handwashingreminder.collections.* +import com.javinator9889.handwashingreminder.firebase.Auth import com.javinator9889.handwashingreminder.network.HttpDownloader +import com.javinator9889.handwashingreminder.utils.API_URL +import javinator9889.localemanager.utils.languagesupport.LanguagesSupport.Language import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext +import okhttp3.Headers +import timber.log.Timber import java.io.Reader class NewsViewModel : ViewModel() { val newsData: MutableLiveData = MutableLiveData() - suspend fun populateData(from: Int = 0, amount: Int = 10) { + suspend fun populateData( + from: Int = 0, + amount: Int = 10, + @Language language: String = Language.ENGLISH + ) { val httpRequest = HttpDownloader() val klaxon = with(Klaxon()) { propertyStrategy(newsStrategy) @@ -39,9 +48,13 @@ class NewsViewModel : ViewModel() { fieldConverter(KlaxonElements::class, elementConverter) } var requestReader: Reader? = null + Auth.init() + val token = Auth.token() + Timber.d("Auth token: $token") withContext(Dispatchers.IO) { requestReader = httpRequest.json( - "http://10.0.2.2:3000/api/v1?from=$from&amount=$amount" + "${API_URL}/api/v1?from=$from&amount=$amount&lang=$language", + headers = Headers.of(mapOf("Authorization" to "Bearer $token")) ) } withContext(Dispatchers.Default) { diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/viewmodels/VideoModel.kt b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/viewmodels/VideoModel.kt index db0abca..ff05116 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/viewmodels/VideoModel.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/viewmodels/VideoModel.kt @@ -25,10 +25,10 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.liveData import com.javinator9889.handwashingreminder.application.HandwashingApplication import com.javinator9889.handwashingreminder.network.HttpDownloader -import com.javinator9889.handwashingreminder.utils.Videos.URI.FILENAME -import com.javinator9889.handwashingreminder.utils.Videos.URI.HASH -import com.javinator9889.handwashingreminder.utils.Videos.URI.URL -import com.javinator9889.handwashingreminder.utils.Videos.URI.VideoList +import com.javinator9889.handwashingreminder.utils.Videos.FILENAME +import com.javinator9889.handwashingreminder.utils.Videos.HASH +import com.javinator9889.handwashingreminder.utils.Videos.URL +import com.javinator9889.handwashingreminder.utils.Videos.VideoList import com.javinator9889.handwashingreminder.utils.isConnected import com.javinator9889.handwashingreminder.utils.trace import kotlinx.coroutines.Dispatchers diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/data/UserProperties.kt b/app/src/main/java/com/javinator9889/handwashingreminder/data/UserProperties.kt new file mode 100644 index 0000000..ae08fd8 --- /dev/null +++ b/app/src/main/java/com/javinator9889/handwashingreminder/data/UserProperties.kt @@ -0,0 +1,40 @@ +/* + * Copyright © 2020 - present | Handwashing reminder by Javinator9889 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see https://www.gnu.org/licenses/. + * + * Created by Javinator9889 on 9/06/20 - Handwashing reminder. + */ +package com.javinator9889.handwashingreminder.data + +import javinator9889.localemanager.utils.languagesupport.LanguagesSupport.Language +import java.util.* + + +object UserProperties { + @Language + private lateinit var privateLanguage: String + + @Language + val language: String + get() { + if (::privateLanguage.isInitialized) + return privateLanguage + privateLanguage = when (Locale.getDefault().language) { + Locale(Language.SPANISH).language -> Language.SPANISH + else -> Language.ENGLISH + } + return privateLanguage + } +} \ No newline at end of file diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/network/HttpDownloader.kt b/app/src/main/java/com/javinator9889/handwashingreminder/network/HttpDownloader.kt index b29ca1b..664b0bd 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/network/HttpDownloader.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/network/HttpDownloader.kt @@ -19,6 +19,7 @@ package com.javinator9889.handwashingreminder.network import okhttp3.CacheControl +import okhttp3.Headers import okhttp3.OkHttpClient import okhttp3.Request import okio.BufferedSource @@ -43,10 +44,11 @@ class HttpDownloader : OkHttpDownloader { } } - fun json(url: String): Reader { + fun json(url: String, headers: Headers? = null): Reader { val request = with(Request.Builder()) { url(url) cacheControl(CacheControl.FORCE_NETWORK) + headers?.let { headers(it) } build() } with(client.newCall(request).execute()) { diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/utils/Constants.kt b/app/src/main/java/com/javinator9889/handwashingreminder/utils/Constants.kt index 07886d4..9495bb5 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/utils/Constants.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/utils/Constants.kt @@ -23,147 +23,101 @@ import com.google.android.gms.location.DetectedActivity const val TIME_CHANNEL_ID = "timeNotificationsChannel" const val ACTIVITY_CHANNEL_ID = "activityNotificationsChannel" -class Preferences { - companion object { - const val CREATE_CHANNEL_KEY = "notifications:channel:create" - const val APP_INIT_KEY = "app:initialized" - const val ADS_ENABLED = "app:ads:enabled" - const val BREAKFAST_TIME = "app:breakfast" - const val LUNCH_TIME = "app:lunch" - const val DINNER_TIME = "app:dinner" - const val ANALYTICS_ENABLED = "firebase:analytics" - const val PERFORMANCE_ENABLED = "firebase:performance" - const val ACTIVITY_TRACKING_ENABLED = "activity:gms:tracking" - const val ACTIVITIES_ENABLED = "activity:gms:activities:enabled" - val DEFAULT_ACTIVITY_SET = setOf( - DetectedActivity.IN_VEHICLE.toString(), - DetectedActivity.ON_BICYCLE.toString(), - DetectedActivity.RUNNING.toString(), - DetectedActivity.WALKING.toString() - ) - const val DONATIONS = "donations" - const val INITIAL_TUTORIAL_DONE = "app:tutorial:is_done" - } +object Preferences { + const val CREATE_CHANNEL_KEY = "notifications:channel:create" + const val APP_INIT_KEY = "app:initialized" + const val ADS_ENABLED = "app:ads:enabled" + const val BREAKFAST_TIME = "app:breakfast" + const val LUNCH_TIME = "app:lunch" + const val DINNER_TIME = "app:dinner" + const val ANALYTICS_ENABLED = "firebase:analytics" + const val PERFORMANCE_ENABLED = "firebase:performance" + const val ACTIVITY_TRACKING_ENABLED = "activity:gms:tracking" + const val ACTIVITIES_ENABLED = "activity:gms:activities:enabled" + val DEFAULT_ACTIVITY_SET = setOf( + DetectedActivity.IN_VEHICLE.toString(), + DetectedActivity.ON_BICYCLE.toString(), + DetectedActivity.RUNNING.toString(), + DetectedActivity.WALKING.toString() + ) + const val DONATIONS = "donations" + const val INITIAL_TUTORIAL_DONE = "app:tutorial:is_done" } -class TimeConfig { - companion object { - const val BREAKFAST_ID = 0L - const val LUNCH_ID = 1L - const val DINNER_ID = 2L - } +object TimeConfig { + const val BREAKFAST_ID = 0L + const val LUNCH_ID = 1L + const val DINNER_ID = 2L } -class AppIntro { - companion object Modules { - const val MODULE_NAME = "appintro" - const val PACKAGE_NAME = - "com.javinator9889.handwashingreminder.appintro" - const val MAIN_ACTIVITY_NAME = "IntroActivity" - } +object AppIntro { + const val MODULE_NAME = "appintro" + const val PACKAGE_NAME = "com.javinator9889.handwashingreminder.appintro" + const val MAIN_ACTIVITY_NAME = "IntroActivity" } -class Ads { - companion object Modules { - const val MODULE_NAME = "ads" - const val PACKAGE_NAME = "com.javinator9889.handwashingreminder.ads" - const val CLASS_NAME = "AdLoaderImpl" - const val PROVIDER_NAME = "Provider" - } +object Ads { + const val MODULE_NAME = "ads" + const val PACKAGE_NAME = "com.javinator9889.handwashingreminder.ads" + const val CLASS_NAME = "AdLoaderImpl" + const val PROVIDER_NAME = "Provider" } -class BundledEmoji { - companion object Modules { - const val MODULE_NAME = "bundledemoji" - const val PACKAGE_NAME = "com.javinator9889.handwashingreminder.bundledemoji" - const val CLASS_NAME = "BundledEmojiConfig" - } +object BundledEmoji { + const val MODULE_NAME = "bundledemoji" + const val PACKAGE_NAME = + "com.javinator9889.handwashingreminder.bundledemoji" + const val CLASS_NAME = "BundledEmojiConfig" } -class OkHttp { - companion object Modules { - const val MODULE_NAME = "okhttp" - const val PACKAGE_NAME = "com.javinator9889.handwashingreminder.okhttp" - const val CLASS_NAME = "OkHttpDownloader" - const val PROVIDER_NAME = "Provider" - } +object RemoteConfig { + const val SPECIAL_EVENT = "special_event" + const val ANIMATION_NAME = "animation_name" + const val WORK_IN_PROGRESS = "work_in_progress_message" + const val DISEASES_JSON = "diseases" } -class OkHttpLegacy { - companion object Modules { - const val MODULE_NAME = "okhttplegacy" - const val PACKAGE_NAME = "com.javinator9889.handwashingreminder.okhttplegacy" - const val CLASS_NAME = "OkHttpDownloader" - const val PROVIDER_NAME = "Provider" - } -} - -class RemoteConfig { - companion object Keys { - const val SPECIAL_EVENT = "special_event" - const val ANIMATION_NAME = "animation_name" - const val WORK_IN_PROGRESS = "work_in_progress_message" - const val DISEASES_JSON = "diseases" - } -} - -class Workers { - companion object Keys { - const val WHO = "workers:key_id" - const val BREAKFAST = 0 - const val LUNCH = 1 - const val DINNER = 2 - const val BREAKFAST_UUID = "workers:breakfast" - const val LUNCH_UUID = "workers:lunch" - const val DINNER_UUID = "workers:dinner" - const val HOUR = "worker:hour" - const val MINUTE = "worker:minute" - } -} - -class Videos { - companion object URI { - const val URL = "video:url" - const val HASH = "video:hash" - const val FILENAME = "video:name" - val VideoList = arrayOf( - hashMapOf( - URL to "https://cloud.javinator9889.com/s/j4tAXFpNmcZ7WRG/download", - HASH to "4bab6874bf8b091accaaf01b07cbc16cbba661411b4dbdb4b2a9f4c033ce8889", - FILENAME to "first-step.mp4" - ), - hashMapOf( - URL to "https://cloud.javinator9889.com/s/XRDj6LBwb92mSNc/download", - HASH to "dc42473a8150d1f258d26e22d455500dcef9ee3c13122e183d712f8b5b8ecf0d", - FILENAME to "second-step.mp4" - ), - hashMapOf( - URL to "https://cloud.javinator9889.com/s/bQNtM9LPsfLYqjy/download", - HASH to "d8c83722a72466780ba00a6df3cf5fec30f662d538041c49e5cd1f6228d9a009", - FILENAME to "third-step.mp4" - ), - hashMapOf( - URL to "https://cloud.javinator9889.com/s/3G4npX58tkqXxx9/download", - HASH to "2e1ce76a465be9a0c4de53d607d4a6285c393094e88d63673571ab9355e09825", - FILENAME to "fourth-step.mp4" - ), - hashMapOf( - URL to "https://cloud.javinator9889.com/s/D9CPyBWQ5MbEsbE/download", - HASH to "e2b8893019af37a59b7dc3f9dfdf1788d781498ed8c67fa50997d26cc3beb27b", - FILENAME to "fifth-step.mp4" - ), - hashMapOf( - URL to "https://cloud.javinator9889.com/s/NZ23LBigk5pAXqR/download", - HASH to "b8831875e52841dd9d1bb3bb575fc1759cf69f93af868c3bb9098d7483be30f9", - FILENAME to "sixth-step.mp4" - ), - hashMapOf( - URL to "https://cloud.javinator9889.com/s/zMRz6nWqi6rsNen/download", - HASH to "d414da0b1cdc818bea8028b7ac1f4e9702fcad6546cae83575b3dd908caa0f3d", - FILENAME to "seventh-step.mp4" - ) +object Videos { + const val URL = "video:url" + const val HASH = "video:hash" + const val FILENAME = "video:name" + val VideoList = arrayOf( + hashMapOf( + URL to "https://cloud.javinator9889.com/s/j4tAXFpNmcZ7WRG/download", + HASH to "4bab6874bf8b091accaaf01b07cbc16cbba661411b4dbdb4b2a9f4c033ce8889", + FILENAME to "first-step.mp4" + ), + hashMapOf( + URL to "https://cloud.javinator9889.com/s/XRDj6LBwb92mSNc/download", + HASH to "dc42473a8150d1f258d26e22d455500dcef9ee3c13122e183d712f8b5b8ecf0d", + FILENAME to "second-step.mp4" + ), + hashMapOf( + URL to "https://cloud.javinator9889.com/s/bQNtM9LPsfLYqjy/download", + HASH to "d8c83722a72466780ba00a6df3cf5fec30f662d538041c49e5cd1f6228d9a009", + FILENAME to "third-step.mp4" + ), + hashMapOf( + URL to "https://cloud.javinator9889.com/s/3G4npX58tkqXxx9/download", + HASH to "2e1ce76a465be9a0c4de53d607d4a6285c393094e88d63673571ab9355e09825", + FILENAME to "fourth-step.mp4" + ), + hashMapOf( + URL to "https://cloud.javinator9889.com/s/D9CPyBWQ5MbEsbE/download", + HASH to "e2b8893019af37a59b7dc3f9dfdf1788d781498ed8c67fa50997d26cc3beb27b", + FILENAME to "fifth-step.mp4" + ), + hashMapOf( + URL to "https://cloud.javinator9889.com/s/NZ23LBigk5pAXqR/download", + HASH to "b8831875e52841dd9d1bb3bb575fc1759cf69f93af868c3bb9098d7483be30f9", + FILENAME to "sixth-step.mp4" + ), + hashMapOf( + URL to "https://cloud.javinator9889.com/s/zMRz6nWqi6rsNen/download", + HASH to "d414da0b1cdc818bea8028b7ac1f4e9702fcad6546cae83575b3dd908caa0f3d", + FILENAME to "seventh-step.mp4" ) - } + ) } object Email { @@ -178,11 +132,14 @@ object Firebase { } const val TRANSLATE_URL = "https://s.javinator9889.com/hwtranslate" -const val PLAYSTORE_URL = "https://play.google.com/store/apps/details?id=com.javinator9889.handwashingreminder" +const val PLAYSTORE_URL = + "https://play.google.com/store/apps/details?id=com.javinator9889.handwashingreminder" const val TELEGRAM_URL = "https://t.me/handwash" const val TWITTER_URL = "https://twitter.com/javinator9889" const val LINKEDIN_URL = "https://www.linkedin.com/in/javinator9889" const val GITHUB_URL = "https://github.com/Javinator9889/Handwashing-reminder" +const val API_URL = + "https://us-central1-handwashing.cloudfunctions.net/www-webApi" const val MODULE_COUNT = 3 const val DYNAMIC_FEATURE_INSTALL_RESULT_CODE = 32 From a0eab3bcbde2ef01a8644c81347f36da6dc27540 Mon Sep 17 00:00:00 2001 From: Javinator9889 Date: Tue, 9 Jun 2020 16:07:04 +0200 Subject: [PATCH 40/95] Updated MainActivity.kt logic Moved some initialization processes out of "onCreate" and translated into coroutines --- .../activities/MainActivity.kt | 132 ++++++++++-------- .../views/viewmodels/MainActivityViewModel.kt | 118 ++++++++++++++++ 2 files changed, 191 insertions(+), 59 deletions(-) create mode 100644 app/src/main/java/com/javinator9889/handwashingreminder/activities/views/viewmodels/MainActivityViewModel.kt diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/activities/MainActivity.kt b/app/src/main/java/com/javinator9889/handwashingreminder/activities/MainActivity.kt index 58c7fa8..2ae1fb3 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/activities/MainActivity.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/activities/MainActivity.kt @@ -28,11 +28,13 @@ import androidx.core.util.set import androidx.core.view.forEach import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentTransaction +import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.whenCreated +import androidx.lifecycle.whenStarted import androidx.preference.PreferenceManager import com.google.android.material.bottomnavigation.BottomNavigationView import com.google.firebase.analytics.FirebaseAnalytics import com.google.firebase.ktx.Firebase -import com.google.firebase.perf.metrics.AddTrace import com.google.firebase.remoteconfig.ktx.remoteConfig import com.javinator9889.handwashingreminder.R import com.javinator9889.handwashingreminder.activities.support.ActionBarBase @@ -44,19 +46,22 @@ import com.javinator9889.handwashingreminder.custom.libraries.AppRate import com.javinator9889.handwashingreminder.firebase.Auth import com.javinator9889.handwashingreminder.utils.Preferences import com.javinator9889.handwashingreminder.utils.isDebuggable +import com.javinator9889.handwashingreminder.utils.notNull import com.mikepenz.iconics.IconicsDrawable import com.mikepenz.iconics.typeface.library.googlematerial.GoogleMaterial import com.mikepenz.iconics.typeface.library.ionicons.Ionicons import kotlinx.android.synthetic.main.activity_main.* import kotlinx.android.synthetic.main.how_to_wash_hands_layout.* +import kotlinx.coroutines.* import timber.log.Timber import uk.co.deanwild.materialshowcaseview.MaterialShowcaseSequence import uk.co.deanwild.materialshowcaseview.ShowcaseConfig import java.lang.ref.WeakReference -import kotlin.concurrent.thread import kotlin.properties.Delegates internal const val ARG_CURRENT_ITEM = "bundle:args:current_item" +internal val IDS = + arrayOf(R.id.diseases, R.id.handwashing, R.id.news, R.id.settings) class MainActivity : ActionBarBase(), BottomNavigationView.OnNavigationItemSelectedListener { @@ -64,35 +69,41 @@ class MainActivity : ActionBarBase(), private val fragments: SparseArray> = SparseArray(4) private var activeFragment by Delegates.notNull<@IdRes Int>() - @AddTrace(name = "onCreateMainView") - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - with(FirebaseAnalytics.getInstance(this)) { - setCurrentScreen(this@MainActivity, "Main view", null) - } - with(Firebase.remoteConfig) { - fetchAndActivate() + init { + lifecycleScope.launch { + whenCreated { + val deferreds = mutableSetOf>() + with(Firebase.remoteConfig) { fetchAndActivate() } + deferreds.add(async { delegateMenuIcons(menu) }) + menu.setOnNavigationItemSelectedListener(this@MainActivity) + deferreds.add(async { loadTutorial() }) + deferreds.add(async { suggestRating() }) + for (id in IDS) + deferreds.add(async { createFragmentForId(id) }) + activeFragment = R.id.diseases + deferreds.awaitAll() + } + whenStarted { + with(FirebaseAnalytics.getInstance(this@MainActivity)) { + setCurrentScreen(this@MainActivity, "Main view", null) + } + withContext(Dispatchers.Main) { + initFragmentView() + } + } } - delegateMenuIcons(menu) - val ids = - arrayOf(R.id.diseases, R.id.handwashing, R.id.news, R.id.settings) - menu.setOnNavigationItemSelectedListener(this) - loadTutorial() - suggestRating() - if (savedInstanceState != null) { - for (id in ids) { + } + + override fun onRestoreInstanceState(savedInstanceState: Bundle) { + super.onRestoreInstanceState(savedInstanceState) + savedInstanceState.notNull { + for (id in IDS) { val fragment = supportFragmentManager.getFragment( - savedInstanceState, - id.toString() + savedInstanceState, id.toString() ) ?: createFragmentForId(id) fragments[id] = WeakReference(fragment) } activeFragment = savedInstanceState.getInt(ARG_CURRENT_ITEM) - } else { - for (id in ids) - createFragmentForId(id) - activeFragment = R.id.diseases - initFragmentView() } } @@ -104,25 +115,25 @@ class MainActivity : ActionBarBase(), } } - protected fun delegateMenuIcons(menu: BottomNavigationView) { - thread(start = true) { - menu.menu.forEach { item -> - val icon = when (item.itemId) { - R.id.diseases -> - IconicsDrawable( - this, Ionicons.Icon.ion_ios_medkit - ) - R.id.news -> - IconicsDrawable( - this, GoogleMaterial.Icon.gmd_chrome_reader_mode - ) - R.id.settings -> - IconicsDrawable( - this, Ionicons.Icon.ion_android_settings - ) - else -> null - } - icon?.let { runOnUiThread { item.icon = it } } + private suspend fun delegateMenuIcons(menu: BottomNavigationView) { + menu.menu.forEach { item -> + val icon = when (item.itemId) { + R.id.diseases -> + IconicsDrawable( + this, Ionicons.Icon.ion_ios_medkit + ) + R.id.news -> + IconicsDrawable( + this, GoogleMaterial.Icon.gmd_chrome_reader_mode + ) + R.id.settings -> + IconicsDrawable( + this, Ionicons.Icon.ion_android_settings + ) + else -> null + } + withContext(Dispatchers.Main) { + icon?.let { item.icon = it } } } } @@ -203,9 +214,8 @@ class MainActivity : ActionBarBase(), hide(fragment) } show( - fragments[activeFragment].get() ?: createFragmentForId( - activeFragment - ) + fragments[activeFragment].get() + ?: createFragmentForId(activeFragment) ) commit() } @@ -225,7 +235,7 @@ class MainActivity : ActionBarBase(), }.commit() } - private fun loadTutorial() { + private suspend fun loadTutorial() { val preferences = with(PreferenceManager.getDefaultSharedPreferences(this)) { if (getBoolean(Preferences.INITIAL_TUTORIAL_DONE, false)) @@ -268,21 +278,25 @@ class MainActivity : ActionBarBase(), putBoolean(Preferences.INITIAL_TUTORIAL_DONE, true) } } - start() + withContext(Dispatchers.Main) { + start() + } } } - private fun suggestRating() { - with(AppRate(this)) { - if (!isDebuggable()) { - setMinDaysUntilPrompt(2L) - setMinLaunchesUntilPrompt(5) + private suspend fun suggestRating() { + withContext(Dispatchers.Main) { + with(AppRate(this@MainActivity)) { + if (!isDebuggable()) { + setMinDaysUntilPrompt(2L) + setMinLaunchesUntilPrompt(5) + } + dialogTitle = R.string.rate_text_title + dialogMessage = R.string.rate_app_message + positiveButtonText = R.string.rate_text + negativeButtonText = R.string.rate_do_not_show + init() } - dialogTitle = R.string.rate_text_title - dialogMessage = R.string.rate_app_message - positiveButtonText = R.string.rate_text - negativeButtonText = R.string.rate_do_not_show - init() } } diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/viewmodels/MainActivityViewModel.kt b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/viewmodels/MainActivityViewModel.kt new file mode 100644 index 0000000..4013c11 --- /dev/null +++ b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/viewmodels/MainActivityViewModel.kt @@ -0,0 +1,118 @@ +/* + * Copyright © 2020 - present | Handwashing reminder by Javinator9889 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see https://www.gnu.org/licenses/. + * + * Created by Javinator9889 on 9/06/20 - Handwashing reminder. + */ +package com.javinator9889.handwashingreminder.activities.views.viewmodels + +import android.content.Context +import android.util.SparseArray +import android.view.MenuItem +import androidx.annotation.IdRes +import androidx.core.util.set +import androidx.core.view.forEach +import androidx.fragment.app.Fragment +import androidx.lifecycle.SavedStateHandle +import androidx.lifecycle.ViewModel +import com.google.android.material.bottomnavigation.BottomNavigationView +import com.javinator9889.handwashingreminder.R +import com.javinator9889.handwashingreminder.activities.ARG_CURRENT_ITEM +import com.javinator9889.handwashingreminder.activities.IDS +import com.javinator9889.handwashingreminder.activities.views.fragments.diseases.DiseasesFragment +import com.javinator9889.handwashingreminder.activities.views.fragments.news.NewsFragment +import com.javinator9889.handwashingreminder.activities.views.fragments.settings.SettingsView +import com.javinator9889.handwashingreminder.activities.views.fragments.washinghands.WashingHandsFragment +import com.mikepenz.iconics.IconicsDrawable +import com.mikepenz.iconics.typeface.library.googlematerial.GoogleMaterial +import com.mikepenz.iconics.typeface.library.ionicons.Ionicons +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import java.lang.ref.WeakReference + +internal const val ARG_FRAGMENTS_ARRAY = "args:handle:fragments" + +class MainActivityViewModel(private val handle: SavedStateHandle) : + ViewModel() { + private val fragments: SparseArray> = + handle.get(ARG_FRAGMENTS_ARRAY) + ?: SparseArray(4) + + @IdRes + private var privateActiveFragment: Int = + handle.get(ARG_CURRENT_ITEM) ?: R.id.diseases + val activeFragment: Fragment + get() = loadFragment(privateActiveFragment) + + init { + for (id in IDS) + createFragmentForId(id) + handle.set(ARG_FRAGMENTS_ARRAY, fragments) + } + + suspend fun setMenuIcons( + view: BottomNavigationView, + context: Context + ) { + view.menu.forEach { item: MenuItem -> + when (item.itemId) { + R.id.diseases -> IconicsDrawable( + context, + Ionicons.Icon.ion_ios_medkit + ) + R.id.news -> IconicsDrawable( + context, + GoogleMaterial.Icon.gmd_chrome_reader_mode + ) + R.id.settings -> IconicsDrawable( + context, + Ionicons.Icon.ion_android_settings + ) + else -> null + }?.run { + withContext(Dispatchers.Main) { + item.icon = this@run + } + } + } + } + + fun loadFragment(@IdRes id: Int) = + fragments[id, null]?.get() ?: createFragmentForId(id) + + fun setActiveFragment(@IdRes id: Int) { + if (id !in IDS) + throw IllegalArgumentException("id not in ids") + privateActiveFragment = id + handle.set(ARG_CURRENT_ITEM, privateActiveFragment) + } + + private fun createFragmentForId(@IdRes id: Int): Fragment { + val fragment = when (id) { + R.id.diseases -> DiseasesFragment() + R.id.handwashing -> WashingHandsFragment() + R.id.news -> NewsFragment() + R.id.settings -> SettingsView() + else -> Fragment() // this should never happen + } + fragments[id] = WeakReference(fragment) + return fragment + } + + companion object Factory : ViewModelAssistedFactory { + override fun create(handle: SavedStateHandle) = + MainActivityViewModel(handle) + } +} \ No newline at end of file From 7577219ec5d915d4f5457258f43120e4c3cc57df Mon Sep 17 00:00:00 2001 From: Javinator9889 Date: Tue, 9 Jun 2020 19:02:16 +0200 Subject: [PATCH 41/95] Created stub structure for MainActivity data container --- .../activities/MainActivity.kt | 68 ++++++++++--------- .../views/data/MainActivityDataHandler.kt | 54 +++++++++++++++ .../views/viewmodels/MainActivityViewModel.kt | 17 +++-- 3 files changed, 97 insertions(+), 42 deletions(-) create mode 100644 app/src/main/java/com/javinator9889/handwashingreminder/activities/views/data/MainActivityDataHandler.kt diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/activities/MainActivity.kt b/app/src/main/java/com/javinator9889/handwashingreminder/activities/MainActivity.kt index 2ae1fb3..89db4b8 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/activities/MainActivity.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/activities/MainActivity.kt @@ -18,12 +18,11 @@ */ package com.javinator9889.handwashingreminder.activities -import android.os.Bundle import android.util.SparseArray import android.view.MenuItem +import androidx.activity.viewModels import androidx.annotation.IdRes import androidx.core.content.edit -import androidx.core.util.forEach import androidx.core.util.set import androidx.core.view.forEach import androidx.fragment.app.Fragment @@ -42,11 +41,12 @@ import com.javinator9889.handwashingreminder.activities.views.fragments.diseases import com.javinator9889.handwashingreminder.activities.views.fragments.news.NewsFragment import com.javinator9889.handwashingreminder.activities.views.fragments.settings.SettingsView import com.javinator9889.handwashingreminder.activities.views.fragments.washinghands.WashingHandsFragment +import com.javinator9889.handwashingreminder.activities.views.viewmodels.MainActivityViewModel +import com.javinator9889.handwashingreminder.activities.views.viewmodels.SavedViewModelFactory import com.javinator9889.handwashingreminder.custom.libraries.AppRate import com.javinator9889.handwashingreminder.firebase.Auth import com.javinator9889.handwashingreminder.utils.Preferences import com.javinator9889.handwashingreminder.utils.isDebuggable -import com.javinator9889.handwashingreminder.utils.notNull import com.mikepenz.iconics.IconicsDrawable import com.mikepenz.iconics.typeface.library.googlematerial.GoogleMaterial import com.mikepenz.iconics.typeface.library.ionicons.Ionicons @@ -57,7 +57,6 @@ import timber.log.Timber import uk.co.deanwild.materialshowcaseview.MaterialShowcaseSequence import uk.co.deanwild.materialshowcaseview.ShowcaseConfig import java.lang.ref.WeakReference -import kotlin.properties.Delegates internal const val ARG_CURRENT_ITEM = "bundle:args:current_item" internal val IDS = @@ -67,20 +66,26 @@ class MainActivity : ActionBarBase(), BottomNavigationView.OnNavigationItemSelectedListener { override val layoutId: Int = R.layout.activity_main private val fragments: SparseArray> = SparseArray(4) - private var activeFragment by Delegates.notNull<@IdRes Int>() +// private var activeFragment by Delegates.notNull<@IdRes Int>() + private val activityViewModel by viewModels { + SavedViewModelFactory(MainActivityViewModel.Factory, this) + } init { lifecycleScope.launch { whenCreated { val deferreds = mutableSetOf>() with(Firebase.remoteConfig) { fetchAndActivate() } - deferreds.add(async { delegateMenuIcons(menu) }) + deferreds.add(async { + activityViewModel.setMenuIcons(menu, this@MainActivity) + }) +// deferreds.add(async { delegateMenuIcons(menu) }) menu.setOnNavigationItemSelectedListener(this@MainActivity) deferreds.add(async { loadTutorial() }) deferreds.add(async { suggestRating() }) - for (id in IDS) + /*for (id in IDS) deferreds.add(async { createFragmentForId(id) }) - activeFragment = R.id.diseases + activeFragment = R.id.diseases*/ deferreds.awaitAll() } whenStarted { @@ -94,7 +99,7 @@ class MainActivity : ActionBarBase(), } } - override fun onRestoreInstanceState(savedInstanceState: Bundle) { + /*override fun onRestoreInstanceState(savedInstanceState: Bundle) { super.onRestoreInstanceState(savedInstanceState) savedInstanceState.notNull { for (id in IDS) { @@ -105,7 +110,7 @@ class MainActivity : ActionBarBase(), } activeFragment = savedInstanceState.getInt(ARG_CURRENT_ITEM) } - } + }*/ override fun onDestroy() { try { @@ -139,23 +144,22 @@ class MainActivity : ActionBarBase(), } override fun onBackPressed() { - if (activeFragment != R.id.diseases && - activeFragment != R.id.handwashing + if (activityViewModel.activeFragmentId != R.id.diseases && + activityViewModel.activeFragmentId != R.id.handwashing ) { menu.selectedItemId = R.id.diseases onNavigationItemSelected(menu.menu.findItem(R.id.diseases)) } else { - if (activeFragment == R.id.diseases) { - with(fragments[activeFragment].get()!! as DiseasesFragment) { + if (activityViewModel.activeFragmentId == R.id.diseases) { + with(activityViewModel.activeFragment as DiseasesFragment) { onBackPressed() } fragments.clear() super.onBackPressed() finish() } else { - val washingHandsFragment = fragments[activeFragment].get() - ?: createFragmentForId(R.id.handwashing) - as WashingHandsFragment + val washingHandsFragment = + activityViewModel.activeFragment as WashingHandsFragment if (washingHandsFragment.pager.currentItem != 0) washingHandsFragment.pager.currentItem-- else { @@ -180,7 +184,7 @@ class MainActivity : ActionBarBase(), return onItemSelected(item.itemId) } - override fun onSaveInstanceState(outState: Bundle) { + /*override fun onSaveInstanceState(outState: Bundle) { super.onSaveInstanceState(outState) fragments.forEach { id, reference -> reference.get()?.let { @@ -190,7 +194,7 @@ class MainActivity : ActionBarBase(), } } outState.putInt(ARG_CURRENT_ITEM, activeFragment) - } + }*/ protected fun onItemSelected(@IdRes id: Int): Boolean { return try { @@ -208,28 +212,26 @@ class MainActivity : ActionBarBase(), private fun initFragmentView() { with(supportFragmentManager.beginTransaction()) { - fragments.forEach { id, reference -> - val fragment = reference.get() ?: createFragmentForId(id) - add(R.id.mainContent, fragment) - hide(fragment) + for (id in IDS) { + activityViewModel.loadFragment(id).also { + if (supportFragmentManager.findFragmentByTag(id.toString()) == null) { + add(R.id.mainContent, it, id.toString()) + hide(it) + } + } } - show( - fragments[activeFragment].get() - ?: createFragmentForId(activeFragment) - ) + show(activityViewModel.activeFragment) commit() } } private fun loadFragment(@IdRes id: Int) { - if (id == activeFragment) + if (id == activityViewModel.activeFragmentId) return - val fragment = fragments[id].get() ?: return - val displayedFragment = fragments[activeFragment].get()!! with(supportFragmentManager.beginTransaction()) { - show(fragment) - hide(displayedFragment) - activeFragment = id + show(activityViewModel.loadFragment(id)) + hide(activityViewModel.activeFragment) + activityViewModel.activeFragmentId = id setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE) disallowAddToBackStack() }.commit() diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/data/MainActivityDataHandler.kt b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/data/MainActivityDataHandler.kt new file mode 100644 index 0000000..687e388 --- /dev/null +++ b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/data/MainActivityDataHandler.kt @@ -0,0 +1,54 @@ +/* + * Copyright © 2020 - present | Handwashing reminder by Javinator9889 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see https://www.gnu.org/licenses/. + * + * Created by Javinator9889 on 9/06/20 - Handwashing reminder. + */ +package com.javinator9889.handwashingreminder.activities.views.data + +import android.util.SparseArray +import androidx.annotation.IdRes +import androidx.core.util.set +import androidx.fragment.app.Fragment +import com.javinator9889.handwashingreminder.R +import com.javinator9889.handwashingreminder.activities.IDS +import com.javinator9889.handwashingreminder.activities.views.fragments.diseases.DiseasesFragment +import com.javinator9889.handwashingreminder.activities.views.fragments.news.NewsFragment +import com.javinator9889.handwashingreminder.activities.views.fragments.settings.SettingsView +import com.javinator9889.handwashingreminder.activities.views.fragments.washinghands.WashingHandsFragment +import java.lang.ref.WeakReference + +class MainActivityDataHandler(@IdRes var activeFragmentId: Int = R.id.diseases) { + private val fragments = SparseArray>(4) + val activeFragment: Fragment + get() = this[activeFragmentId] + + operator fun get(@IdRes id: Int): Fragment = + fragments[id, null]?.get() ?: createFragmentForId(id) + + private fun createFragmentForId(@IdRes id: Int): Fragment { + if (id !in IDS) + throw IllegalArgumentException("id not in IDs") + val fragment = when (id) { + R.id.diseases -> DiseasesFragment() + R.id.handwashing -> WashingHandsFragment() + R.id.news -> NewsFragment() + R.id.settings -> SettingsView() + else -> Fragment() // this should never happen + } + fragments[id] = WeakReference(fragment) + return fragment + } +} \ No newline at end of file diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/viewmodels/MainActivityViewModel.kt b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/viewmodels/MainActivityViewModel.kt index 4013c11..3c7fd66 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/viewmodels/MainActivityViewModel.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/viewmodels/MainActivityViewModel.kt @@ -51,10 +51,16 @@ class MainActivityViewModel(private val handle: SavedStateHandle) : ?: SparseArray(4) @IdRes - private var privateActiveFragment: Int = + var activeFragmentId: Int = handle.get(ARG_CURRENT_ITEM) ?: R.id.diseases + set(@IdRes id) { + if (id !in IDS) + throw IllegalArgumentException("id not in ids") + field = id + handle.set(ARG_CURRENT_ITEM, activeFragmentId) + } val activeFragment: Fragment - get() = loadFragment(privateActiveFragment) + get() = loadFragment(activeFragmentId) init { for (id in IDS) @@ -92,13 +98,6 @@ class MainActivityViewModel(private val handle: SavedStateHandle) : fun loadFragment(@IdRes id: Int) = fragments[id, null]?.get() ?: createFragmentForId(id) - fun setActiveFragment(@IdRes id: Int) { - if (id !in IDS) - throw IllegalArgumentException("id not in ids") - privateActiveFragment = id - handle.set(ARG_CURRENT_ITEM, privateActiveFragment) - } - private fun createFragmentForId(@IdRes id: Int): Fragment { val fragment = when (id) { R.id.diseases -> DiseasesFragment() From 1c02be3872d34cc320d96188c92061ce03524f13 Mon Sep 17 00:00:00 2001 From: Javinator9889 Date: Wed, 10 Jun 2020 11:58:51 +0200 Subject: [PATCH 42/95] Updated MainActivity data handling - in addition, the NewsViewModel was updated and NewsFragment --- .../activities/MainActivity.kt | 107 +++++++++------- .../views/data/MainActivityDataHandler.kt | 87 ++++++++++++- .../views/fragments/news/NewsFragment.kt | 6 +- .../views/viewmodels/MainActivityViewModel.kt | 117 ------------------ .../views/viewmodels/NewsViewModel.kt | 5 +- .../collections/NewsInformation.kt | 76 +++++++++++- 6 files changed, 230 insertions(+), 168 deletions(-) delete mode 100644 app/src/main/java/com/javinator9889/handwashingreminder/activities/views/viewmodels/MainActivityViewModel.kt diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/activities/MainActivity.kt b/app/src/main/java/com/javinator9889/handwashingreminder/activities/MainActivity.kt index 89db4b8..63afeaf 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/activities/MainActivity.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/activities/MainActivity.kt @@ -18,9 +18,9 @@ */ package com.javinator9889.handwashingreminder.activities +import android.os.Bundle import android.util.SparseArray import android.view.MenuItem -import androidx.activity.viewModels import androidx.annotation.IdRes import androidx.core.content.edit import androidx.core.util.set @@ -37,16 +37,16 @@ import com.google.firebase.ktx.Firebase import com.google.firebase.remoteconfig.ktx.remoteConfig import com.javinator9889.handwashingreminder.R import com.javinator9889.handwashingreminder.activities.support.ActionBarBase +import com.javinator9889.handwashingreminder.activities.views.data.MainActivityDataHandler import com.javinator9889.handwashingreminder.activities.views.fragments.diseases.DiseasesFragment import com.javinator9889.handwashingreminder.activities.views.fragments.news.NewsFragment import com.javinator9889.handwashingreminder.activities.views.fragments.settings.SettingsView import com.javinator9889.handwashingreminder.activities.views.fragments.washinghands.WashingHandsFragment -import com.javinator9889.handwashingreminder.activities.views.viewmodels.MainActivityViewModel -import com.javinator9889.handwashingreminder.activities.views.viewmodels.SavedViewModelFactory import com.javinator9889.handwashingreminder.custom.libraries.AppRate import com.javinator9889.handwashingreminder.firebase.Auth import com.javinator9889.handwashingreminder.utils.Preferences import com.javinator9889.handwashingreminder.utils.isDebuggable +import com.javinator9889.handwashingreminder.utils.notNull import com.mikepenz.iconics.IconicsDrawable import com.mikepenz.iconics.typeface.library.googlematerial.GoogleMaterial import com.mikepenz.iconics.typeface.library.ionicons.Ionicons @@ -58,18 +58,16 @@ import uk.co.deanwild.materialshowcaseview.MaterialShowcaseSequence import uk.co.deanwild.materialshowcaseview.ShowcaseConfig import java.lang.ref.WeakReference -internal const val ARG_CURRENT_ITEM = "bundle:args:current_item" -internal val IDS = - arrayOf(R.id.diseases, R.id.handwashing, R.id.news, R.id.settings) - class MainActivity : ActionBarBase(), BottomNavigationView.OnNavigationItemSelectedListener { override val layoutId: Int = R.layout.activity_main private val fragments: SparseArray> = SparseArray(4) -// private var activeFragment by Delegates.notNull<@IdRes Int>() - private val activityViewModel by viewModels { + + // private var activeFragment by Delegates.notNull<@IdRes Int>() + /*private val activityViewModel by viewModels { SavedViewModelFactory(MainActivityViewModel.Factory, this) - } + }*/ + private val dataHandler = MainActivityDataHandler() init { lifecycleScope.launch { @@ -77,28 +75,38 @@ class MainActivity : ActionBarBase(), val deferreds = mutableSetOf>() with(Firebase.remoteConfig) { fetchAndActivate() } deferreds.add(async { - activityViewModel.setMenuIcons(menu, this@MainActivity) + dataHandler.setMenuIcons(menu, this@MainActivity) }) -// deferreds.add(async { delegateMenuIcons(menu) }) menu.setOnNavigationItemSelectedListener(this@MainActivity) deferreds.add(async { loadTutorial() }) deferreds.add(async { suggestRating() }) - /*for (id in IDS) - deferreds.add(async { createFragmentForId(id) }) - activeFragment = R.id.diseases*/ deferreds.awaitAll() } whenStarted { with(FirebaseAnalytics.getInstance(this@MainActivity)) { setCurrentScreen(this@MainActivity, "Main view", null) } - withContext(Dispatchers.Main) { + /*withContext(Dispatchers.Main) { initFragmentView() - } + }*/ } } } + override fun onPostCreate(savedInstanceState: Bundle?) { + super.onPostCreate(savedInstanceState) + savedInstanceState.notNull { + Timber.d("Activity recreated") + } + if (savedInstanceState == null) + dataHandler.loadFragmentView(supportFragmentManager) + } + + override fun onDestroy() { + dataHandler.clear() + super.onDestroy() + } + /*override fun onRestoreInstanceState(savedInstanceState: Bundle) { super.onRestoreInstanceState(savedInstanceState) savedInstanceState.notNull { @@ -112,11 +120,24 @@ class MainActivity : ActionBarBase(), } }*/ - override fun onDestroy() { + override fun onRestoreInstanceState(savedInstanceState: Bundle) { + super.onRestoreInstanceState(savedInstanceState) + dataHandler.onRestoreInstanceState( + savedInstanceState, + supportFragmentManager + ) + } + + override fun onSaveInstanceState(outState: Bundle) { + super.onSaveInstanceState(outState) + dataHandler.onSaveInstanceState(outState, supportFragmentManager) + } + + override fun finish() { try { Auth.logout() } finally { - super.onDestroy() + super.finish() } } @@ -144,29 +165,26 @@ class MainActivity : ActionBarBase(), } override fun onBackPressed() { - if (activityViewModel.activeFragmentId != R.id.diseases && - activityViewModel.activeFragmentId != R.id.handwashing - ) { - menu.selectedItemId = R.id.diseases - onNavigationItemSelected(menu.menu.findItem(R.id.diseases)) - } else { - if (activityViewModel.activeFragmentId == R.id.diseases) { - with(activityViewModel.activeFragment as DiseasesFragment) { - onBackPressed() - } - fragments.clear() + when (dataHandler.activeFragmentId) { + R.id.diseases -> { + (dataHandler.activeFragment as DiseasesFragment).onBackPressed() super.onBackPressed() finish() - } else { + } + R.id.handwashing -> { val washingHandsFragment = - activityViewModel.activeFragment as WashingHandsFragment + dataHandler.activeFragment as WashingHandsFragment if (washingHandsFragment.pager.currentItem != 0) washingHandsFragment.pager.currentItem-- else { menu.selectedItemId = R.id.diseases - onNavigationItemSelected(menu.menu.findItem(R.id.diseases)) + onItemSelected(R.id.diseases) } } + else -> { + menu.selectedItemId = R.id.diseases + onItemSelected(R.id.diseases) + } } } @@ -210,28 +228,31 @@ class MainActivity : ActionBarBase(), } } - private fun initFragmentView() { + /*private fun initFragmentView() { with(supportFragmentManager.beginTransaction()) { for (id in IDS) { activityViewModel.loadFragment(id).also { - if (supportFragmentManager.findFragmentByTag(id.toString()) == null) { - add(R.id.mainContent, it, id.toString()) - hide(it) - } +// if (supportFragmentManager.findFragmentByTag(id.toString()) == null) { + add(R.id.mainContent, it) + hide(it) +// } } } show(activityViewModel.activeFragment) commit() } - } + }*/ private fun loadFragment(@IdRes id: Int) { - if (id == activityViewModel.activeFragmentId) + Timber.d("$id - ${dataHandler.activeFragmentId} | ${id == dataHandler.activeFragmentId}") + if (id == dataHandler.activeFragmentId) return with(supportFragmentManager.beginTransaction()) { - show(activityViewModel.loadFragment(id)) - hide(activityViewModel.activeFragment) - activityViewModel.activeFragmentId = id + show(dataHandler[id]) + Timber.d("Showing fragment: ${dataHandler[id]}") + hide(dataHandler.activeFragment) + Timber.d("Hiding fragment: ${dataHandler.activeFragment}") + dataHandler.activeFragmentId = id setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE) disallowAddToBackStack() }.commit() diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/data/MainActivityDataHandler.kt b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/data/MainActivityDataHandler.kt index 687e388..f32618c 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/data/MainActivityDataHandler.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/data/MainActivityDataHandler.kt @@ -18,18 +18,35 @@ */ package com.javinator9889.handwashingreminder.activities.views.data +import android.content.Context +import android.os.Bundle import android.util.SparseArray +import android.view.MenuItem import androidx.annotation.IdRes +import androidx.core.util.forEach import androidx.core.util.set +import androidx.core.view.forEach import androidx.fragment.app.Fragment +import androidx.fragment.app.FragmentManager +import com.google.android.material.bottomnavigation.BottomNavigationView import com.javinator9889.handwashingreminder.R -import com.javinator9889.handwashingreminder.activities.IDS import com.javinator9889.handwashingreminder.activities.views.fragments.diseases.DiseasesFragment import com.javinator9889.handwashingreminder.activities.views.fragments.news.NewsFragment import com.javinator9889.handwashingreminder.activities.views.fragments.settings.SettingsView import com.javinator9889.handwashingreminder.activities.views.fragments.washinghands.WashingHandsFragment +import com.mikepenz.iconics.IconicsDrawable +import com.mikepenz.iconics.typeface.library.googlematerial.GoogleMaterial +import com.mikepenz.iconics.typeface.library.ionicons.Ionicons +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import timber.log.Timber import java.lang.ref.WeakReference +internal const val ARG_CURRENT_ITEM = "bundle:args:current_item" +internal val IDS = + arrayOf(R.id.diseases, R.id.handwashing, R.id.news, R.id.settings) + + class MainActivityDataHandler(@IdRes var activeFragmentId: Int = R.id.diseases) { private val fragments = SparseArray>(4) val activeFragment: Fragment @@ -38,9 +55,77 @@ class MainActivityDataHandler(@IdRes var activeFragmentId: Int = R.id.diseases) operator fun get(@IdRes id: Int): Fragment = fragments[id, null]?.get() ?: createFragmentForId(id) + fun onSaveInstanceState( + outState: Bundle, + fragmentManager: FragmentManager + ) { + fragments.forEach { id, reference -> + reference.get()?.let { + fragmentManager.putFragment(outState, id.toString(), it) + } + } + outState.putInt(ARG_CURRENT_ITEM, activeFragmentId) + } + + fun onRestoreInstanceState( + savedInstanceState: Bundle, + fragmentManager: FragmentManager + ) { + for (id in IDS) { + val restoredFragment = + fragmentManager.getFragment(savedInstanceState, id.toString()) + ?: createFragmentForId(id) + fragments[id] = WeakReference(restoredFragment) + } + activeFragmentId = savedInstanceState.getInt(ARG_CURRENT_ITEM) + } + + suspend fun setMenuIcons( + view: BottomNavigationView, + context: Context + ) { + view.menu.forEach { item: MenuItem -> + when (item.itemId) { + R.id.diseases -> IconicsDrawable( + context, + Ionicons.Icon.ion_ios_medkit + ) + R.id.news -> IconicsDrawable( + context, + GoogleMaterial.Icon.gmd_chrome_reader_mode + ) + R.id.settings -> IconicsDrawable( + context, + Ionicons.Icon.ion_android_settings + ) + else -> null + }?.run { + withContext(Dispatchers.Main) { + item.icon = this@run + } + } + } + } + + fun loadFragmentView(fragmentManager: FragmentManager) { + with(fragmentManager.beginTransaction()) { + IDS.forEach { id -> + get(id).also { + add(R.id.mainContent, it) + hide(it) + } + } + show(activeFragment) + commit() + } + } + + fun clear() = fragments.clear() + private fun createFragmentForId(@IdRes id: Int): Fragment { if (id !in IDS) throw IllegalArgumentException("id not in IDs") + Timber.d("Creating fragment for ID $id") val fragment = when (id) { R.id.diseases -> DiseasesFragment() R.id.handwashing -> WashingHandsFragment() diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/news/NewsFragment.kt b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/news/NewsFragment.kt index e80bd23..cbf89e8 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/news/NewsFragment.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/news/NewsFragment.kt @@ -58,7 +58,6 @@ class NewsFragment : BaseFragmentView() { lifecycleScope.launch { whenStarted { loading.visibility = View.VISIBLE - launch { newsViewModel.populateData(language = UserProperties.language) } newsViewModel.newsData.observe(viewLifecycleOwner, Observer { if (::footerAdapter.isInitialized) footerAdapter.clear() @@ -115,6 +114,11 @@ class NewsFragment : BaseFragmentView() { } fastAdapter.addEventHooks(listOf(NewsClickHook(), ShareClickHook())) fastAdapter.withSavedInstanceState(savedInstanceState) + if (savedInstanceState == null) { + lifecycleScope.launch { + newsViewModel.populateData(language = UserProperties.language) + } + } } override fun onSaveInstanceState(outState: Bundle) { diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/viewmodels/MainActivityViewModel.kt b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/viewmodels/MainActivityViewModel.kt deleted file mode 100644 index 3c7fd66..0000000 --- a/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/viewmodels/MainActivityViewModel.kt +++ /dev/null @@ -1,117 +0,0 @@ -/* - * Copyright © 2020 - present | Handwashing reminder by Javinator9889 - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see https://www.gnu.org/licenses/. - * - * Created by Javinator9889 on 9/06/20 - Handwashing reminder. - */ -package com.javinator9889.handwashingreminder.activities.views.viewmodels - -import android.content.Context -import android.util.SparseArray -import android.view.MenuItem -import androidx.annotation.IdRes -import androidx.core.util.set -import androidx.core.view.forEach -import androidx.fragment.app.Fragment -import androidx.lifecycle.SavedStateHandle -import androidx.lifecycle.ViewModel -import com.google.android.material.bottomnavigation.BottomNavigationView -import com.javinator9889.handwashingreminder.R -import com.javinator9889.handwashingreminder.activities.ARG_CURRENT_ITEM -import com.javinator9889.handwashingreminder.activities.IDS -import com.javinator9889.handwashingreminder.activities.views.fragments.diseases.DiseasesFragment -import com.javinator9889.handwashingreminder.activities.views.fragments.news.NewsFragment -import com.javinator9889.handwashingreminder.activities.views.fragments.settings.SettingsView -import com.javinator9889.handwashingreminder.activities.views.fragments.washinghands.WashingHandsFragment -import com.mikepenz.iconics.IconicsDrawable -import com.mikepenz.iconics.typeface.library.googlematerial.GoogleMaterial -import com.mikepenz.iconics.typeface.library.ionicons.Ionicons -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.withContext -import java.lang.ref.WeakReference - -internal const val ARG_FRAGMENTS_ARRAY = "args:handle:fragments" - -class MainActivityViewModel(private val handle: SavedStateHandle) : - ViewModel() { - private val fragments: SparseArray> = - handle.get(ARG_FRAGMENTS_ARRAY) - ?: SparseArray(4) - - @IdRes - var activeFragmentId: Int = - handle.get(ARG_CURRENT_ITEM) ?: R.id.diseases - set(@IdRes id) { - if (id !in IDS) - throw IllegalArgumentException("id not in ids") - field = id - handle.set(ARG_CURRENT_ITEM, activeFragmentId) - } - val activeFragment: Fragment - get() = loadFragment(activeFragmentId) - - init { - for (id in IDS) - createFragmentForId(id) - handle.set(ARG_FRAGMENTS_ARRAY, fragments) - } - - suspend fun setMenuIcons( - view: BottomNavigationView, - context: Context - ) { - view.menu.forEach { item: MenuItem -> - when (item.itemId) { - R.id.diseases -> IconicsDrawable( - context, - Ionicons.Icon.ion_ios_medkit - ) - R.id.news -> IconicsDrawable( - context, - GoogleMaterial.Icon.gmd_chrome_reader_mode - ) - R.id.settings -> IconicsDrawable( - context, - Ionicons.Icon.ion_android_settings - ) - else -> null - }?.run { - withContext(Dispatchers.Main) { - item.icon = this@run - } - } - } - } - - fun loadFragment(@IdRes id: Int) = - fragments[id, null]?.get() ?: createFragmentForId(id) - - private fun createFragmentForId(@IdRes id: Int): Fragment { - val fragment = when (id) { - R.id.diseases -> DiseasesFragment() - R.id.handwashing -> WashingHandsFragment() - R.id.news -> NewsFragment() - R.id.settings -> SettingsView() - else -> Fragment() // this should never happen - } - fragments[id] = WeakReference(fragment) - return fragment - } - - companion object Factory : ViewModelAssistedFactory { - override fun create(handle: SavedStateHandle) = - MainActivityViewModel(handle) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/viewmodels/NewsViewModel.kt b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/viewmodels/NewsViewModel.kt index 6858706..9da4410 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/viewmodels/NewsViewModel.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/viewmodels/NewsViewModel.kt @@ -33,8 +33,11 @@ import okhttp3.Headers import timber.log.Timber import java.io.Reader +internal const val ARG_NEWS_DATA = "args:newsmodel:data" + class NewsViewModel : ViewModel() { val newsData: MutableLiveData = MutableLiveData() +// val activeItems = mutableSetOf() suspend fun populateData( from: Int = 0, @@ -59,11 +62,9 @@ class NewsViewModel : ViewModel() { } withContext(Dispatchers.Default) { JsonReader(requestReader!!).use { reader -> - var position = 0 reader.beginArray { while (reader.hasNext()) { newsData.postValue(klaxon.parse(reader)) - position++ } } } diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/collections/NewsInformation.kt b/app/src/main/java/com/javinator9889/handwashingreminder/collections/NewsInformation.kt index 4812578..aaac9a0 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/collections/NewsInformation.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/collections/NewsInformation.kt @@ -18,6 +18,8 @@ */ package com.javinator9889.handwashingreminder.collections +import android.os.Parcel +import android.os.Parcelable import com.beust.klaxon.* import timber.log.Timber import java.text.SimpleDateFormat @@ -27,22 +29,88 @@ import kotlin.reflect.KProperty data class NewsData( val id: String, @KlaxonDate - val discoverDate: Date?, + val discoverDate: Date, val title: String, val text: String, val url: String, @KlaxonElements val elements: Elements? = null, val website: Website? = null -) +) : Parcelable { + constructor(parcel: Parcel) : this( + parcel.readString()!!, + Date(parcel.readLong()), + parcel.readString()!!, + parcel.readString()!!, + parcel.readString()!!, + parcel.readParcelable(Elements::class.java.classLoader), + parcel.readParcelable(Website::class.java.classLoader) + ) -data class Elements(val url: String?) + override fun writeToParcel(parcel: Parcel, flags: Int) { + parcel.writeString(id) + parcel.writeLong(discoverDate.time) + parcel.writeString(title) + parcel.writeString(text) + parcel.writeString(url) + parcel.writeParcelable(elements, flags) + parcel.writeParcelable(website, flags) + } + + override fun describeContents() = 0 + + companion object CREATOR : Parcelable.Creator { + override fun createFromParcel(parcel: Parcel): NewsData { + return NewsData(parcel) + } + + override fun newArray(size: Int): Array { + return arrayOfNulls(size) + } + } +} + +data class Elements(val url: String?) : Parcelable { + constructor(parcel: Parcel) : this(parcel.readString()) + + override fun writeToParcel(parcel: Parcel, flags: Int) { + parcel.writeString(url) + } + + override fun describeContents() = 0 + + companion object CREATOR : Parcelable.Creator { + override fun createFromParcel(parcel: Parcel) = Elements(parcel) + + override fun newArray(size: Int) = arrayOfNulls(size) + } +} data class Website( val name: String, val hostName: String, val iconURL: String? -) +) : Parcelable { + constructor(parcel: Parcel) : this( + parcel.readString()!!, + parcel.readString()!!, + parcel.readString()!! + ) + + override fun writeToParcel(parcel: Parcel, flags: Int) { + parcel.writeString(name) + parcel.writeString(hostName) + parcel.writeString(iconURL) + } + + override fun describeContents() = 0 + + companion object CREATOR : Parcelable.Creator { + override fun createFromParcel(parcel: Parcel) = Website(parcel) + + override fun newArray(size: Int) = arrayOfNulls(size) + } +} @Target(AnnotationTarget.FIELD) annotation class KlaxonDate From fcca36de8ff1cd14fcbcac2b924d05f99b7ea301 Mon Sep 17 00:00:00 2001 From: Javinator9889 Date: Wed, 10 Jun 2020 13:31:29 +0200 Subject: [PATCH 43/95] Included open and share options --- .../views/fragments/news/NewsFragment.kt | 37 ++++++++++++++++++- 1 file changed, 35 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/news/NewsFragment.kt b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/news/NewsFragment.kt index cbf89e8..2d72f92 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/news/NewsFragment.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/news/NewsFragment.kt @@ -18,6 +18,8 @@ */ package com.javinator9889.handwashingreminder.activities.views.fragments.news +import android.content.Intent +import android.net.Uri import android.os.Bundle import android.view.View import androidx.annotation.LayoutRes @@ -28,6 +30,7 @@ import androidx.lifecycle.whenStarted import androidx.recyclerview.widget.DefaultItemAnimator import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView +import com.afollestad.materialdialogs.MaterialDialog import com.javinator9889.handwashingreminder.R import com.javinator9889.handwashingreminder.activities.base.BaseFragmentView import com.javinator9889.handwashingreminder.activities.views.fragments.news.adapter.News @@ -137,7 +140,25 @@ class NewsFragment : BaseFragmentView() { fastAdapter: FastAdapter, item: News ) { - TODO("Not yet implemented - open web browser") + val website = Uri.parse(item.url) + with(Intent(Intent.ACTION_VIEW, website)) { + if (resolveActivity(requireContext().packageManager) != null) + startActivity(this) + else { + MaterialDialog(requireContext()).show { + title(R.string.no_app) + message( + text = getString( + R.string.no_app_long, + getString(R.string.browser_err) + ) + ) + positiveButton(android.R.string.ok) + cancelable(true) + cancelOnTouchOutside(true) + } + } + } } } @@ -152,7 +173,19 @@ class NewsFragment : BaseFragmentView() { fastAdapter: FastAdapter, item: News ) { - TODO("Not yet implemented - share intent") + with(Intent.createChooser(Intent().apply { + action = Intent.ACTION_SEND + putExtra( + Intent.EXTRA_TEXT, + "${item.title} — ${item.url} via Handwashing Reminder" + ) + putExtra(Intent.EXTRA_TITLE, item.title) + item.imageUrl?.let { data = Uri.parse(it) } + flags = Intent.FLAG_GRANT_READ_URI_PERMISSION + type = "text/plain" + }, null)) { + startActivity(this) + } } } } \ No newline at end of file From 6cc21ad376366789bb533da201097ca138f6acf4 Mon Sep 17 00:00:00 2001 From: Javinator9889 Date: Thu, 11 Jun 2020 15:08:32 +0200 Subject: [PATCH 44/95] Refactored models for loading preferences async and having their own data handler --- .../activities/MainActivity.kt | 101 +---- .../base/BaseVisibleFragmentView.kt | 23 ++ .../activities/base/LayoutVisibilityChange.kt | 23 ++ .../diseases/DiseaseDescriptionFragment.kt | 2 +- .../fragments/diseases/DiseaseExpandedView.kt | 2 +- .../DiseaseExtraInformationFragment.kt | 2 +- .../fragments/diseases/DiseasesFragment.kt | 17 +- .../fragments/diseases/adapter/Disease.kt | 2 +- .../views/fragments/news/NewsFragment.kt | 7 +- .../views/fragments/settings/SettingsView.kt | 374 +----------------- .../washinghands/WashingHandsFragment.kt | 7 +- .../viewmodels/DiseaseInformationViewModel.kt | 163 +++----- .../views/viewmodels/NewsViewModel.kt | 2 - .../collections/DiseaseTextAdapter.kt | 2 +- .../data/MainActivityDataHandler.kt | 14 +- .../data/ParsedHTMLText.kt | 61 +++ .../data/SettingsLoader.kt | 240 +++++++++++ .../data/SettingsLoaderBak.kt | 290 ++++++++++++++ .../handwashingreminder/utils/Collections.kt | 2 +- 19 files changed, 761 insertions(+), 573 deletions(-) create mode 100644 app/src/main/java/com/javinator9889/handwashingreminder/activities/base/BaseVisibleFragmentView.kt create mode 100644 app/src/main/java/com/javinator9889/handwashingreminder/activities/base/LayoutVisibilityChange.kt rename app/src/main/java/com/javinator9889/handwashingreminder/{activities/views => }/data/MainActivityDataHandler.kt (90%) create mode 100644 app/src/main/java/com/javinator9889/handwashingreminder/data/ParsedHTMLText.kt create mode 100644 app/src/main/java/com/javinator9889/handwashingreminder/data/SettingsLoader.kt create mode 100644 app/src/main/java/com/javinator9889/handwashingreminder/data/SettingsLoaderBak.kt diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/activities/MainActivity.kt b/app/src/main/java/com/javinator9889/handwashingreminder/activities/MainActivity.kt index 63afeaf..b5a1b8f 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/activities/MainActivity.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/activities/MainActivity.kt @@ -19,13 +19,9 @@ package com.javinator9889.handwashingreminder.activities import android.os.Bundle -import android.util.SparseArray import android.view.MenuItem import androidx.annotation.IdRes import androidx.core.content.edit -import androidx.core.util.set -import androidx.core.view.forEach -import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentTransaction import androidx.lifecycle.lifecycleScope import androidx.lifecycle.whenCreated @@ -37,36 +33,25 @@ import com.google.firebase.ktx.Firebase import com.google.firebase.remoteconfig.ktx.remoteConfig import com.javinator9889.handwashingreminder.R import com.javinator9889.handwashingreminder.activities.support.ActionBarBase -import com.javinator9889.handwashingreminder.activities.views.data.MainActivityDataHandler import com.javinator9889.handwashingreminder.activities.views.fragments.diseases.DiseasesFragment -import com.javinator9889.handwashingreminder.activities.views.fragments.news.NewsFragment -import com.javinator9889.handwashingreminder.activities.views.fragments.settings.SettingsView import com.javinator9889.handwashingreminder.activities.views.fragments.washinghands.WashingHandsFragment import com.javinator9889.handwashingreminder.custom.libraries.AppRate +import com.javinator9889.handwashingreminder.data.MainActivityDataHandler import com.javinator9889.handwashingreminder.firebase.Auth import com.javinator9889.handwashingreminder.utils.Preferences import com.javinator9889.handwashingreminder.utils.isDebuggable import com.javinator9889.handwashingreminder.utils.notNull -import com.mikepenz.iconics.IconicsDrawable -import com.mikepenz.iconics.typeface.library.googlematerial.GoogleMaterial -import com.mikepenz.iconics.typeface.library.ionicons.Ionicons import kotlinx.android.synthetic.main.activity_main.* import kotlinx.android.synthetic.main.how_to_wash_hands_layout.* import kotlinx.coroutines.* import timber.log.Timber import uk.co.deanwild.materialshowcaseview.MaterialShowcaseSequence import uk.co.deanwild.materialshowcaseview.ShowcaseConfig -import java.lang.ref.WeakReference + class MainActivity : ActionBarBase(), BottomNavigationView.OnNavigationItemSelectedListener { override val layoutId: Int = R.layout.activity_main - private val fragments: SparseArray> = SparseArray(4) - - // private var activeFragment by Delegates.notNull<@IdRes Int>() - /*private val activityViewModel by viewModels { - SavedViewModelFactory(MainActivityViewModel.Factory, this) - }*/ private val dataHandler = MainActivityDataHandler() init { @@ -86,9 +71,6 @@ class MainActivity : ActionBarBase(), with(FirebaseAnalytics.getInstance(this@MainActivity)) { setCurrentScreen(this@MainActivity, "Main view", null) } - /*withContext(Dispatchers.Main) { - initFragmentView() - }*/ } } } @@ -107,19 +89,6 @@ class MainActivity : ActionBarBase(), super.onDestroy() } - /*override fun onRestoreInstanceState(savedInstanceState: Bundle) { - super.onRestoreInstanceState(savedInstanceState) - savedInstanceState.notNull { - for (id in IDS) { - val fragment = supportFragmentManager.getFragment( - savedInstanceState, id.toString() - ) ?: createFragmentForId(id) - fragments[id] = WeakReference(fragment) - } - activeFragment = savedInstanceState.getInt(ARG_CURRENT_ITEM) - } - }*/ - override fun onRestoreInstanceState(savedInstanceState: Bundle) { super.onRestoreInstanceState(savedInstanceState) dataHandler.onRestoreInstanceState( @@ -141,29 +110,6 @@ class MainActivity : ActionBarBase(), } } - private suspend fun delegateMenuIcons(menu: BottomNavigationView) { - menu.menu.forEach { item -> - val icon = when (item.itemId) { - R.id.diseases -> - IconicsDrawable( - this, Ionicons.Icon.ion_ios_medkit - ) - R.id.news -> - IconicsDrawable( - this, GoogleMaterial.Icon.gmd_chrome_reader_mode - ) - R.id.settings -> - IconicsDrawable( - this, Ionicons.Icon.ion_android_settings - ) - else -> null - } - withContext(Dispatchers.Main) { - icon?.let { item.icon = it } - } - } - } - override fun onBackPressed() { when (dataHandler.activeFragmentId) { R.id.diseases -> { @@ -202,19 +148,7 @@ class MainActivity : ActionBarBase(), return onItemSelected(item.itemId) } - /*override fun onSaveInstanceState(outState: Bundle) { - super.onSaveInstanceState(outState) - fragments.forEach { id, reference -> - reference.get()?.let { - supportFragmentManager.putFragment( - outState, id.toString(), it - ) - } - } - outState.putInt(ARG_CURRENT_ITEM, activeFragment) - }*/ - - protected fun onItemSelected(@IdRes id: Int): Boolean { + private fun onItemSelected(@IdRes id: Int): Boolean { return try { loadFragment(id) if (id == R.id.handwashing) @@ -228,29 +162,16 @@ class MainActivity : ActionBarBase(), } } - /*private fun initFragmentView() { - with(supportFragmentManager.beginTransaction()) { - for (id in IDS) { - activityViewModel.loadFragment(id).also { -// if (supportFragmentManager.findFragmentByTag(id.toString()) == null) { - add(R.id.mainContent, it) - hide(it) -// } - } - } - show(activityViewModel.activeFragment) - commit() - } - }*/ - private fun loadFragment(@IdRes id: Int) { Timber.d("$id - ${dataHandler.activeFragmentId} | ${id == dataHandler.activeFragmentId}") if (id == dataHandler.activeFragmentId) return with(supportFragmentManager.beginTransaction()) { show(dataHandler[id]) + dataHandler.onShow(id) Timber.d("Showing fragment: ${dataHandler[id]}") hide(dataHandler.activeFragment) + dataHandler.onHide(id) Timber.d("Hiding fragment: ${dataHandler.activeFragment}") dataHandler.activeFragmentId = id setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE) @@ -322,16 +243,4 @@ class MainActivity : ActionBarBase(), } } } - - private fun createFragmentForId(@IdRes id: Int): Fragment { - val fragment = when (id) { - R.id.diseases -> DiseasesFragment() - R.id.handwashing -> WashingHandsFragment() - R.id.news -> NewsFragment() - R.id.settings -> SettingsView() - else -> Fragment() // this should never happen - } - fragments[id] = WeakReference(fragment) - return fragment - } } diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/activities/base/BaseVisibleFragmentView.kt b/app/src/main/java/com/javinator9889/handwashingreminder/activities/base/BaseVisibleFragmentView.kt new file mode 100644 index 0000000..113bf33 --- /dev/null +++ b/app/src/main/java/com/javinator9889/handwashingreminder/activities/base/BaseVisibleFragmentView.kt @@ -0,0 +1,23 @@ +/* + * Copyright © 2020 - present | Handwashing reminder by Javinator9889 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see https://www.gnu.org/licenses/. + * + * Created by Javinator9889 on 11/06/20 - Handwashing reminder. + */ +package com.javinator9889.handwashingreminder.activities.base + +abstract class BaseVisibleFragmentView : BaseFragmentView() { + abstract fun onVisibilityChanged(visibility: Int) +} \ No newline at end of file diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/activities/base/LayoutVisibilityChange.kt b/app/src/main/java/com/javinator9889/handwashingreminder/activities/base/LayoutVisibilityChange.kt new file mode 100644 index 0000000..5468885 --- /dev/null +++ b/app/src/main/java/com/javinator9889/handwashingreminder/activities/base/LayoutVisibilityChange.kt @@ -0,0 +1,23 @@ +/* + * Copyright © 2020 - present | Handwashing reminder by Javinator9889 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see https://www.gnu.org/licenses/. + * + * Created by Javinator9889 on 11/06/20 - Handwashing reminder. + */ +package com.javinator9889.handwashingreminder.activities.base + +interface LayoutVisibilityChange { + fun onVisibilityChanged(visibility: Int) +} \ No newline at end of file diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/diseases/DiseaseDescriptionFragment.kt b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/diseases/DiseaseDescriptionFragment.kt index d2130dc..7630a49 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/diseases/DiseaseDescriptionFragment.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/diseases/DiseaseDescriptionFragment.kt @@ -22,7 +22,7 @@ import android.os.Bundle import androidx.annotation.LayoutRes import com.javinator9889.handwashingreminder.R import com.javinator9889.handwashingreminder.activities.base.BaseFragmentView -import com.javinator9889.handwashingreminder.activities.views.viewmodels.ParsedHTMLText +import com.javinator9889.handwashingreminder.data.ParsedHTMLText import kotlinx.android.synthetic.main.disease_description.* import kotlin.properties.Delegates diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/diseases/DiseaseExpandedView.kt b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/diseases/DiseaseExpandedView.kt index b78f581..026a918 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/diseases/DiseaseExpandedView.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/diseases/DiseaseExpandedView.kt @@ -23,8 +23,8 @@ import android.os.Bundle import com.google.android.material.tabs.TabLayoutMediator import com.javinator9889.handwashingreminder.R import com.javinator9889.handwashingreminder.activities.support.ActionBarBase -import com.javinator9889.handwashingreminder.activities.views.viewmodels.ParsedHTMLText import com.javinator9889.handwashingreminder.collections.DiseaseTextAdapter +import com.javinator9889.handwashingreminder.data.ParsedHTMLText import kotlinx.android.synthetic.main.disease_view_expanded.* import kotlin.properties.Delegates diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/diseases/DiseaseExtraInformationFragment.kt b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/diseases/DiseaseExtraInformationFragment.kt index 634879e..f42e9e7 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/diseases/DiseaseExtraInformationFragment.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/diseases/DiseaseExtraInformationFragment.kt @@ -22,7 +22,7 @@ import android.os.Bundle import androidx.annotation.LayoutRes import com.javinator9889.handwashingreminder.R import com.javinator9889.handwashingreminder.activities.base.BaseFragmentView -import com.javinator9889.handwashingreminder.activities.views.viewmodels.ParsedHTMLText +import com.javinator9889.handwashingreminder.data.ParsedHTMLText import kotlinx.android.synthetic.main.simple_text_view.* import kotlin.properties.Delegates diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/diseases/DiseasesFragment.kt b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/diseases/DiseasesFragment.kt index a67f7ae..faff425 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/diseases/DiseasesFragment.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/diseases/DiseasesFragment.kt @@ -30,12 +30,12 @@ import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import com.javinator9889.handwashingreminder.R import com.javinator9889.handwashingreminder.activities.base.BaseFragmentView +import com.javinator9889.handwashingreminder.activities.base.LayoutVisibilityChange import com.javinator9889.handwashingreminder.activities.views.fragments.diseases.adapter.Ads import com.javinator9889.handwashingreminder.activities.views.fragments.diseases.adapter.Disease -import com.javinator9889.handwashingreminder.activities.views.viewmodels.DiseaseInformationFactory import com.javinator9889.handwashingreminder.activities.views.viewmodels.DiseaseInformationViewModel -import com.javinator9889.handwashingreminder.activities.views.viewmodels.ParsedHTMLText import com.javinator9889.handwashingreminder.activities.views.viewmodels.SavedViewModelFactory +import com.javinator9889.handwashingreminder.data.ParsedHTMLText import com.mikepenz.fastadapter.FastAdapter import com.mikepenz.fastadapter.GenericItem import com.mikepenz.fastadapter.adapters.ItemAdapter @@ -45,16 +45,16 @@ import kotlinx.android.synthetic.main.loading_recycler_view.view.* import kotlinx.coroutines.launch import timber.log.Timber -class DiseasesFragment : BaseFragmentView() { +class DiseasesFragment : BaseFragmentView(), LayoutVisibilityChange { override val layoutId: Int = R.layout.loading_recycler_view + private lateinit var parsedHTMLTexts: List private lateinit var fastAdapter: FastAdapter private val upperAdsAdapter: ItemAdapter = ItemAdapter() private val lowerAdsAdapter: ItemAdapter = ItemAdapter() private val diseasesAdapter: ItemAdapter = ItemAdapter() - private val informationFactory = DiseaseInformationFactory() private val informationViewModel: DiseaseInformationViewModel by viewModels { - SavedViewModelFactory(informationFactory, this) + SavedViewModelFactory(DiseaseInformationViewModel.Factory, this) } init { @@ -63,6 +63,8 @@ class DiseasesFragment : BaseFragmentView() { loading.visibility = View.VISIBLE informationViewModel.parsedHTMLText .observe(viewLifecycleOwner, Observer { + if (it.isEmpty()) + return@Observer parsedHTMLTexts = it upperAdsAdapter.add(Ads()) lowerAdsAdapter.add(Ads()) @@ -113,6 +115,11 @@ class DiseasesFragment : BaseFragmentView() { } } + override fun onVisibilityChanged(visibility: Int) { + if (visibility == View.VISIBLE) + lifecycleScope.launchWhenCreated { informationViewModel.parseHtml() } + } + private inner class DiseaseClickEventHook : ClickEventHook() { override fun onBind(viewHolder: RecyclerView.ViewHolder) = if (viewHolder is Disease.ViewHolder) viewHolder.cardContainer diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/diseases/adapter/Disease.kt b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/diseases/adapter/Disease.kt index b29fff7..15ed326 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/diseases/adapter/Disease.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/diseases/adapter/Disease.kt @@ -24,7 +24,7 @@ import androidx.annotation.LayoutRes import androidx.annotation.RawRes import com.google.android.material.card.MaterialCardView import com.javinator9889.handwashingreminder.R -import com.javinator9889.handwashingreminder.activities.views.viewmodels.ParsedHTMLText +import com.javinator9889.handwashingreminder.data.ParsedHTMLText import com.javinator9889.handwashingreminder.graphics.LottieAdaptedPerformanceAnimationView import com.mikepenz.fastadapter.FastAdapter import com.mikepenz.fastadapter.items.AbstractItem diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/news/NewsFragment.kt b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/news/NewsFragment.kt index 2d72f92..113586e 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/news/NewsFragment.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/news/NewsFragment.kt @@ -33,6 +33,7 @@ import androidx.recyclerview.widget.RecyclerView import com.afollestad.materialdialogs.MaterialDialog import com.javinator9889.handwashingreminder.R import com.javinator9889.handwashingreminder.activities.base.BaseFragmentView +import com.javinator9889.handwashingreminder.activities.base.LayoutVisibilityChange import com.javinator9889.handwashingreminder.activities.views.fragments.news.adapter.News import com.javinator9889.handwashingreminder.activities.views.viewmodels.NewsViewModel import com.javinator9889.handwashingreminder.data.UserProperties @@ -48,7 +49,7 @@ import kotlinx.android.synthetic.main.loading_recycler_view.view.* import kotlinx.coroutines.launch import timber.log.Timber -class NewsFragment : BaseFragmentView() { +class NewsFragment : BaseFragmentView(), LayoutVisibilityChange { @LayoutRes override val layoutId: Int = R.layout.loading_recycler_view private lateinit var fastAdapter: FastAdapter @@ -162,6 +163,10 @@ class NewsFragment : BaseFragmentView() { } } + override fun onVisibilityChanged(visibility: Int) { + TODO("Not yet implemented") + } + private inner class ShareClickHook : ClickEventHook() { override fun onBind(viewHolder: RecyclerView.ViewHolder) = if (viewHolder is News.ViewHolder) viewHolder.shareImage diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/settings/SettingsView.kt b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/settings/SettingsView.kt index a7412f5..f5ead08 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/settings/SettingsView.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/settings/SettingsView.kt @@ -18,15 +18,9 @@ */ package com.javinator9889.handwashingreminder.activities.views.fragments.settings -import android.content.ClipData -import android.content.ClipDescription -import android.content.Intent -import android.net.Uri import android.os.Bundle import android.view.View -import androidx.annotation.StringRes import androidx.emoji.text.EmojiCompat -import androidx.lifecycle.lifecycleScope import androidx.preference.ListPreference import androidx.preference.Preference import androidx.preference.PreferenceFragmentCompat @@ -36,34 +30,28 @@ import com.android.billingclient.api.BillingClient.BillingResponseCode import com.google.firebase.analytics.FirebaseAnalytics import com.google.firebase.perf.FirebasePerformance import com.javinator9889.handwashingreminder.R -import com.javinator9889.handwashingreminder.activities.PrivacyTermsActivity +import com.javinator9889.handwashingreminder.activities.base.LayoutVisibilityChange import com.javinator9889.handwashingreminder.application.HandwashingApplication -import com.javinator9889.handwashingreminder.emoji.EmojiLoader import com.javinator9889.handwashingreminder.gms.ads.AdsEnabler import com.javinator9889.handwashingreminder.gms.splitservice.SplitInstallService import com.javinator9889.handwashingreminder.gms.vendor.BillingService -import com.javinator9889.handwashingreminder.jobs.alarms.Alarms import com.javinator9889.handwashingreminder.listeners.OnPurchaseFinishedListener -import com.javinator9889.handwashingreminder.utils.* -import com.mikepenz.aboutlibraries.LibsBuilder -import com.mikepenz.iconics.IconicsDrawable -import com.mikepenz.iconics.typeface.IIcon -import com.mikepenz.iconics.typeface.library.ionicons.Ionicons -import com.mikepenz.iconics.utils.sizeDp -import kotlinx.coroutines.* +import com.javinator9889.handwashingreminder.utils.Ads +import com.javinator9889.handwashingreminder.utils.isConnected import timber.log.Timber import java.lang.ref.WeakReference class SettingsView : PreferenceFragmentCompat(), - Preference.OnPreferenceChangeListener, OnPurchaseFinishedListener { - private lateinit var firebaseAnalyticsPreference: - WeakReference - private lateinit var firebasePerformancePreference: - WeakReference - private lateinit var adsPreference: WeakReference - private lateinit var donationsPreference: WeakReference + Preference.OnPreferenceChangeListener, OnPurchaseFinishedListener, + LayoutVisibilityChange { + lateinit var firebaseAnalyticsPreference: + WeakReference + lateinit var firebasePerformancePreference: + WeakReference + lateinit var adsPreference: WeakReference + lateinit var donationsPreference: WeakReference private lateinit var emojiCompat: EmojiCompat - private lateinit var billingService: BillingService + lateinit var billingService: BillingService private val app = HandwashingApplication.instance override fun onCreate(savedInstanceState: Bundle?) { @@ -81,308 +69,7 @@ class SettingsView : PreferenceFragmentCompat(), override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) billingService = BillingService(view.context) - viewLifecycleOwner.lifecycleScope.launch { - val emojiLoader = EmojiLoader.get(view.context) - val share = findPreference("share") - val playStore = findPreference("playstore") - val telegram = findPreference("telegram") - val github = findPreference("github") - val linkedIn = findPreference("linkedin") - val twitter = findPreference("twitter") - val breakfast = findPreference( - Preferences.BREAKFAST_TIME - ) - val lunch = findPreference( - Preferences.LUNCH_TIME - ) - val dinner = findPreference( - Preferences.DINNER_TIME - ) - val firebaseAnalytics = findPreference( - Preferences.ANALYTICS_ENABLED - ) - val firebasePerformance = findPreference( - Preferences.PERFORMANCE_ENABLED - ) - val ads = findPreference(Preferences.ADS_ENABLED) - val donations = - findPreference(Preferences.DONATIONS) - val translations = findPreference("translate") - val suggestions = findPreference("send_suggestions") - val libraries = findPreference("opensource_libs") - val privacyAndTerms = findPreference("tos_privacy") - val deferreds = mutableListOf>() - deferreds.add( - async(Dispatchers.Main) { - share?.let { - it.icon = icon(Ionicons.Icon.ion_android_share) - it.setOnPreferenceClickListener { - with(FirebaseAnalytics.getInstance(requireContext())) { - logEvent(FirebaseAnalytics.Event.SHARE, null) - } - with(Intent.createChooser(Intent().apply { - action = Intent.ACTION_SEND - putExtra( - Intent.EXTRA_TEXT, - getText(R.string.share_text) - ) - putExtra( - Intent.EXTRA_TITLE, - getText(R.string.share_title) - ) - ClipData.Item( - getUriFromRes( - requireContext(), - R.drawable.handwashing_app_logo - ) - ) - clipData = ClipData( - ClipDescription( - getString(R.string.share_label), - arrayOf("image/*") - ), - ClipData.Item( - getUriFromRes( - requireContext(), - R.drawable.handwashing_app_logo - ) - ) - ) - flags = Intent.FLAG_GRANT_READ_URI_PERMISSION - type = "text/plain" - }, null)) { - startActivity(this) - } - true - } - } - }) - deferreds.add( - async(Dispatchers.Main) { - playStore?.let { - it.icon = icon(Ionicons.Icon.ion_android_playstore) - it.setOnPreferenceClickListener { - openWebsite(PLAYSTORE_URL, R.string.playstore_err) - true - } - } - }) - deferreds.add( - async(Dispatchers.Main) { - telegram?.let { - it.setOnPreferenceClickListener { - openWebsite(TELEGRAM_URL, R.string.telegram_err) - true - } - } - }) - deferreds.add( - async(Dispatchers.Main) { - github?.let { - it.icon = icon(Ionicons.Icon.ion_social_github) - it.setOnPreferenceClickListener { - openWebsite(GITHUB_URL, R.string.browser_err) - true - } - } - }) - deferreds.add( - async(Dispatchers.Main) { - twitter?.let { - it.icon = icon(Ionicons.Icon.ion_social_twitter) - it.setOnPreferenceClickListener { - openWebsite(TWITTER_URL, R.string.twitter_err) - true - } - } - }) - deferreds.add( - async(Dispatchers.Main) { - linkedIn?.let { - it.icon = icon(Ionicons.Icon.ion_social_linkedin) - it.setOnPreferenceClickListener { - openWebsite(LINKEDIN_URL, R.string.browser_err) - true - } - } - }) - deferreds.add( - async(Dispatchers.Main) { - firebaseAnalytics?.let { - it.onPreferenceChangeListener = this@SettingsView - it.icon = icon(Ionicons.Icon.ion_arrow_graph_up_right) - firebaseAnalyticsPreference = WeakReference(it) - } - }) - deferreds.add( - async(Dispatchers.Main) { - firebasePerformance?.let { - it.onPreferenceChangeListener = this@SettingsView - it.icon = icon(Ionicons.Icon.ion_speedometer) - firebasePerformancePreference = WeakReference(it) - } - }) - deferreds.add( - async(Dispatchers.Main) { - ads?.let { - it.onPreferenceChangeListener = this@SettingsView - it.icon = icon(Ionicons.Icon.ion_ios_barcode_outline) - adsPreference = WeakReference(it) - } - }) - deferreds.add( - async(Dispatchers.Main) { - donations?.let { - it.onPreferenceChangeListener = this@SettingsView - it.entryValues = if (isDebuggable()) - resources.getTextArray(R.array.in_app_donations_debug) - else - resources.getTextArray(R.array.in_app_donations) - it.icon = icon(Ionicons.Icon.ion_card) - billingService.addOnPurchaseFinishedListener(this@SettingsView) - donationsPreference = WeakReference(it) - } - }) - deferreds.add( - async(Dispatchers.Main) { - translations?.let { - it.icon = icon(Ionicons.Icon.ion_chatbox_working) - it.setOnPreferenceClickListener { - openWebsite(TRANSLATE_URL, R.string.browser_err) - true - } - } - }) - deferreds.add( - async(Dispatchers.Main) { - suggestions?.let { - it.setOnPreferenceClickListener { - with(Intent(Intent.ACTION_SENDTO)) { - type = "*/*" - data = Uri.parse("mailto:") - putExtra(Intent.EXTRA_EMAIL, arrayOf(Email.TO)) - putExtra(Intent.EXTRA_SUBJECT, Email.SUBJECT) - putExtra(Intent.EXTRA_TEXT, getDeviceInfo()) - if (resolveActivity(requireContext().packageManager) - != null - ) { - startActivity(this) - } else { - MaterialDialog(requireContext()).show { - title(R.string.no_app) - message( - text = getString( - R.string.no_app_long, - getString(R.string.sending_email) - ) - ) - positiveButton(android.R.string.ok) - cancelable(true) - cancelOnTouchOutside(true) - } - } - } - true - } - it.icon = icon(Ionicons.Icon.ion_chatbubbles) - } - }) - deferreds.add( - async(Dispatchers.Main) { - libraries?.let { - it.setOnPreferenceClickListener { - val bundle = Bundle(1).apply { - putString("view", "libs") - } - with(FirebaseAnalytics.getInstance(requireContext())) { - logEvent( - FirebaseAnalytics.Event.VIEW_ITEM, - bundle - ) - } - LibsBuilder() - .withAutoDetect(true) - .withFields(R.string::class.java.fields) - .withCheckCachedDetection(true) - .withSortEnabled(true) - .withAboutVersionShown(true) - .withAboutVersionShownCode(true) - .withAboutVersionShownName(true) - .withShowLoadingProgress(true) - .withActivityTitle(getString(R.string.app_name)) - .start(requireContext()) - true - } - it.icon = icon(Ionicons.Icon.ion_code) - } - }) - deferreds.add( - async(Dispatchers.Main) { - privacyAndTerms?.let { - it.setOnPreferenceClickListener { - Intent( - requireContext(), - PrivacyTermsActivity::class.java - ).run { - startActivity(this) - } - true - } - it.icon = icon(Ionicons.Icon.ion_android_cloud_done) - } - }) - deferreds.add( - async(Dispatchers.Main) { - emojiCompat = emojiLoader.await() - breakfast?.let { - it.icon = icon(Ionicons.Icon.ion_coffee) - it.alarm = Alarms.BREAKFAST_ALARM - try { - it.title = - emojiCompat.process(getText(R.string.breakfast_pref_title)) - it.summaryText = - emojiCompat.process(getText(R.string.breakfast_pref_summ)) - } catch (_: IllegalStateException) { - it.title = getText(R.string.breakfast_pref_title) - it.summaryText = - getText(R.string.breakfast_pref_summ) - } finally { - it.updateSummary() - } - } - lunch?.let { - it.icon = icon(Ionicons.Icon.ion_android_restaurant) - it.alarm = Alarms.LUNCH_ALARM - try { - it.title = - emojiCompat.process(getText(R.string.lunch_pref_title)) - it.summaryText = - emojiCompat.process(getText(R.string.lunch_pref_summ)) - } catch (_: IllegalStateException) { - it.title = getText(R.string.lunch_pref_title) - it.summaryText = getText(R.string.lunch_pref_summ) - } finally { - it.updateSummary() - } - } - dinner?.let { - it.icon = icon(Ionicons.Icon.ion_ios_moon_outline) - it.alarm = Alarms.DINNER_ALARM - try { - it.title = - emojiCompat.process(getText(R.string.dinner_pref_title)) - it.summaryText = - emojiCompat.process(getText(R.string.dinner_pref_summ)) - } catch (_: IllegalStateException) { - it.title = getText(R.string.dinner_pref_title) - it.summaryText = getText(R.string.dinner_pref_summ) - } finally { - it.updateSummary() - } - } - }) - deferreds.awaitAll() - } +// viewLifecycleOwner.lifecycleScope.launch { } override fun onPreferenceChange( @@ -531,38 +218,7 @@ class SettingsView : PreferenceFragmentCompat(), }.show() } - private fun icon(icon: IIcon): IconicsDrawable = - IconicsDrawable(requireContext(), icon).apply { sizeDp = 20 } - - private fun openWebsite(url: String, @StringRes onErrString: Int) { - openWebsite(url, getString(onErrString)) - } - - private fun openWebsite(url: String, onErrString: String) { - if (context == null) - return - val website = Uri.parse(url) - val bundle = Bundle(1).apply { putString("url", url) } - with(FirebaseAnalytics.getInstance(requireContext())) { - logEvent(FirebaseAnalytics.Event.VIEW_ITEM, bundle) - } - with(Intent(Intent.ACTION_VIEW, website)) { - if (resolveActivity(requireContext().packageManager) != - null - ) startActivity(this) - else - MaterialDialog(requireContext()).show { - title(R.string.no_app) - message( - text = getString( - R.string.no_app_long, - onErrString - ) - ) - positiveButton(android.R.string.ok) - cancelable(true) - cancelOnTouchOutside(true) - } - } + override fun onVisibilityChanged(visibility: Int) { + TODO("Not yet implemented") } } \ No newline at end of file diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/washinghands/WashingHandsFragment.kt b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/washinghands/WashingHandsFragment.kt index 05afd44..9e3912e 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/washinghands/WashingHandsFragment.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/washinghands/WashingHandsFragment.kt @@ -27,6 +27,7 @@ import androidx.viewpager2.widget.ViewPager2 import com.google.android.material.tabs.TabLayoutMediator import com.javinator9889.handwashingreminder.R import com.javinator9889.handwashingreminder.activities.base.BaseFragmentView +import com.javinator9889.handwashingreminder.activities.base.LayoutVisibilityChange import kotlinx.android.synthetic.main.how_to_wash_hands_layout.view.* import kotlinx.android.synthetic.main.privacy_terms.* import timber.log.Timber @@ -34,7 +35,7 @@ import java.lang.ref.WeakReference internal const val NUM_PAGES = 8 -class WashingHandsFragment : BaseFragmentView() { +class WashingHandsFragment : BaseFragmentView(), LayoutVisibilityChange { override val layoutId: Int = R.layout.how_to_wash_hands_layout private val items = arrayOfNulls>(NUM_PAGES) @@ -91,4 +92,8 @@ class WashingHandsFragment : BaseFragmentView() { } } } + + override fun onVisibilityChanged(visibility: Int) { + TODO("Not yet implemented") + } } \ No newline at end of file diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/viewmodels/DiseaseInformationViewModel.kt b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/viewmodels/DiseaseInformationViewModel.kt index 3e8975d..64c5b1e 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/viewmodels/DiseaseInformationViewModel.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/viewmodels/DiseaseInformationViewModel.kt @@ -18,21 +18,19 @@ */ package com.javinator9889.handwashingreminder.activities.views.viewmodels -import android.os.Parcel -import android.os.Parcelable import android.text.Spanned -import android.text.TextUtils -import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel -import androidx.lifecycle.liveData +import androidx.lifecycle.viewModelScope import com.beust.klaxon.Klaxon import com.google.firebase.ktx.Firebase import com.google.firebase.remoteconfig.ktx.remoteConfig import com.javinator9889.handwashingreminder.collections.DiseasesInformation import com.javinator9889.handwashingreminder.collections.DiseasesList -import com.javinator9889.handwashingreminder.collections.DiseasesListWrapper +import com.javinator9889.handwashingreminder.data.ParsedHTMLText import com.javinator9889.handwashingreminder.utils.RemoteConfig.DISEASES_JSON +import com.javinator9889.handwashingreminder.utils.notNull import kotlinx.coroutines.* import org.sufficientlysecure.htmltextview.HtmlFormatter import org.sufficientlysecure.htmltextview.HtmlFormatterBuilder @@ -43,61 +41,68 @@ private const val PARSED_JSON_KEY = "text:json:parsed" class DiseaseInformationViewModel( private val state: SavedStateHandle ) : ViewModel() { - val parsedHTMLText: LiveData> = liveData { - emitSource(state.getLiveData(DATA_KEY, parseHTML())) - } + private val informationList: DiseasesList = loadHtmlData() + val parsedHTMLText: MutableLiveData> = + state.getLiveData(DATA_KEY, emptyList()) - private suspend fun parseHTML(): List { - val informationList = withContext(Dispatchers.IO) { - if (state.contains(PARSED_JSON_KEY) && - state.get>(PARSED_JSON_KEY) != null - ) - DiseasesList( - state.get>(PARSED_JSON_KEY)!! - ) - val diseasesString = - with(Firebase.remoteConfig) { - getString(DISEASES_JSON) - } - Klaxon().parse(diseasesString) - } ?: return emptyList() - state.set( - PARSED_JSON_KEY, - DiseasesListWrapper(informationList.diseases) + private fun loadHtmlData(): DiseasesList { + if (state.contains(PARSED_JSON_KEY) && + state.get>(PARSED_JSON_KEY) != null ) - return withContext(Dispatchers.Default) { + return DiseasesList(state.get(PARSED_JSON_KEY)!!) + val diseasesString = with(Firebase.remoteConfig) { + getString(DISEASES_JSON) + } + return Klaxon().parse(diseasesString) + ?: DiseasesList(emptyList()) + } + + fun parseHtml() { + viewModelScope.launch { + if (!state.get>(DATA_KEY).isNullOrEmpty()) + return@launch val parsedItemsList = ArrayList(informationList.diseases.size) - val deferreds = - ArrayList>>(informationList.diseases.size) - informationList.diseases.forEach { information -> - val htmlDef = listOf( - async { createHTML(information.name) }, - async { createHTML(information.shortDescription) }, - async { createHTML(information.longDescription) }, - async { createHTML(information.provider) }, - async { createHTML(information.website) }, - async { createHTML(information.symptoms) }, - async { createHTML(information.prevention) } + val deferreds = mutableListOf>>() + informationList.diseases.forEach { disease -> + deferreds.add( + listOf( + async { createHTML(disease.name) }, + async { createHTML(disease.shortDescription) }, + async { createHTML(disease.longDescription) }, + async { createHTML(disease.provider) }, + async { createHTML(disease.website) }, + async { createHTML(disease.symptoms) }, + async { createHTML(disease.prevention) } + ) ) - deferreds.add(htmlDef) } - deferreds.forEachIndexed { i, infoList -> - val data = infoList.awaitAll() - parsedItemsList.add( - i, ParsedHTMLText( - data[0], - data[1], - data[2], - data[3], - data[4], - data[5], - data[6] + deferreds.forEachIndexed { i, htmlData -> + launch { + val data = htmlData.awaitAll() + parsedItemsList.add( + i, ParsedHTMLText( + name = data[0], + shortDescription = data[1], + longDescription = data[2], + provider = data[3], + website = data[4], + symptoms = data[5], + prevention = data[6] + ) ) - ) + withContext(Dispatchers.Main) { + state[DATA_KEY] = parsedItemsList + } + }.invokeOnCompletion { + it.notNull { + viewModelScope.launch(context = Dispatchers.Main) { + state[DATA_KEY] = parsedItemsList + parsedHTMLText.value = parsedItemsList + } + } + } } - state.set(DATA_KEY, parsedItemsList) - parsedItemsList } } @@ -107,54 +112,10 @@ class DiseaseInformationViewModel( isRemoveTrailingWhiteSpace = true HtmlFormatter.formatHtml(this) } -} -class DiseaseInformationFactory : - ViewModelAssistedFactory { - override fun create(handle: SavedStateHandle) = - DiseaseInformationViewModel(handle) -} - -data class ParsedHTMLText( - val name: CharSequence, - val shortDescription: CharSequence, - val longDescription: CharSequence, - val provider: CharSequence, - val website: CharSequence, - val symptoms: CharSequence, - val prevention: CharSequence -) : Parcelable { - constructor(parcel: Parcel) : this( - TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel), - TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel), - TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel), - TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel), - TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel), - TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel), - TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel) - ) - - override fun writeToParcel(parcel: Parcel, flags: Int) { - TextUtils.writeToParcel(name, parcel, flags) - TextUtils.writeToParcel(shortDescription, parcel, flags) - TextUtils.writeToParcel(longDescription, parcel, flags) - TextUtils.writeToParcel(provider, parcel, flags) - TextUtils.writeToParcel(website, parcel, flags) - TextUtils.writeToParcel(symptoms, parcel, flags) - TextUtils.writeToParcel(prevention, parcel, flags) - } - - override fun describeContents(): Int { - return 0 - } - - companion object CREATOR : Parcelable.Creator { - override fun createFromParcel(parcel: Parcel): ParsedHTMLText { - return ParsedHTMLText(parcel) - } - - override fun newArray(size: Int): Array { - return arrayOfNulls(size) - } + companion object Factory : + ViewModelAssistedFactory { + override fun create(handle: SavedStateHandle) = + DiseaseInformationViewModel(handle) } } \ No newline at end of file diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/viewmodels/NewsViewModel.kt b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/viewmodels/NewsViewModel.kt index 9da4410..a2a9075 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/viewmodels/NewsViewModel.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/viewmodels/NewsViewModel.kt @@ -33,11 +33,9 @@ import okhttp3.Headers import timber.log.Timber import java.io.Reader -internal const val ARG_NEWS_DATA = "args:newsmodel:data" class NewsViewModel : ViewModel() { val newsData: MutableLiveData = MutableLiveData() -// val activeItems = mutableSetOf() suspend fun populateData( from: Int = 0, diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/collections/DiseaseTextAdapter.kt b/app/src/main/java/com/javinator9889/handwashingreminder/collections/DiseaseTextAdapter.kt index 44b1ddc..441a5f0 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/collections/DiseaseTextAdapter.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/collections/DiseaseTextAdapter.kt @@ -27,7 +27,7 @@ import com.javinator9889.handwashingreminder.activities.views.fragments.diseases import com.javinator9889.handwashingreminder.activities.views.fragments.diseases.ARG_POSITION import com.javinator9889.handwashingreminder.activities.views.fragments.diseases.DiseaseDescriptionFragment import com.javinator9889.handwashingreminder.activities.views.fragments.diseases.DiseaseExtraInformationFragment -import com.javinator9889.handwashingreminder.activities.views.viewmodels.ParsedHTMLText +import com.javinator9889.handwashingreminder.data.ParsedHTMLText class DiseaseTextAdapter( fm: FragmentActivity, diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/data/MainActivityDataHandler.kt b/app/src/main/java/com/javinator9889/handwashingreminder/data/MainActivityDataHandler.kt similarity index 90% rename from app/src/main/java/com/javinator9889/handwashingreminder/activities/views/data/MainActivityDataHandler.kt rename to app/src/main/java/com/javinator9889/handwashingreminder/data/MainActivityDataHandler.kt index f32618c..188fcf8 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/data/MainActivityDataHandler.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/data/MainActivityDataHandler.kt @@ -16,12 +16,13 @@ * * Created by Javinator9889 on 9/06/20 - Handwashing reminder. */ -package com.javinator9889.handwashingreminder.activities.views.data +package com.javinator9889.handwashingreminder.data import android.content.Context import android.os.Bundle import android.util.SparseArray import android.view.MenuItem +import android.view.View import androidx.annotation.IdRes import androidx.core.util.forEach import androidx.core.util.set @@ -30,6 +31,7 @@ import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentManager import com.google.android.material.bottomnavigation.BottomNavigationView import com.javinator9889.handwashingreminder.R +import com.javinator9889.handwashingreminder.activities.base.LayoutVisibilityChange import com.javinator9889.handwashingreminder.activities.views.fragments.diseases.DiseasesFragment import com.javinator9889.handwashingreminder.activities.views.fragments.news.NewsFragment import com.javinator9889.handwashingreminder.activities.views.fragments.settings.SettingsView @@ -48,7 +50,8 @@ internal val IDS = class MainActivityDataHandler(@IdRes var activeFragmentId: Int = R.id.diseases) { - private val fragments = SparseArray>(4) + private val fragments = + SparseArray>(4) val activeFragment: Fragment get() = this[activeFragmentId] @@ -116,12 +119,19 @@ class MainActivityDataHandler(@IdRes var activeFragmentId: Int = R.id.diseases) } } show(activeFragment) + onShow(activeFragmentId) commit() } } fun clear() = fragments.clear() + fun onShow(@IdRes id: Int) = + (this[id] as LayoutVisibilityChange).onVisibilityChanged(View.VISIBLE) + + fun onHide(@IdRes id: Int) = + (this[id] as LayoutVisibilityChange).onVisibilityChanged(View.INVISIBLE) + private fun createFragmentForId(@IdRes id: Int): Fragment { if (id !in IDS) throw IllegalArgumentException("id not in IDs") diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/data/ParsedHTMLText.kt b/app/src/main/java/com/javinator9889/handwashingreminder/data/ParsedHTMLText.kt new file mode 100644 index 0000000..045072b --- /dev/null +++ b/app/src/main/java/com/javinator9889/handwashingreminder/data/ParsedHTMLText.kt @@ -0,0 +1,61 @@ +/* + * Copyright © 2020 - present | Handwashing reminder by Javinator9889 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see https://www.gnu.org/licenses/. + * + * Created by Javinator9889 on 11/06/20 - Handwashing reminder. + */ +package com.javinator9889.handwashingreminder.data + +import android.os.Parcel +import android.os.Parcelable +import android.text.TextUtils + +data class ParsedHTMLText( + val name: CharSequence, + val shortDescription: CharSequence, + val longDescription: CharSequence, + val provider: CharSequence, + val website: CharSequence, + val symptoms: CharSequence, + val prevention: CharSequence +) : Parcelable { + constructor(parcel: Parcel) : this( + TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel), + TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel), + TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel), + TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel), + TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel), + TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel), + TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel) + ) + + override fun writeToParcel(parcel: Parcel, flags: Int) { + TextUtils.writeToParcel(name, parcel, flags) + TextUtils.writeToParcel(shortDescription, parcel, flags) + TextUtils.writeToParcel(longDescription, parcel, flags) + TextUtils.writeToParcel(provider, parcel, flags) + TextUtils.writeToParcel(website, parcel, flags) + TextUtils.writeToParcel(symptoms, parcel, flags) + TextUtils.writeToParcel(prevention, parcel, flags) + } + + override fun describeContents() = 0 + + companion object CREATOR : Parcelable.Creator { + override fun createFromParcel(parcel: Parcel) = ParsedHTMLText(parcel) + + override fun newArray(size: Int) = arrayOfNulls(size) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/data/SettingsLoader.kt b/app/src/main/java/com/javinator9889/handwashingreminder/data/SettingsLoader.kt new file mode 100644 index 0000000..4731246 --- /dev/null +++ b/app/src/main/java/com/javinator9889/handwashingreminder/data/SettingsLoader.kt @@ -0,0 +1,240 @@ +/* + * Copyright © 2020 - present | Handwashing reminder by Javinator9889 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see https://www.gnu.org/licenses/. + * + * Created by Javinator9889 on 11/06/20 - Handwashing reminder. + */ +package com.javinator9889.handwashingreminder.data + +import android.content.ClipData +import android.content.ClipDescription +import android.content.Intent +import android.net.Uri +import android.os.Bundle +import androidx.annotation.StringRes +import androidx.emoji.text.EmojiCompat +import androidx.lifecycle.LifecycleOwner +import androidx.lifecycle.lifecycleScope +import androidx.preference.Preference +import com.afollestad.materialdialogs.MaterialDialog +import com.google.firebase.analytics.FirebaseAnalytics +import com.javinator9889.handwashingreminder.R +import com.javinator9889.handwashingreminder.activities.views.fragments.settings.SettingsView +import com.javinator9889.handwashingreminder.emoji.EmojiLoader +import com.javinator9889.handwashingreminder.utils.* +import com.mikepenz.iconics.IconicsDrawable +import com.mikepenz.iconics.typeface.IIcon +import com.mikepenz.iconics.typeface.library.ionicons.Ionicons +import com.mikepenz.iconics.utils.sizeDp +import kotlinx.coroutines.Deferred +import kotlinx.coroutines.async +import kotlinx.coroutines.awaitAll +import kotlinx.coroutines.launch +import java.lang.ref.WeakReference + +class SettingsLoader( + private val view: SettingsView, + private val lifecycleOwner: LifecycleOwner +) { + private val emojiLoader = EmojiLoader.get(view.requireContext()) + private lateinit var emojiCompat: EmojiCompat + + fun loadViews() { + val deferreds = mutableSetOf>() + with(view) { + lifecycleOwner.lifecycleScope.launch { + setupPreferenceAsync( + "playstore", + Ionicons.Icon.ion_android_playstore, + onClickListener = { + openWebsite(PLAYSTORE_URL, R.string.playstore_err) + true + }).let { deferreds.add(it) } + setupPreferenceAsync( + "share", + Ionicons.Icon.ion_android_share, + onClickListener = { + with(FirebaseAnalytics.getInstance(requireContext())) { + logEvent(FirebaseAnalytics.Event.SHARE, null) + } + with(Intent.createChooser(Intent().apply { + action = Intent.ACTION_SEND + putExtra( + Intent.EXTRA_TEXT, + getText(R.string.share_text) + ) + putExtra( + Intent.EXTRA_TITLE, + getText(R.string.share_title) + ) + ClipData.Item( + getUriFromRes( + requireContext(), + R.drawable.handwashing_app_logo + ) + ) + clipData = ClipData( + ClipDescription( + getString(R.string.share_label), + arrayOf("image/*") + ), + ClipData.Item( + getUriFromRes( + requireContext(), + R.drawable.handwashing_app_logo + ) + ) + ) + flags = Intent.FLAG_GRANT_READ_URI_PERMISSION + type = "text/plain" + }, null)) { + startActivity(this) + } + true + }).let { deferreds.add(it) } + setupPreferenceAsync("telegram", onClickListener = { + openWebsite(TELEGRAM_URL, R.string.telegram_err) + true + }).let { deferreds.add(it) } + setupPreferenceAsync( + "github", + Ionicons.Icon.ion_social_github, + onClickListener = { + openWebsite(GITHUB_URL, R.string.browser_err) + true + }).let { deferreds.add(it) } + setupPreferenceAsync( + "twitter", + Ionicons.Icon.ion_social_twitter, + onClickListener = { + openWebsite(TWITTER_URL, R.string.twitter_err) + true + }).let { deferreds.add(it) } + setupPreferenceAsync( + "linkedin", + Ionicons.Icon.ion_social_linkedin, + onClickListener = { + openWebsite(LINKEDIN_URL, R.string.browser_err) + true + }).let { deferreds.add(it) } + setupPreferenceAsync( + Preferences.ANALYTICS_ENABLED, + Ionicons.Icon.ion_arrow_graph_up_right, + onChangeListener = this@with, + action = { firebaseAnalyticsPreference = WeakReference(it) } + ).let { deferreds.add(it) } + deferreds.awaitAll() + } + } + } + + private fun setupPreferenceAsync( + name: String, + icon: IIcon? = null, + onClickListener: ((Preference) -> Boolean)? = null, + onChangeListener: Preference.OnPreferenceChangeListener? = null, + action: ((Preference) -> Unit)? = null + ): Deferred = lifecycleOwner.lifecycleScope.async { + view.findPreference(name)?.let { + icon.notNull { icon -> + it.icon = setupIcon(icon) + } + onClickListener.notNull { listener -> + it.setOnPreferenceClickListener(listener) + } + onChangeListener.notNull { listener -> + it.onPreferenceChangeListener = listener + } + action.notNull { action -> + action(it) + } + } + } + + private fun setupIcon(icon: IIcon): IconicsDrawable = + IconicsDrawable(view.requireContext(), icon).apply { sizeDp = 20 } + + private fun openWebsite(url: String, @StringRes onErrString: Int) = + openWebsite(url, view.getString(onErrString)) + + private fun openWebsite(url: String, onErrString: String) { + if (view.context == null) + return + val website = Uri.parse(url) + val bundle = Bundle(1).apply { putString("url", url) } + with(FirebaseAnalytics.getInstance(view.requireContext())) { + logEvent(FirebaseAnalytics.Event.VIEW_ITEM, bundle) + } + with(Intent(Intent.ACTION_VIEW, website)) { + if (resolveActivity(view.requireContext().packageManager) != null) + view.startActivity(this) + else + MaterialDialog(view.requireContext()).show { + title(R.string.no_app) + message( + text = view.getString( + R.string.no_app_long, + onErrString + ) + ) + positiveButton(android.R.string.ok) + cancelable(true) + cancelOnTouchOutside(true) + } + } + } +} + +/* +setupPreference("share", Ionicons.Icon.ion_android_share, { + with(FirebaseAnalytics.getInstance(requireContext())) { + logEvent(FirebaseAnalytics.Event.SHARE, null) + } + with(Intent.createChooser(Intent().apply { + action = Intent.ACTION_SEND + putExtra( + Intent.EXTRA_TEXT, + getText(R.string.share_text) + ) + putExtra( + Intent.EXTRA_TITLE, + getText(R.string.share_title) + ) + ClipData.Item( + getUriFromRes( + requireContext(), + R.drawable.handwashing_app_logo + ) + ) + clipData = ClipData( + ClipDescription( + getString(R.string.share_label), + arrayOf("image/*") + ), + ClipData.Item( + getUriFromRes( + requireContext(), + R.drawable.handwashing_app_logo + ) + ) + ) + flags = Intent.FLAG_GRANT_READ_URI_PERMISSION + type = "text/plain" + }, null)) { + startActivity(this) + } + true + }) + */ \ No newline at end of file diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/data/SettingsLoaderBak.kt b/app/src/main/java/com/javinator9889/handwashingreminder/data/SettingsLoaderBak.kt new file mode 100644 index 0000000..8fe6614 --- /dev/null +++ b/app/src/main/java/com/javinator9889/handwashingreminder/data/SettingsLoaderBak.kt @@ -0,0 +1,290 @@ +/* + * Copyright © 2020 - present | Handwashing reminder by Javinator9889 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see https://www.gnu.org/licenses/. + * + * Created by Javinator9889 on 11/06/20 - Handwashing reminder. + */ +package com.javinator9889.handwashingreminder.data + +import android.content.Intent +import android.net.Uri +import android.os.Bundle +import androidx.annotation.StringRes +import androidx.emoji.text.EmojiCompat +import androidx.preference.ListPreference +import androidx.preference.Preference +import androidx.preference.SwitchPreference +import com.afollestad.materialdialogs.MaterialDialog +import com.google.firebase.analytics.FirebaseAnalytics +import com.javinator9889.handwashingreminder.R +import com.javinator9889.handwashingreminder.activities.PrivacyTermsActivity +import com.javinator9889.handwashingreminder.activities.views.fragments.settings.SettingsView +import com.javinator9889.handwashingreminder.activities.views.fragments.settings.TimePickerPreference +import com.javinator9889.handwashingreminder.emoji.EmojiLoader +import com.javinator9889.handwashingreminder.jobs.alarms.Alarms +import com.javinator9889.handwashingreminder.utils.* +import com.mikepenz.aboutlibraries.LibsBuilder +import com.mikepenz.iconics.IconicsDrawable +import com.mikepenz.iconics.typeface.IIcon +import com.mikepenz.iconics.typeface.library.ionicons.Ionicons +import com.mikepenz.iconics.utils.sizeDp +import kotlinx.coroutines.Deferred +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.async +import kotlinx.coroutines.awaitAll +import java.lang.ref.WeakReference + + +class SettingsLoaderBak(private val view: SettingsView) { + private val emojiLoader = EmojiLoader.get(view.requireContext()) + private lateinit var emojiCompat: EmojiCompat + + suspend fun loadViews() { + with(view) { + val breakfast = findPreference( + Preferences.BREAKFAST_TIME + ) + val lunch = findPreference( + Preferences.LUNCH_TIME + ) + val dinner = findPreference( + Preferences.DINNER_TIME + ) + val firebaseAnalytics = findPreference( + Preferences.ANALYTICS_ENABLED + ) + val firebasePerformance = findPreference( + Preferences.PERFORMANCE_ENABLED + ) + val ads = findPreference(Preferences.ADS_ENABLED) + val donations = + findPreference(Preferences.DONATIONS) + val translations = findPreference("translate") + val suggestions = findPreference("send_suggestions") + val libraries = findPreference("opensource_libs") + val privacyAndTerms = findPreference("tos_privacy") + val deferreds = mutableSetOf>() + deferreds.add( + async(Dispatchers.Main) { + firebaseAnalytics?.let { + it.onPreferenceChangeListener = this@SettingsView + it.icon = icon(Ionicons.Icon.ion_arrow_graph_up_right) + firebaseAnalyticsPreference = WeakReference(it) + } + }) + deferreds.add( + async(Dispatchers.Main) { + firebasePerformance?.let { + it.onPreferenceChangeListener = this@with + it.icon = icon(Ionicons.Icon.ion_speedometer) + firebasePerformancePreference = WeakReference(it) + } + }) + deferreds.add( + async(Dispatchers.Main) { + ads?.let { + it.onPreferenceChangeListener = this@SettingsView + it.icon = icon(Ionicons.Icon.ion_ios_barcode_outline) + adsPreference = WeakReference(it) + } + }) + deferreds.add( + async(Dispatchers.Main) { + donations?.let { + it.onPreferenceChangeListener = this@SettingsView + it.entryValues = if (isDebuggable()) + resources.getTextArray(R.array.in_app_donations_debug) + else + resources.getTextArray(R.array.in_app_donations) + it.icon = icon(Ionicons.Icon.ion_card) + billingService.addOnPurchaseFinishedListener(this@SettingsView) + donationsPreference = WeakReference(it) + } + }) + deferreds.add( + async(Dispatchers.Main) { + translations?.let { + it.icon = icon(Ionicons.Icon.ion_chatbox_working) + it.setOnPreferenceClickListener { + openWebsite(TRANSLATE_URL, R.string.browser_err) + true + } + } + }) + deferreds.add( + async(Dispatchers.Main) { + suggestions?.let { + it.setOnPreferenceClickListener { + with(Intent(Intent.ACTION_SENDTO)) { + type = "*/*" + data = Uri.parse("mailto:") + putExtra(Intent.EXTRA_EMAIL, arrayOf(Email.TO)) + putExtra(Intent.EXTRA_SUBJECT, Email.SUBJECT) + putExtra(Intent.EXTRA_TEXT, getDeviceInfo()) + if (resolveActivity(requireContext().packageManager) + != null + ) { + startActivity(this) + } else { + MaterialDialog(requireContext()).show { + title(R.string.no_app) + message( + text = getString( + R.string.no_app_long, + getString(R.string.sending_email) + ) + ) + positiveButton(android.R.string.ok) + cancelable(true) + cancelOnTouchOutside(true) + } + } + } + true + } + it.icon = icon(Ionicons.Icon.ion_chatbubbles) + } + }) + deferreds.add( + async(Dispatchers.Main) { + libraries?.let { + it.setOnPreferenceClickListener { + val bundle = Bundle(1).apply { + putString("view", "libs") + } + with(FirebaseAnalytics.getInstance(requireContext())) { + logEvent( + FirebaseAnalytics.Event.VIEW_ITEM, + bundle + ) + } + LibsBuilder() + .withAutoDetect(true) + .withFields(R.string::class.java.fields) + .withCheckCachedDetection(true) + .withSortEnabled(true) + .withAboutVersionShown(true) + .withAboutVersionShownCode(true) + .withAboutVersionShownName(true) + .withShowLoadingProgress(true) + .withActivityTitle(getString(R.string.app_name)) + .start(requireContext()) + true + } + it.icon = icon(Ionicons.Icon.ion_code) + } + }) + deferreds.add( + async(Dispatchers.Main) { + privacyAndTerms?.let { + it.setOnPreferenceClickListener { + Intent( + requireContext(), + PrivacyTermsActivity::class.java + ).run { + startActivity(this) + } + true + } + it.icon = icon(Ionicons.Icon.ion_android_cloud_done) + } + }) + deferreds.add( + async(Dispatchers.Main) { + emojiCompat = emojiLoader.await() + breakfast?.let { + it.icon = icon(Ionicons.Icon.ion_coffee) + it.alarm = Alarms.BREAKFAST_ALARM + try { + it.title = + emojiCompat.process(getText(R.string.breakfast_pref_title)) + it.summaryText = + emojiCompat.process(getText(R.string.breakfast_pref_summ)) + } catch (_: IllegalStateException) { + it.title = getText(R.string.breakfast_pref_title) + it.summaryText = + getText(R.string.breakfast_pref_summ) + } finally { + it.updateSummary() + } + } + lunch?.let { + it.icon = icon(Ionicons.Icon.ion_android_restaurant) + it.alarm = Alarms.LUNCH_ALARM + try { + it.title = + emojiCompat.process(getText(R.string.lunch_pref_title)) + it.summaryText = + emojiCompat.process(getText(R.string.lunch_pref_summ)) + } catch (_: IllegalStateException) { + it.title = getText(R.string.lunch_pref_title) + it.summaryText = getText(R.string.lunch_pref_summ) + } finally { + it.updateSummary() + } + } + dinner?.let { + it.icon = icon(Ionicons.Icon.ion_ios_moon_outline) + it.alarm = Alarms.DINNER_ALARM + try { + it.title = + emojiCompat.process(getText(R.string.dinner_pref_title)) + it.summaryText = + emojiCompat.process(getText(R.string.dinner_pref_summ)) + } catch (_: IllegalStateException) { + it.title = getText(R.string.dinner_pref_title) + it.summaryText = getText(R.string.dinner_pref_summ) + } finally { + it.updateSummary() + } + } + }) + deferreds.awaitAll() + } + } + + private fun icon(icon: IIcon): IconicsDrawable = + IconicsDrawable(view.requireContext(), icon).apply { sizeDp = 20 } + + private fun openWebsite(url: String, @StringRes onErrString: Int) = + openWebsite(url, view.getString(onErrString)) + + private fun openWebsite(url: String, onErrString: String) { + if (view.context == null) + return + val website = Uri.parse(url) + val bundle = Bundle(1).apply { putString("url", url) } + with(FirebaseAnalytics.getInstance(view.requireContext())) { + logEvent(FirebaseAnalytics.Event.VIEW_ITEM, bundle) + } + with(Intent(Intent.ACTION_VIEW, website)) { + if (resolveActivity(view.requireContext().packageManager) != null) + view.startActivity(this) + else + MaterialDialog(view.requireContext()).show { + title(R.string.no_app) + message( + text = view.getString( + R.string.no_app_long, + onErrString + ) + ) + positiveButton(android.R.string.ok) + cancelable(true) + cancelOnTouchOutside(true) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/utils/Collections.kt b/app/src/main/java/com/javinator9889/handwashingreminder/utils/Collections.kt index 7d8746c..d5afe54 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/utils/Collections.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/utils/Collections.kt @@ -23,7 +23,7 @@ import kotlin.Comparator import kotlin.collections.ArrayList import kotlin.math.abs -fun Array?.notEmpty(f: (it: Array) -> Unit) { +fun Collection?.notEmpty(f: (it: Collection) -> Unit) { if (!this.isNullOrEmpty()) f(this) } From ab94e8e1da7cc34f1ede5e38e6adaf165b834d72 Mon Sep 17 00:00:00 2001 From: Javinator9889 Date: Tue, 16 Jun 2020 11:41:04 +0200 Subject: [PATCH 45/95] Completed settings loader file --- .../views/fragments/settings/SettingsView.kt | 6 +- .../data/SettingsLoader.kt | 241 +++++++++++---- .../data/SettingsLoaderBak.kt | 290 ------------------ .../handwashingreminder/utils/Android.kt | 3 + 4 files changed, 196 insertions(+), 344 deletions(-) delete mode 100644 app/src/main/java/com/javinator9889/handwashingreminder/data/SettingsLoaderBak.kt diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/settings/SettingsView.kt b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/settings/SettingsView.kt index f5ead08..3023a61 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/settings/SettingsView.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/settings/SettingsView.kt @@ -32,6 +32,7 @@ import com.google.firebase.perf.FirebasePerformance import com.javinator9889.handwashingreminder.R import com.javinator9889.handwashingreminder.activities.base.LayoutVisibilityChange import com.javinator9889.handwashingreminder.application.HandwashingApplication +import com.javinator9889.handwashingreminder.data.SettingsLoader import com.javinator9889.handwashingreminder.gms.ads.AdsEnabler import com.javinator9889.handwashingreminder.gms.splitservice.SplitInstallService import com.javinator9889.handwashingreminder.gms.vendor.BillingService @@ -52,6 +53,7 @@ class SettingsView : PreferenceFragmentCompat(), lateinit var donationsPreference: WeakReference private lateinit var emojiCompat: EmojiCompat lateinit var billingService: BillingService + private val loader = SettingsLoader(view = this, lifecycleOwner = this) private val app = HandwashingApplication.instance override fun onCreate(savedInstanceState: Bundle?) { @@ -69,7 +71,6 @@ class SettingsView : PreferenceFragmentCompat(), override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) billingService = BillingService(view.context) -// viewLifecycleOwner.lifecycleScope.launch { } override fun onPreferenceChange( @@ -219,6 +220,7 @@ class SettingsView : PreferenceFragmentCompat(), } override fun onVisibilityChanged(visibility: Int) { - TODO("Not yet implemented") + if (visibility == View.VISIBLE) + loader.loadViews() } } \ No newline at end of file diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/data/SettingsLoader.kt b/app/src/main/java/com/javinator9889/handwashingreminder/data/SettingsLoader.kt index 4731246..bb2e7a8 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/data/SettingsLoader.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/data/SettingsLoader.kt @@ -27,32 +27,38 @@ import androidx.annotation.StringRes import androidx.emoji.text.EmojiCompat import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.lifecycleScope +import androidx.preference.ListPreference import androidx.preference.Preference import com.afollestad.materialdialogs.MaterialDialog import com.google.firebase.analytics.FirebaseAnalytics import com.javinator9889.handwashingreminder.R +import com.javinator9889.handwashingreminder.activities.PrivacyTermsActivity import com.javinator9889.handwashingreminder.activities.views.fragments.settings.SettingsView +import com.javinator9889.handwashingreminder.activities.views.fragments.settings.TimePickerPreference import com.javinator9889.handwashingreminder.emoji.EmojiLoader import com.javinator9889.handwashingreminder.utils.* +import com.mikepenz.aboutlibraries.LibsBuilder import com.mikepenz.iconics.IconicsDrawable import com.mikepenz.iconics.typeface.IIcon import com.mikepenz.iconics.typeface.library.ionicons.Ionicons import com.mikepenz.iconics.utils.sizeDp -import kotlinx.coroutines.Deferred -import kotlinx.coroutines.async -import kotlinx.coroutines.awaitAll -import kotlinx.coroutines.launch +import kotlinx.coroutines.* import java.lang.ref.WeakReference +import java.util.concurrent.atomic.AtomicBoolean class SettingsLoader( private val view: SettingsView, private val lifecycleOwner: LifecycleOwner ) { - private val emojiLoader = EmojiLoader.get(view.requireContext()) + private lateinit var emojiLoader: CompletableDeferred private lateinit var emojiCompat: EmojiCompat + private var arePreferencesInitialized = AtomicBoolean(false) fun loadViews() { + if (arePreferencesInitialized.get()) + return val deferreds = mutableSetOf>() + emojiLoader = EmojiLoader.get(view.requireContext()) with(view) { lifecycleOwner.lifecycleScope.launch { setupPreferenceAsync( @@ -133,9 +139,150 @@ class SettingsLoader( Preferences.ANALYTICS_ENABLED, Ionicons.Icon.ion_arrow_graph_up_right, onChangeListener = this@with, - action = { firebaseAnalyticsPreference = WeakReference(it) } + onInitialized = { it, _ -> + firebaseAnalyticsPreference = WeakReference(it) + } + ).let { deferreds.add(it) } + setupPreferenceAsync( + Preferences.PERFORMANCE_ENABLED, + Ionicons.Icon.ion_ios_speedometer, + onChangeListener = this@with, + onInitialized = { it, _ -> + firebasePerformancePreference = WeakReference(it) + } + ).let { deferreds.add(it) } + setupPreferenceAsync( + Preferences.ADS_ENABLED, + Ionicons.Icon.ion_ios_barcode_outline, + onChangeListener = this@with, + onInitialized = { it, _ -> + adsPreference = WeakReference(it) + } + ).let { deferreds.add(it) } + setupPreferenceAsync( + Preferences.DONATIONS, + Ionicons.Icon.ion_card, + onChangeListener = this@with, + onInitialized = { it, _ -> + it as ListPreference + it.entryValues = if (isDebuggable()) + requireContext().resources.getTextArray(R.array.in_app_donations_debug) + else + requireContext().resources.getTextArray(R.array.in_app_donations) + billingService.addOnPurchaseFinishedListener(this@with) + donationsPreference = WeakReference(it) + } + ).let { deferreds.add(it) } + setupPreferenceAsync( + "translate", + Ionicons.Icon.ion_chatbox_working, + onClickListener = { + openWebsite( + TRANSLATE_URL, + R.string.browser_err + ) + true + } + ).let { deferreds.add(it) } + setupPreferenceAsync( + "send_suggestions", + Ionicons.Icon.ion_chatbubbles, + onClickListener = { + with(Intent(Intent.ACTION_SENDTO)) { + type = "text/plain" + data = Uri.parse("mailto:") + putExtra(Intent.EXTRA_EMAIL, arrayOf(Email.TO)) + putExtra(Intent.EXTRA_SUBJECT, Email.SUBJECT) + putExtra(Intent.EXTRA_TEXT, getDeviceInfo()) + putExtra( + Intent.EXTRA_HTML_TEXT, + getDeviceInfoHTML() + ) + if (resolveActivity(requireContext().packageManager) != null) + startActivity(this) + else MaterialDialog(requireContext()).show { + title(R.string.no_app) + message( + text = getString( + R.string.no_app_long, + getString(R.string.sending_email) + ) + ) + positiveButton(android.R.string.ok) + cancelable(true) + cancelOnTouchOutside(true) + } + } + true + } + ).let { deferreds.add(it) } + setupPreferenceAsync( + "opensource_libs", + Ionicons.Icon.ion_code, + onClickListener = { + val bundle = Bundle(1).apply { + putString("view", "libs") + } + with(FirebaseAnalytics.getInstance(requireContext())) { + logEvent(FirebaseAnalytics.Event.VIEW_ITEM, bundle) + } + LibsBuilder() + .withAutoDetect(true) + .withFields(R.string::class.java.fields) + .withCheckCachedDetection(true) + .withSortEnabled(true) + .withAboutVersionShown(true) + .withAboutVersionShownCode(true) + .withAboutVersionShownName(true) + .withShowLoadingProgress(true) + .withActivityTitle(getString(R.string.app_name)) + .start(requireContext()) + true + } + ).let { deferreds.add(it) } + setupPreferenceAsync( + "tos_privacy", + Ionicons.Icon.ion_android_cloud_done, + onClickListener = { + Intent( + requireContext(), + PrivacyTermsActivity::class.java + ).run { startActivity(this) } + true + } + ).let { deferreds.add(it) } + setupPreferenceAsync( + Preferences.BREAKFAST_TIME, + Ionicons.Icon.ion_coffee, + onInitialized = ::setupTimePickerDialog, + onInitializedArgs = setOf( + "title" to getText(R.string.breakfast_pref_title), + "summary" to getText(R.string.breakfast_pref_summ) + ), + dispatcher = Dispatchers.Main + ).let { deferreds.add(it) } + setupPreferenceAsync( + Preferences.LUNCH_TIME, + Ionicons.Icon.ion_android_restaurant, + onInitialized = ::setupTimePickerDialog, + onInitializedArgs = setOf( + "title" to getText(R.string.lunch_pref_title), + "summary" to getText(R.string.lunch_pref_summ) + ), + dispatcher = Dispatchers.Main + ).let { deferreds.add(it) } + setupPreferenceAsync( + Preferences.DINNER_TIME, + Ionicons.Icon.ion_ios_moon_outline, + onInitialized = ::setupTimePickerDialog, + onInitializedArgs = setOf( + "title" to getText(R.string.dinner_pref_title), + "summary" to getText(R.string.dinner_pref_summ) + ), + dispatcher = Dispatchers.Main ).let { deferreds.add(it) } deferreds.awaitAll() + arePreferencesInitialized.set(true) } } } @@ -145,7 +292,9 @@ class SettingsLoader( icon: IIcon? = null, onClickListener: ((Preference) -> Boolean)? = null, onChangeListener: Preference.OnPreferenceChangeListener? = null, - action: ((Preference) -> Unit)? = null + onInitialized: (suspend (Preference, Collection>?) -> Unit)? = null, + dispatcher: CoroutineDispatcher = Dispatchers.Default, + onInitializedArgs: Collection>? = null ): Deferred = lifecycleOwner.lifecycleScope.async { view.findPreference(name)?.let { icon.notNull { icon -> @@ -157,9 +306,39 @@ class SettingsLoader( onChangeListener.notNull { listener -> it.onPreferenceChangeListener = listener } - action.notNull { action -> - action(it) + if (onInitialized != null) + withContext(dispatcher) { + onInitialized(it, onInitializedArgs) + } + } + } + + private suspend fun setupTimePickerDialog( + preference: Preference, + args: Collection>? + ) { + if (args == null) + return + var title: CharSequence? = null + var summary: CharSequence? = null + for (arg in args) + when (arg.first) { + "title" -> title = arg.second as CharSequence + "summary" -> summary = arg.second as CharSequence } + if (title == null || summary == null) + return + if (!::emojiCompat.isInitialized) + emojiCompat = emojiLoader.await() + preference as TimePickerPreference + try { + preference.title = emojiCompat.process(title) + preference.summaryText = emojiCompat.process(summary) + } catch (_: IllegalStateException) { + preference.title = title + preference.summaryText = summary + } finally { + preference.updateSummary() } } @@ -184,7 +363,7 @@ class SettingsLoader( MaterialDialog(view.requireContext()).show { title(R.string.no_app) message( - text = view.getString( + text = view.context.getString( R.string.no_app_long, onErrString ) @@ -196,45 +375,3 @@ class SettingsLoader( } } } - -/* -setupPreference("share", Ionicons.Icon.ion_android_share, { - with(FirebaseAnalytics.getInstance(requireContext())) { - logEvent(FirebaseAnalytics.Event.SHARE, null) - } - with(Intent.createChooser(Intent().apply { - action = Intent.ACTION_SEND - putExtra( - Intent.EXTRA_TEXT, - getText(R.string.share_text) - ) - putExtra( - Intent.EXTRA_TITLE, - getText(R.string.share_title) - ) - ClipData.Item( - getUriFromRes( - requireContext(), - R.drawable.handwashing_app_logo - ) - ) - clipData = ClipData( - ClipDescription( - getString(R.string.share_label), - arrayOf("image/*") - ), - ClipData.Item( - getUriFromRes( - requireContext(), - R.drawable.handwashing_app_logo - ) - ) - ) - flags = Intent.FLAG_GRANT_READ_URI_PERMISSION - type = "text/plain" - }, null)) { - startActivity(this) - } - true - }) - */ \ No newline at end of file diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/data/SettingsLoaderBak.kt b/app/src/main/java/com/javinator9889/handwashingreminder/data/SettingsLoaderBak.kt deleted file mode 100644 index 8fe6614..0000000 --- a/app/src/main/java/com/javinator9889/handwashingreminder/data/SettingsLoaderBak.kt +++ /dev/null @@ -1,290 +0,0 @@ -/* - * Copyright © 2020 - present | Handwashing reminder by Javinator9889 - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see https://www.gnu.org/licenses/. - * - * Created by Javinator9889 on 11/06/20 - Handwashing reminder. - */ -package com.javinator9889.handwashingreminder.data - -import android.content.Intent -import android.net.Uri -import android.os.Bundle -import androidx.annotation.StringRes -import androidx.emoji.text.EmojiCompat -import androidx.preference.ListPreference -import androidx.preference.Preference -import androidx.preference.SwitchPreference -import com.afollestad.materialdialogs.MaterialDialog -import com.google.firebase.analytics.FirebaseAnalytics -import com.javinator9889.handwashingreminder.R -import com.javinator9889.handwashingreminder.activities.PrivacyTermsActivity -import com.javinator9889.handwashingreminder.activities.views.fragments.settings.SettingsView -import com.javinator9889.handwashingreminder.activities.views.fragments.settings.TimePickerPreference -import com.javinator9889.handwashingreminder.emoji.EmojiLoader -import com.javinator9889.handwashingreminder.jobs.alarms.Alarms -import com.javinator9889.handwashingreminder.utils.* -import com.mikepenz.aboutlibraries.LibsBuilder -import com.mikepenz.iconics.IconicsDrawable -import com.mikepenz.iconics.typeface.IIcon -import com.mikepenz.iconics.typeface.library.ionicons.Ionicons -import com.mikepenz.iconics.utils.sizeDp -import kotlinx.coroutines.Deferred -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.async -import kotlinx.coroutines.awaitAll -import java.lang.ref.WeakReference - - -class SettingsLoaderBak(private val view: SettingsView) { - private val emojiLoader = EmojiLoader.get(view.requireContext()) - private lateinit var emojiCompat: EmojiCompat - - suspend fun loadViews() { - with(view) { - val breakfast = findPreference( - Preferences.BREAKFAST_TIME - ) - val lunch = findPreference( - Preferences.LUNCH_TIME - ) - val dinner = findPreference( - Preferences.DINNER_TIME - ) - val firebaseAnalytics = findPreference( - Preferences.ANALYTICS_ENABLED - ) - val firebasePerformance = findPreference( - Preferences.PERFORMANCE_ENABLED - ) - val ads = findPreference(Preferences.ADS_ENABLED) - val donations = - findPreference(Preferences.DONATIONS) - val translations = findPreference("translate") - val suggestions = findPreference("send_suggestions") - val libraries = findPreference("opensource_libs") - val privacyAndTerms = findPreference("tos_privacy") - val deferreds = mutableSetOf>() - deferreds.add( - async(Dispatchers.Main) { - firebaseAnalytics?.let { - it.onPreferenceChangeListener = this@SettingsView - it.icon = icon(Ionicons.Icon.ion_arrow_graph_up_right) - firebaseAnalyticsPreference = WeakReference(it) - } - }) - deferreds.add( - async(Dispatchers.Main) { - firebasePerformance?.let { - it.onPreferenceChangeListener = this@with - it.icon = icon(Ionicons.Icon.ion_speedometer) - firebasePerformancePreference = WeakReference(it) - } - }) - deferreds.add( - async(Dispatchers.Main) { - ads?.let { - it.onPreferenceChangeListener = this@SettingsView - it.icon = icon(Ionicons.Icon.ion_ios_barcode_outline) - adsPreference = WeakReference(it) - } - }) - deferreds.add( - async(Dispatchers.Main) { - donations?.let { - it.onPreferenceChangeListener = this@SettingsView - it.entryValues = if (isDebuggable()) - resources.getTextArray(R.array.in_app_donations_debug) - else - resources.getTextArray(R.array.in_app_donations) - it.icon = icon(Ionicons.Icon.ion_card) - billingService.addOnPurchaseFinishedListener(this@SettingsView) - donationsPreference = WeakReference(it) - } - }) - deferreds.add( - async(Dispatchers.Main) { - translations?.let { - it.icon = icon(Ionicons.Icon.ion_chatbox_working) - it.setOnPreferenceClickListener { - openWebsite(TRANSLATE_URL, R.string.browser_err) - true - } - } - }) - deferreds.add( - async(Dispatchers.Main) { - suggestions?.let { - it.setOnPreferenceClickListener { - with(Intent(Intent.ACTION_SENDTO)) { - type = "*/*" - data = Uri.parse("mailto:") - putExtra(Intent.EXTRA_EMAIL, arrayOf(Email.TO)) - putExtra(Intent.EXTRA_SUBJECT, Email.SUBJECT) - putExtra(Intent.EXTRA_TEXT, getDeviceInfo()) - if (resolveActivity(requireContext().packageManager) - != null - ) { - startActivity(this) - } else { - MaterialDialog(requireContext()).show { - title(R.string.no_app) - message( - text = getString( - R.string.no_app_long, - getString(R.string.sending_email) - ) - ) - positiveButton(android.R.string.ok) - cancelable(true) - cancelOnTouchOutside(true) - } - } - } - true - } - it.icon = icon(Ionicons.Icon.ion_chatbubbles) - } - }) - deferreds.add( - async(Dispatchers.Main) { - libraries?.let { - it.setOnPreferenceClickListener { - val bundle = Bundle(1).apply { - putString("view", "libs") - } - with(FirebaseAnalytics.getInstance(requireContext())) { - logEvent( - FirebaseAnalytics.Event.VIEW_ITEM, - bundle - ) - } - LibsBuilder() - .withAutoDetect(true) - .withFields(R.string::class.java.fields) - .withCheckCachedDetection(true) - .withSortEnabled(true) - .withAboutVersionShown(true) - .withAboutVersionShownCode(true) - .withAboutVersionShownName(true) - .withShowLoadingProgress(true) - .withActivityTitle(getString(R.string.app_name)) - .start(requireContext()) - true - } - it.icon = icon(Ionicons.Icon.ion_code) - } - }) - deferreds.add( - async(Dispatchers.Main) { - privacyAndTerms?.let { - it.setOnPreferenceClickListener { - Intent( - requireContext(), - PrivacyTermsActivity::class.java - ).run { - startActivity(this) - } - true - } - it.icon = icon(Ionicons.Icon.ion_android_cloud_done) - } - }) - deferreds.add( - async(Dispatchers.Main) { - emojiCompat = emojiLoader.await() - breakfast?.let { - it.icon = icon(Ionicons.Icon.ion_coffee) - it.alarm = Alarms.BREAKFAST_ALARM - try { - it.title = - emojiCompat.process(getText(R.string.breakfast_pref_title)) - it.summaryText = - emojiCompat.process(getText(R.string.breakfast_pref_summ)) - } catch (_: IllegalStateException) { - it.title = getText(R.string.breakfast_pref_title) - it.summaryText = - getText(R.string.breakfast_pref_summ) - } finally { - it.updateSummary() - } - } - lunch?.let { - it.icon = icon(Ionicons.Icon.ion_android_restaurant) - it.alarm = Alarms.LUNCH_ALARM - try { - it.title = - emojiCompat.process(getText(R.string.lunch_pref_title)) - it.summaryText = - emojiCompat.process(getText(R.string.lunch_pref_summ)) - } catch (_: IllegalStateException) { - it.title = getText(R.string.lunch_pref_title) - it.summaryText = getText(R.string.lunch_pref_summ) - } finally { - it.updateSummary() - } - } - dinner?.let { - it.icon = icon(Ionicons.Icon.ion_ios_moon_outline) - it.alarm = Alarms.DINNER_ALARM - try { - it.title = - emojiCompat.process(getText(R.string.dinner_pref_title)) - it.summaryText = - emojiCompat.process(getText(R.string.dinner_pref_summ)) - } catch (_: IllegalStateException) { - it.title = getText(R.string.dinner_pref_title) - it.summaryText = getText(R.string.dinner_pref_summ) - } finally { - it.updateSummary() - } - } - }) - deferreds.awaitAll() - } - } - - private fun icon(icon: IIcon): IconicsDrawable = - IconicsDrawable(view.requireContext(), icon).apply { sizeDp = 20 } - - private fun openWebsite(url: String, @StringRes onErrString: Int) = - openWebsite(url, view.getString(onErrString)) - - private fun openWebsite(url: String, onErrString: String) { - if (view.context == null) - return - val website = Uri.parse(url) - val bundle = Bundle(1).apply { putString("url", url) } - with(FirebaseAnalytics.getInstance(view.requireContext())) { - logEvent(FirebaseAnalytics.Event.VIEW_ITEM, bundle) - } - with(Intent(Intent.ACTION_VIEW, website)) { - if (resolveActivity(view.requireContext().packageManager) != null) - view.startActivity(this) - else - MaterialDialog(view.requireContext()).show { - title(R.string.no_app) - message( - text = view.getString( - R.string.no_app_long, - onErrString - ) - ) - positiveButton(android.R.string.ok) - cancelable(true) - cancelOnTouchOutside(true) - } - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/utils/Android.kt b/app/src/main/java/com/javinator9889/handwashingreminder/utils/Android.kt index 272322f..5a0f2a4 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/utils/Android.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/utils/Android.kt @@ -99,6 +99,9 @@ fun getDeviceInfo(): String = with(StringBuilder()) { toString() } +fun getDeviceInfoHTML(): String = + getDeviceInfo().replace(Regex("(\r\n|\n)"), "
") + fun getUriFromRes(context: Context, @AnyRes resId: Int): Uri = with(context.resources) { Uri.Builder() From c0ed4447c12046c2deb9692a2809281dd2b91491 Mon Sep 17 00:00:00 2001 From: Javinator9889 Date: Tue, 16 Jun 2020 11:45:57 +0200 Subject: [PATCH 46/95] NewsFragment has the behaviour for loading when visible In addition, WashingHandsFragment does not have to do anything for reacting whether the view is visible or not as the Fragments are created on-demand when the user slides the SliderPage --- .../views/fragments/news/NewsFragment.kt | 14 ++++++++------ .../fragments/washinghands/WashingHandsFragment.kt | 4 +--- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/news/NewsFragment.kt b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/news/NewsFragment.kt index 113586e..369c84d 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/news/NewsFragment.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/news/NewsFragment.kt @@ -54,6 +54,7 @@ class NewsFragment : BaseFragmentView(), LayoutVisibilityChange { override val layoutId: Int = R.layout.loading_recycler_view private lateinit var fastAdapter: FastAdapter private lateinit var footerAdapter: GenericItemAdapter + private var viewCreated = false private val newsAdapter = ItemAdapter() private val newsViewModel: NewsViewModel by viewModels() private val activeItems = mutableSetOf() @@ -118,11 +119,7 @@ class NewsFragment : BaseFragmentView(), LayoutVisibilityChange { } fastAdapter.addEventHooks(listOf(NewsClickHook(), ShareClickHook())) fastAdapter.withSavedInstanceState(savedInstanceState) - if (savedInstanceState == null) { - lifecycleScope.launch { - newsViewModel.populateData(language = UserProperties.language) - } - } + viewCreated = savedInstanceState == null } override fun onSaveInstanceState(outState: Bundle) { @@ -164,7 +161,12 @@ class NewsFragment : BaseFragmentView(), LayoutVisibilityChange { } override fun onVisibilityChanged(visibility: Int) { - TODO("Not yet implemented") + if (visibility == View.VISIBLE && viewCreated) { + lifecycleScope.launch { + newsViewModel.populateData(language = UserProperties.language) + } + viewCreated = false + } } private inner class ShareClickHook : ClickEventHook() { diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/washinghands/WashingHandsFragment.kt b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/washinghands/WashingHandsFragment.kt index 9e3912e..f02bd10 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/washinghands/WashingHandsFragment.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/washinghands/WashingHandsFragment.kt @@ -93,7 +93,5 @@ class WashingHandsFragment : BaseFragmentView(), LayoutVisibilityChange { } } - override fun onVisibilityChanged(visibility: Int) { - TODO("Not yet implemented") - } + override fun onVisibilityChanged(visibility: Int) {} } \ No newline at end of file From 55bdbf7e65cf8d03ed507a5c91ec7a42144d5ca4 Mon Sep 17 00:00:00 2001 From: Javinator9889 Date: Tue, 16 Jun 2020 13:51:18 +0200 Subject: [PATCH 47/95] Updated ProGuard rules for reducing application size (issue #14) --- app/proguard-rules.pro | 35 +++++++++++++++++++++++++---------- 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index 3c68aca..f7b5d2f 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -14,7 +14,11 @@ # Uncomment this to preserve the line number information for # debugging stack traces. --keepattributes *Annotation* +# -keepattributes *Annotation* +# -keepattributes SourceFile,LineNumberTable +-printmapping out.map + +-renamesourcefileattribute SourceFile -keepattributes SourceFile,LineNumberTable # If you keep the line number information, uncomment this to @@ -22,6 +26,11 @@ #-renamesourcefileattribute SourceFile -optimizationpasses 4 +-repackageclasses '' +-allowaccessmodification +-android +-dontpreverify +-optimizations !code/simplification/arithmetic # https://github.com/mikepenz/Android-Iconics#proguard -keep class .R @@ -29,6 +38,21 @@ ; } +-assumenosideeffects class android.util.Log { + public static boolean isLoggable(java.lang.String, int); + public static int v(...); + public static int i(...); + public static int w(...); + public static int d(...); + public static int e(...); +} + +-assumenosideeffects class timber.log.Timber { + public static void d(...); + public static void i(...); + public static void v(...); +} + -keep class com.javinator9889.handwashingreminder.ads.AdLoaderImpl { com.javinator9889.handwashingreminder.ads.AdLoaderImpl$Provider Provider; } @@ -70,19 +94,10 @@ -keep class com.bumptech.glide.load.data.ParcelFileDescriptorRewinder$InternalRewinder { *** rewind(); } --keep class com.bumptech.glide.integration.okhttp3.OkHttpGlideModule - -dontwarn com.bumptech.glide.load.resource.bitmap.VideoDecoder # Klaxon -keep public class kotlin.reflect.jvm.internal.impl.** { public *; } --keep class com.beust.klaxon.** { *; } --keep interface com.beust.klaxon.** { *; } -keep class kotlin.Metadata { *; } #data models -keep class com.javinator9889.handwashingreminder.collections.** { *;} - --keepclassmembers enum * { - public static **[] values(); - public static ** valueOf(java.lang.String); -} From 6a06887aad0b8fb921f4d6afcabab05542af1429 Mon Sep 17 00:00:00 2001 From: Javinator9889 Date: Tue, 16 Jun 2020 14:35:00 +0200 Subject: [PATCH 48/95] Updated activities for async loading when possible (issue #11) --- app/build.gradle | 8 +- .../activities/MainActivity.kt | 117 ++++-------- .../data/MainActivityDataHandler.kt | 91 ++++++++- .../data/SettingsLoader.kt | 2 +- appintro/src/main/AndroidManifest.xml | 3 + .../appintro/IntroActivity.kt | 177 ++++++++++-------- .../src/main/res/layout/time_card_view.xml | 10 +- 7 files changed, 233 insertions(+), 175 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 591cac2..50aae92 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -42,7 +42,7 @@ android { applicationId "com.javinator9889.handwashingreminder" minSdkVersion 17 targetSdkVersion 29 - versionCode 122 + versionCode 125 versionName "1.2.0-${gitCommitHash}" multiDexEnabled true resConfigs "en", "es" @@ -167,7 +167,7 @@ dependencies { // https://developer.android.com/kotlin/ktx#viewmodel implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0' // https://developer.android.com/kotlin/ktx#fragment - implementation 'androidx.fragment:fragment-ktx:1.2.4' + implementation 'androidx.fragment:fragment-ktx:1.2.5' // https://developer.android.com/kotlin/ktx#livedata implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.2.0' // https://github.com/JakeWharton/timber @@ -183,8 +183,8 @@ dependencies { // https://github.com/afollestad/material-dialogs/ implementation 'com.afollestad.material-dialogs:core:3.3.0' // https://developer.android.com/google/play/billing/billing_library_overview - implementation 'com.android.billingclient:billing:2.2.1' - implementation 'com.android.billingclient:billing-ktx:2.2.1' + implementation 'com.android.billingclient:billing:3.0.0' + implementation 'com.android.billingclient:billing-ktx:3.0.0' // https://github.com/cbeust/klaxon implementation 'com.beust:klaxon:5.2' // https://github.com/SufficientlySecure/html-textview diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/activities/MainActivity.kt b/app/src/main/java/com/javinator9889/handwashingreminder/activities/MainActivity.kt index b5a1b8f..6a18693 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/activities/MainActivity.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/activities/MainActivity.kt @@ -21,12 +21,11 @@ package com.javinator9889.handwashingreminder.activities import android.os.Bundle import android.view.MenuItem import androidx.annotation.IdRes -import androidx.core.content.edit import androidx.fragment.app.FragmentTransaction +import androidx.fragment.app.commit import androidx.lifecycle.lifecycleScope import androidx.lifecycle.whenCreated import androidx.lifecycle.whenStarted -import androidx.preference.PreferenceManager import com.google.android.material.bottomnavigation.BottomNavigationView import com.google.firebase.analytics.FirebaseAnalytics import com.google.firebase.ktx.Firebase @@ -38,51 +37,70 @@ import com.javinator9889.handwashingreminder.activities.views.fragments.washingh import com.javinator9889.handwashingreminder.custom.libraries.AppRate import com.javinator9889.handwashingreminder.data.MainActivityDataHandler import com.javinator9889.handwashingreminder.firebase.Auth -import com.javinator9889.handwashingreminder.utils.Preferences -import com.javinator9889.handwashingreminder.utils.isDebuggable -import com.javinator9889.handwashingreminder.utils.notNull import kotlinx.android.synthetic.main.activity_main.* import kotlinx.android.synthetic.main.how_to_wash_hands_layout.* -import kotlinx.coroutines.* +import kotlinx.coroutines.Deferred +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import timber.log.Timber import uk.co.deanwild.materialshowcaseview.MaterialShowcaseSequence -import uk.co.deanwild.materialshowcaseview.ShowcaseConfig class MainActivity : ActionBarBase(), BottomNavigationView.OnNavigationItemSelectedListener { override val layoutId: Int = R.layout.activity_main private val dataHandler = MainActivityDataHandler() + private lateinit var deferredRating: Deferred + private lateinit var deferredShowcase: Deferred init { lifecycleScope.launch { whenCreated { - val deferreds = mutableSetOf>() with(Firebase.remoteConfig) { fetchAndActivate() } - deferreds.add(async { - dataHandler.setMenuIcons(menu, this@MainActivity) - }) + launch { dataHandler.setMenuIcons(menu, this@MainActivity) } menu.setOnNavigationItemSelectedListener(this@MainActivity) - deferreds.add(async { loadTutorial() }) - deferreds.add(async { suggestRating() }) - deferreds.awaitAll() + deferredShowcase = dataHandler.asyncLoadShowcase( + activity = this@MainActivity, + lifecycleOwner = this@MainActivity + ) + deferredRating = dataHandler.asyncSuggestRating( + activity = this@MainActivity, + lifecycleOwner = this@MainActivity + ) } whenStarted { with(FirebaseAnalytics.getInstance(this@MainActivity)) { setCurrentScreen(this@MainActivity, "Main view", null) } + deferredShowcase.await()?.let { + withContext(Dispatchers.Main) { + it.start() + } + } + deferredRating.await().run { + withContext(Dispatchers.Main) { + init() + } + } } } } - override fun onPostCreate(savedInstanceState: Bundle?) { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + if (savedInstanceState == null) + dataHandler.loadFragmentView(supportFragmentManager) + } + + /*override fun onPostCreate(savedInstanceState: Bundle?) { super.onPostCreate(savedInstanceState) savedInstanceState.notNull { Timber.d("Activity recreated") } if (savedInstanceState == null) dataHandler.loadFragmentView(supportFragmentManager) - } + }*/ override fun onDestroy() { dataHandler.clear() @@ -166,7 +184,7 @@ class MainActivity : ActionBarBase(), Timber.d("$id - ${dataHandler.activeFragmentId} | ${id == dataHandler.activeFragmentId}") if (id == dataHandler.activeFragmentId) return - with(supportFragmentManager.beginTransaction()) { + supportFragmentManager.commit { show(dataHandler[id]) dataHandler.onShow(id) Timber.d("Showing fragment: ${dataHandler[id]}") @@ -176,71 +194,6 @@ class MainActivity : ActionBarBase(), dataHandler.activeFragmentId = id setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE) disallowAddToBackStack() - }.commit() - } - - private suspend fun loadTutorial() { - val preferences = - with(PreferenceManager.getDefaultSharedPreferences(this)) { - if (getBoolean(Preferences.INITIAL_TUTORIAL_DONE, false)) - return - else this - } - val config = ShowcaseConfig() - config.delay = 500L - with(MaterialShowcaseSequence(this)) { - setConfig(config) - val dismissText = getString(R.string.got_it) - val diseasesText = getString(R.string.diseases_intro) - val handwashingText = getString(R.string.handwashing_intro) - val newsText = getString(R.string.news_intro) - val settingsText = getString(R.string.settings_intro) - addSequenceItem( - findViewById(R.id.diseases), - diseasesText, - dismissText - ) - addSequenceItem( - findViewById(R.id.handwashing), - handwashingText, - dismissText - ) - addSequenceItem( - findViewById(R.id.news), - newsText, - dismissText - ) - addSequenceItem( - findViewById(R.id.settings), - settingsText, - dismissText - ) - var itemCount = 0 - setOnItemDismissedListener { _, _ -> - if (itemCount++ == 3) - preferences.edit { - putBoolean(Preferences.INITIAL_TUTORIAL_DONE, true) - } - } - withContext(Dispatchers.Main) { - start() - } - } - } - - private suspend fun suggestRating() { - withContext(Dispatchers.Main) { - with(AppRate(this@MainActivity)) { - if (!isDebuggable()) { - setMinDaysUntilPrompt(2L) - setMinLaunchesUntilPrompt(5) - } - dialogTitle = R.string.rate_text_title - dialogMessage = R.string.rate_app_message - positiveButtonText = R.string.rate_text - negativeButtonText = R.string.rate_do_not_show - init() - } } } } diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/data/MainActivityDataHandler.kt b/app/src/main/java/com/javinator9889/handwashingreminder/data/MainActivityDataHandler.kt index 188fcf8..b28c0d0 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/data/MainActivityDataHandler.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/data/MainActivityDataHandler.kt @@ -18,17 +18,23 @@ */ package com.javinator9889.handwashingreminder.data +import android.app.Activity import android.content.Context import android.os.Bundle import android.util.SparseArray import android.view.MenuItem import android.view.View import androidx.annotation.IdRes +import androidx.core.content.edit import androidx.core.util.forEach import androidx.core.util.set import androidx.core.view.forEach import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentManager +import androidx.fragment.app.commit +import androidx.lifecycle.LifecycleOwner +import androidx.lifecycle.lifecycleScope +import androidx.preference.PreferenceManager import com.google.android.material.bottomnavigation.BottomNavigationView import com.javinator9889.handwashingreminder.R import com.javinator9889.handwashingreminder.activities.base.LayoutVisibilityChange @@ -36,12 +42,19 @@ import com.javinator9889.handwashingreminder.activities.views.fragments.diseases import com.javinator9889.handwashingreminder.activities.views.fragments.news.NewsFragment import com.javinator9889.handwashingreminder.activities.views.fragments.settings.SettingsView import com.javinator9889.handwashingreminder.activities.views.fragments.washinghands.WashingHandsFragment +import com.javinator9889.handwashingreminder.custom.libraries.AppRate +import com.javinator9889.handwashingreminder.utils.Preferences +import com.javinator9889.handwashingreminder.utils.isDebuggable import com.mikepenz.iconics.IconicsDrawable import com.mikepenz.iconics.typeface.library.googlematerial.GoogleMaterial import com.mikepenz.iconics.typeface.library.ionicons.Ionicons +import kotlinx.coroutines.Deferred import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.async import kotlinx.coroutines.withContext import timber.log.Timber +import uk.co.deanwild.materialshowcaseview.MaterialShowcaseSequence +import uk.co.deanwild.materialshowcaseview.ShowcaseConfig import java.lang.ref.WeakReference internal const val ARG_CURRENT_ITEM = "bundle:args:current_item" @@ -111,16 +124,12 @@ class MainActivityDataHandler(@IdRes var activeFragmentId: Int = R.id.diseases) } fun loadFragmentView(fragmentManager: FragmentManager) { - with(fragmentManager.beginTransaction()) { + fragmentManager.commit { IDS.forEach { id -> - get(id).also { - add(R.id.mainContent, it) - hide(it) - } + get(id).also { add(R.id.mainContent, it); hide(it) } } show(activeFragment) onShow(activeFragmentId) - commit() } } @@ -132,6 +141,76 @@ class MainActivityDataHandler(@IdRes var activeFragmentId: Int = R.id.diseases) fun onHide(@IdRes id: Int) = (this[id] as LayoutVisibilityChange).onVisibilityChanged(View.INVISIBLE) + fun asyncLoadShowcase( + activity: Activity, + lifecycleOwner: LifecycleOwner + ): Deferred = + lifecycleOwner.lifecycleScope.async { + val preferences = + with(PreferenceManager.getDefaultSharedPreferences(activity)) { + if (getBoolean(Preferences.INITIAL_TUTORIAL_DONE, false)) + return@async null + else this + } + + val config = ShowcaseConfig() + config.delay = 500L + with(MaterialShowcaseSequence(activity)) { + setConfig(config) + val dismissText = activity.getString(R.string.got_it) + val diseasesText = activity.getString(R.string.diseases_intro) + val handwashingText = + activity.getString(R.string.handwashing_intro) + val newsText = activity.getString(R.string.news_intro) + val settingsText = activity.getString(R.string.settings_intro) + addSequenceItem( + activity.findViewById(R.id.diseases), + diseasesText, + dismissText + ) + addSequenceItem( + activity.findViewById(R.id.handwashing), + handwashingText, + dismissText + ) + addSequenceItem( + activity.findViewById(R.id.news), + newsText, + dismissText + ) + addSequenceItem( + activity.findViewById(R.id.settings), + settingsText, + dismissText + ) + var itemCount = 0 + setOnItemDismissedListener { _, _ -> + if (itemCount++ == 3) + preferences.edit { + putBoolean(Preferences.INITIAL_TUTORIAL_DONE, true) + } + } + this + } + } + + fun asyncSuggestRating( + activity: Activity, + lifecycleOwner: LifecycleOwner + ): Deferred = lifecycleOwner.lifecycleScope.async { + with(AppRate(activity)) { + if (!isDebuggable()) { + setMinDaysUntilPrompt(2L) + setMinLaunchesUntilPrompt(5) + } + dialogTitle = R.string.rate_text_title + dialogMessage = R.string.rate_app_message + positiveButtonText = R.string.rate_text + negativeButtonText = R.string.rate_do_not_show + this + } + } + private fun createFragmentForId(@IdRes id: Int): Fragment { if (id !in IDS) throw IllegalArgumentException("id not in IDs") diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/data/SettingsLoader.kt b/app/src/main/java/com/javinator9889/handwashingreminder/data/SettingsLoader.kt index bb2e7a8..29a74ec 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/data/SettingsLoader.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/data/SettingsLoader.kt @@ -145,7 +145,7 @@ class SettingsLoader( ).let { deferreds.add(it) } setupPreferenceAsync( Preferences.PERFORMANCE_ENABLED, - Ionicons.Icon.ion_ios_speedometer, + Ionicons.Icon.ion_ios_speedometer_outline, onChangeListener = this@with, onInitialized = { it, _ -> firebasePerformancePreference = WeakReference(it) diff --git a/appintro/src/main/AndroidManifest.xml b/appintro/src/main/AndroidManifest.xml index e08107a..fe7cc77 100644 --- a/appintro/src/main/AndroidManifest.xml +++ b/appintro/src/main/AndroidManifest.xml @@ -12,6 +12,9 @@ + + + diff --git a/appintro/src/main/java/com/javinator9889/handwashingreminder/appintro/IntroActivity.kt b/appintro/src/main/java/com/javinator9889/handwashingreminder/appintro/IntroActivity.kt index 002178f..50eaf6d 100644 --- a/appintro/src/main/java/com/javinator9889/handwashingreminder/appintro/IntroActivity.kt +++ b/appintro/src/main/java/com/javinator9889/handwashingreminder/appintro/IntroActivity.kt @@ -19,6 +19,7 @@ package com.javinator9889.handwashingreminder.appintro import android.Manifest +import android.annotation.SuppressLint import android.content.Context import android.content.Intent import android.content.pm.PackageManager.PERMISSION_GRANTED @@ -30,6 +31,7 @@ import androidx.annotation.Keep import androidx.core.content.edit import androidx.core.util.set import androidx.fragment.app.Fragment +import androidx.lifecycle.lifecycleScope import androidx.preference.PreferenceManager import com.github.paolorotolo.appintro.AppIntro2 import com.github.paolorotolo.appintro.AppIntroViewPager @@ -52,6 +54,7 @@ import com.javinator9889.handwashingreminder.application.HandwashingApplication import com.javinator9889.handwashingreminder.jobs.alarms.AlarmHandler import com.javinator9889.handwashingreminder.utils.* import kotlinx.android.synthetic.main.animated_intro.* +import kotlinx.coroutines.* import timber.log.Timber import com.javinator9889.handwashingreminder.appintro.R as RIntro @@ -72,6 +75,7 @@ class IntroActivity : AppIntro2(), SplitCompat.installActivity(this) } + @SuppressLint("MissingPermission") override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -164,87 +168,111 @@ class IntroActivity : AppIntro2(), } } + @SuppressLint("MissingPermission") override fun onDonePressed(currentFragment: Fragment?) { super.onDonePressed(currentFragment) - val app = HandwashingApplication.instance - val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this) - sharedPreferences.edit(commit = true) { - timeConfigSlide.itemAdapter.adapterItems.forEach { item -> - val time = "${item.hours}:${item.minutes}" - when (item.id) { - TimeConfig.BREAKFAST_ID -> - putString(Preferences.BREAKFAST_TIME, time) - TimeConfig.LUNCH_ID -> - putString(Preferences.LUNCH_TIME, time) - TimeConfig.DINNER_ID -> - putString(Preferences.DINNER_TIME, time) + lifecycleScope.launch { + val deferreds = mutableSetOf>() + val app = HandwashingApplication.instance + val sharedPreferences = + PreferenceManager.getDefaultSharedPreferences(this@IntroActivity) + sharedPreferences.edit { + timeConfigSlide.itemAdapter.adapterItems.forEach { item -> + val time = "${item.hours}:${item.minutes}" + when (item.id) { + TimeConfig.BREAKFAST_ID -> + putString(Preferences.BREAKFAST_TIME, time) + TimeConfig.LUNCH_ID -> + putString(Preferences.LUNCH_TIME, time) + TimeConfig.DINNER_ID -> + putString(Preferences.DINNER_TIME, time) + } + } + putBoolean( + Preferences.ANALYTICS_ENABLED, + policySlide.firebaseAnalytics.isChecked + ) + putBoolean( + Preferences.PERFORMANCE_ENABLED, + policySlide.firebasePerformance.isChecked + ) + putBoolean( + Preferences.ADS_ENABLED, + sharedPreferences.getBoolean(Preferences.ADS_ENABLED, true) + ) + if (!isAtLeast(AndroidVersion.Q)) + activityRecognitionPermissionGranted = true + putBoolean( + Preferences.ACTIVITY_TRACKING_ENABLED, + activityRecognitionPermissionGranted + ) + if (activityRecognitionPermissionGranted) { + putStringSet( + Preferences.ACTIVITIES_ENABLED, + Preferences.DEFAULT_ACTIVITY_SET + ) } + putBoolean(Preferences.APP_INIT_KEY, true) } - putBoolean( - Preferences.ANALYTICS_ENABLED, - policySlide.firebaseAnalytics.isChecked - ) - putBoolean( - Preferences.PERFORMANCE_ENABLED, - policySlide.firebasePerformance.isChecked - ) - putBoolean( - Preferences.ADS_ENABLED, - sharedPreferences.getBoolean(Preferences.ADS_ENABLED, true) - ) - if (!isAtLeast(AndroidVersion.Q)) - activityRecognitionPermissionGranted = true - putBoolean( - Preferences.ACTIVITY_TRACKING_ENABLED, - activityRecognitionPermissionGranted - ) - if (activityRecognitionPermissionGranted) { - putStringSet( - Preferences.ACTIVITIES_ENABLED, - Preferences.DEFAULT_ACTIVITY_SET + val splitInstallManager = + SplitInstallManagerFactory.create(this@IntroActivity) + splitInstallManager.deferredUninstall(listOf(AppIntro.MODULE_NAME)) + async { + if (activityRecognitionPermissionGranted) + app.activityHandler.startTrackingActivity() + else + app.activityHandler.disableActivityTracker() + with(AlarmHandler(this@IntroActivity)) { + scheduleAllAlarms() + } + Timber.d("Finished starting activity recognition and scheduling alarms") + }.also { deferreds.add(it) } + async(context = Dispatchers.IO) { + cacheDir.run { deleteRecursively() } + Timber.d("Finished cleaning cache") + }.also { deferreds.add(it) } + val firebaseAnalytics = + FirebaseAnalytics.getInstance(this@IntroActivity) + with(Bundle(2)) { + putBoolean( + "analytics_enabled", + policySlide.firebaseAnalytics.isChecked + ) + putBoolean( + "performance_enabled", + policySlide.firebasePerformance.isChecked + ) + firebaseAnalytics.logEvent( + FirebaseAnalytics.Event.SELECT_ITEM, + this ) } - putBoolean(Preferences.APP_INIT_KEY, true) - } - val splitInstallManager = SplitInstallManagerFactory.create(this) - splitInstallManager.deferredUninstall(listOf(AppIntro.MODULE_NAME)) - if (activityRecognitionPermissionGranted) - app.activityHandler.startTrackingActivity() - else - app.activityHandler.disableActivityTracker() - with(AlarmHandler(this)) { - scheduleAllAlarms() - } - cacheDir.run { deleteRecursively() } - val firebaseAnalytics = FirebaseAnalytics.getInstance(this) - with(Bundle(2)) { - putBoolean( - "analytics_enabled", - policySlide.firebaseAnalytics.isChecked - ) - putBoolean( - "performance_enabled", - policySlide.firebasePerformance.isChecked - ) - firebaseAnalytics.logEvent( - FirebaseAnalytics.Event.SELECT_ITEM, - this - ) - } - firebaseAnalytics.logEvent( - FirebaseAnalytics.Event.TUTORIAL_COMPLETE, null - ) - if (!policySlide.firebaseAnalytics.isChecked) { - firebaseAnalytics.setCurrentScreen(this, null, null) - firebaseAnalytics.setAnalyticsCollectionEnabled(false) - } - with(FirebasePerformance.getInstance()) { - isPerformanceCollectionEnabled = - policySlide.firebasePerformance.isChecked + async(context = Dispatchers.IO) { + firebaseAnalytics.logEvent( + FirebaseAnalytics.Event.TUTORIAL_COMPLETE, null + ) + if (!policySlide.firebaseAnalytics.isChecked) { + firebaseAnalytics.setCurrentScreen( + this@IntroActivity, + null, + null + ) + firebaseAnalytics.setAnalyticsCollectionEnabled(false) + } + with(FirebasePerformance.getInstance()) { + isPerformanceCollectionEnabled = + policySlide.firebasePerformance.isChecked + } + Timber.d("Finished setting-up Firebase") + }.also { deferreds.add(it) } + deferreds.awaitAll() + Timber.d("All async processes finished - finishing Intro") + this@IntroActivity.finish() + Timber.d("Intro finished!") } val intent = Intent(this, MainActivity::class.java) + Timber.d("Starting MainActivity") startActivity(intent) - this.finish() } override fun onActivityResult( @@ -260,7 +288,7 @@ class IntroActivity : AppIntro2(), val position = data.getIntExtra("position", 0) val hours = data.getStringExtra("hours") val minutes = data.getStringExtra("minutes") - val titleText = when(id) { + val titleText = when (id) { TimeConfig.BREAKFAST_ID -> getString(RIntro.string.breakfast) TimeConfig.LUNCH_ID -> getString(RIntro.string.lunch) TimeConfig.DINNER_ID -> getString(RIntro.string.dinner) @@ -371,10 +399,9 @@ class IntroActivity : AppIntro2(), override fun onClick(v: View?) { when (v) { - nextButton -> { + nextButton -> if (onCanRequestNextPage()) changeSlide() - } } } diff --git a/appintro/src/main/res/layout/time_card_view.xml b/appintro/src/main/res/layout/time_card_view.xml index 04d6886..2ba32f4 100644 --- a/appintro/src/main/res/layout/time_card_view.xml +++ b/appintro/src/main/res/layout/time_card_view.xml @@ -67,7 +67,6 @@ android:layout_gravity="center" android:layout_margin="8dp" android:layout_weight="1" - android:contentDescription="TODO" android:layout_marginEnd="16dp" app:srcCompat="@drawable/ic_breakfast" /> @@ -92,6 +91,7 @@ app:iiv_size="16dp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toStartOf="@+id/hours" + app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" />
From b8c4013e45919915828b46337c39eaa440a35b32 Mon Sep 17 00:00:00 2001 From: Javinator9889 Date: Tue, 16 Jun 2020 15:54:27 +0200 Subject: [PATCH 49/95] Updated EmojiLoader load model for using Kotlin coroutines for async loading (issue #11) --- app/build.gradle | 2 +- app/proguard-rules.pro | 2 +- .../activities/LauncherActivity.kt | 2 +- .../views/viewmodels/WashingHandsModel.kt | 2 +- .../application/HandwashingApplication.kt | 5 ++- .../data/SettingsLoader.kt | 38 ++++++++--------- .../handwashingreminder/emoji/EmojiLoader.kt | 41 ++++++++++--------- .../gms/activity/ActivityReceiver.kt | 6 +-- .../jobs/alarms/AlarmReceiver.kt | 3 +- .../workers/ScheduledNotificationWorker.kt | 2 +- 10 files changed, 54 insertions(+), 49 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 50aae92..a191a8f 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -42,7 +42,7 @@ android { applicationId "com.javinator9889.handwashingreminder" minSdkVersion 17 targetSdkVersion 29 - versionCode 125 + versionCode 126 versionName "1.2.0-${gitCommitHash}" multiDexEnabled true resConfigs "en", "es" diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index f7b5d2f..1e9bc77 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -100,4 +100,4 @@ -keep class kotlin.Metadata { *; } #data models --keep class com.javinator9889.handwashingreminder.collections.** { *;} +#-keep class com.javinator9889.handwashingreminder.collections.** { *;} diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/activities/LauncherActivity.kt b/app/src/main/java/com/javinator9889/handwashingreminder/activities/LauncherActivity.kt index dbe97ba..4e882c1 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/activities/LauncherActivity.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/activities/LauncherActivity.kt @@ -143,7 +143,7 @@ class LauncherActivity : AppCompatActivity() { ) { super.onActivityResult(requestCode, resultCode, data) if (requestCode == DYNAMIC_FEATURE_INSTALL_RESULT_CODE) { - EmojiLoader.get(this) + EmojiLoader.loadAsync(this) if (sharedPreferences.getBoolean(ADS_ENABLED, true)) { when (resultCode) { Activity.RESULT_OK -> { diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/viewmodels/WashingHandsModel.kt b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/viewmodels/WashingHandsModel.kt index 79d2c19..15cf317 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/viewmodels/WashingHandsModel.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/viewmodels/WashingHandsModel.kt @@ -77,7 +77,7 @@ class WashingHandsModel( private suspend fun processStringArray(@ArrayRes array: Int): CharSequence = with(HandwashingApplication.instance) { - with(EmojiLoader.get(this)) { + with(EmojiLoader.loadAsync(this)) { try { this.await() .process(resources.getStringArray(array)[position]) diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/application/HandwashingApplication.kt b/app/src/main/java/com/javinator9889/handwashingreminder/application/HandwashingApplication.kt index 0b192d7..b51199b 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/application/HandwashingApplication.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/application/HandwashingApplication.kt @@ -36,12 +36,15 @@ import timber.log.Timber class HandwashingApplication : BaseApplication() { + private val scope = CoroutineScope(Dispatchers.Default) var adLoader: AdLoader? = null lateinit var activityHandler: ActivityHandler lateinit var firebaseInitDeferred: Deferred companion object { lateinit var instance: HandwashingApplication + val scope: CoroutineScope + get() = instance.scope } override fun attachBaseContext(base: Context?) { @@ -61,7 +64,7 @@ class HandwashingApplication : BaseApplication() { } private fun initFirebaseAppAsync(): Deferred { - return GlobalScope.async { + return scope.async { withContext(Dispatchers.IO) { FirebaseApp.initializeApp(this@HandwashingApplication) if (isDebuggable()) { diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/data/SettingsLoader.kt b/app/src/main/java/com/javinator9889/handwashingreminder/data/SettingsLoader.kt index 29a74ec..e24be12 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/data/SettingsLoader.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/data/SettingsLoader.kt @@ -50,7 +50,7 @@ class SettingsLoader( private val view: SettingsView, private val lifecycleOwner: LifecycleOwner ) { - private lateinit var emojiLoader: CompletableDeferred + private lateinit var emojiLoader: Deferred private lateinit var emojiCompat: EmojiCompat private var arePreferencesInitialized = AtomicBoolean(false) @@ -58,7 +58,7 @@ class SettingsLoader( if (arePreferencesInitialized.get()) return val deferreds = mutableSetOf>() - emojiLoader = EmojiLoader.get(view.requireContext()) + emojiLoader = EmojiLoader.loadAsync(view.requireContext()) with(view) { lifecycleOwner.lifecycleScope.launch { setupPreferenceAsync( @@ -67,7 +67,7 @@ class SettingsLoader( onClickListener = { openWebsite(PLAYSTORE_URL, R.string.playstore_err) true - }).let { deferreds.add(it) } + }).also { deferreds.add(it) } setupPreferenceAsync( "share", Ionicons.Icon.ion_android_share, @@ -109,32 +109,32 @@ class SettingsLoader( startActivity(this) } true - }).let { deferreds.add(it) } + }).also { deferreds.add(it) } setupPreferenceAsync("telegram", onClickListener = { openWebsite(TELEGRAM_URL, R.string.telegram_err) true - }).let { deferreds.add(it) } + }).also { deferreds.add(it) } setupPreferenceAsync( "github", Ionicons.Icon.ion_social_github, onClickListener = { openWebsite(GITHUB_URL, R.string.browser_err) true - }).let { deferreds.add(it) } + }).also { deferreds.add(it) } setupPreferenceAsync( "twitter", Ionicons.Icon.ion_social_twitter, onClickListener = { openWebsite(TWITTER_URL, R.string.twitter_err) true - }).let { deferreds.add(it) } + }).also { deferreds.add(it) } setupPreferenceAsync( "linkedin", Ionicons.Icon.ion_social_linkedin, onClickListener = { openWebsite(LINKEDIN_URL, R.string.browser_err) true - }).let { deferreds.add(it) } + }).also { deferreds.add(it) } setupPreferenceAsync( Preferences.ANALYTICS_ENABLED, Ionicons.Icon.ion_arrow_graph_up_right, @@ -142,7 +142,7 @@ class SettingsLoader( onInitialized = { it, _ -> firebaseAnalyticsPreference = WeakReference(it) } - ).let { deferreds.add(it) } + ).also { deferreds.add(it) } setupPreferenceAsync( Preferences.PERFORMANCE_ENABLED, Ionicons.Icon.ion_ios_speedometer_outline, @@ -150,7 +150,7 @@ class SettingsLoader( onInitialized = { it, _ -> firebasePerformancePreference = WeakReference(it) } - ).let { deferreds.add(it) } + ).also { deferreds.add(it) } setupPreferenceAsync( Preferences.ADS_ENABLED, Ionicons.Icon.ion_ios_barcode_outline, @@ -158,7 +158,7 @@ class SettingsLoader( onInitialized = { it, _ -> adsPreference = WeakReference(it) } - ).let { deferreds.add(it) } + ).also { deferreds.add(it) } setupPreferenceAsync( Preferences.DONATIONS, Ionicons.Icon.ion_card, @@ -172,7 +172,7 @@ class SettingsLoader( billingService.addOnPurchaseFinishedListener(this@with) donationsPreference = WeakReference(it) } - ).let { deferreds.add(it) } + ).also { deferreds.add(it) } setupPreferenceAsync( "translate", Ionicons.Icon.ion_chatbox_working, @@ -183,7 +183,7 @@ class SettingsLoader( ) true } - ).let { deferreds.add(it) } + ).also { deferreds.add(it) } setupPreferenceAsync( "send_suggestions", Ionicons.Icon.ion_chatbubbles, @@ -215,7 +215,7 @@ class SettingsLoader( } true } - ).let { deferreds.add(it) } + ).also { deferreds.add(it) } setupPreferenceAsync( "opensource_libs", Ionicons.Icon.ion_code, @@ -239,7 +239,7 @@ class SettingsLoader( .start(requireContext()) true } - ).let { deferreds.add(it) } + ).also { deferreds.add(it) } setupPreferenceAsync( "tos_privacy", Ionicons.Icon.ion_android_cloud_done, @@ -250,7 +250,7 @@ class SettingsLoader( ).run { startActivity(this) } true } - ).let { deferreds.add(it) } + ).also { deferreds.add(it) } setupPreferenceAsync( Preferences.BREAKFAST_TIME, Ionicons.Icon.ion_coffee, @@ -260,7 +260,7 @@ class SettingsLoader( "summary" to getText(R.string.breakfast_pref_summ) ), dispatcher = Dispatchers.Main - ).let { deferreds.add(it) } + ).also { deferreds.add(it) } setupPreferenceAsync( Preferences.LUNCH_TIME, Ionicons.Icon.ion_android_restaurant, @@ -270,7 +270,7 @@ class SettingsLoader( "summary" to getText(R.string.lunch_pref_summ) ), dispatcher = Dispatchers.Main - ).let { deferreds.add(it) } + ).also { deferreds.add(it) } setupPreferenceAsync( Preferences.DINNER_TIME, Ionicons.Icon.ion_ios_moon_outline, @@ -280,7 +280,7 @@ class SettingsLoader( "summary" to getText(R.string.dinner_pref_summ) ), dispatcher = Dispatchers.Main - ).let { deferreds.add(it) } + ).also { deferreds.add(it) } deferreds.awaitAll() arePreferencesInitialized.set(true) } diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/emoji/EmojiLoader.kt b/app/src/main/java/com/javinator9889/handwashingreminder/emoji/EmojiLoader.kt index 1f6019a..2c2c97c 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/emoji/EmojiLoader.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/emoji/EmojiLoader.kt @@ -20,37 +20,38 @@ package com.javinator9889.handwashingreminder.emoji import android.content.Context import androidx.emoji.text.EmojiCompat +import com.javinator9889.handwashingreminder.application.HandwashingApplication import kotlinx.coroutines.CompletableDeferred +import kotlinx.coroutines.async import timber.log.Timber object EmojiLoader { - fun get(context: Context): CompletableDeferred { - val deferred = CompletableDeferred() + fun loadAsync(context: Context) = HandwashingApplication.scope.async { try { - with(EmojiCompat.get()) { - deferred.complete(this) - } + EmojiCompat.get() } catch (_: IllegalStateException) { Timber.d("EmojiCompat not initialized yet") val emojiCompat = with(EmojiConfig.get(context)) { EmojiCompat.init(this) } - emojiCompat.registerInitCallback( - object : EmojiCompat.InitCallback() { - override fun onInitialized() { - emojiCompat.unregisterInitCallback(this) - deferred.complete(EmojiCompat.get()) - } + val loadDeferred = CompletableDeferred() + emojiCompat.registerInitCallback(object : + EmojiCompat.InitCallback() { + override fun onInitialized() { + super.onInitialized() + emojiCompat.unregisterInitCallback(this) + loadDeferred.complete(EmojiCompat.get()) + } - override fun onFailed(throwable: Throwable?) { - emojiCompat.unregisterInitCallback(this) - val exception = throwable - ?: RuntimeException("EmojiCompat failed to load") - deferred.completeExceptionally(exception) - } - }) - } finally { - return deferred + override fun onFailed(throwable: Throwable?) { + super.onFailed(throwable) + emojiCompat.unregisterInitCallback(this) + val exception = throwable + ?: RuntimeException("EmojiCompat failed to load") + loadDeferred.completeExceptionally(exception) + } + }) + loadDeferred.await() } } } \ No newline at end of file diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/gms/activity/ActivityReceiver.kt b/app/src/main/java/com/javinator9889/handwashingreminder/gms/activity/ActivityReceiver.kt index c23808f..fa02a18 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/gms/activity/ActivityReceiver.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/gms/activity/ActivityReceiver.kt @@ -31,7 +31,7 @@ import com.javinator9889.handwashingreminder.emoji.EmojiLoader import com.javinator9889.handwashingreminder.notifications.NotificationsHandler import com.javinator9889.handwashingreminder.utils.ACTIVITY_CHANNEL_ID import com.javinator9889.handwashingreminder.utils.goAsync -import kotlinx.coroutines.CompletableDeferred +import kotlinx.coroutines.Deferred import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext @@ -41,7 +41,7 @@ class ActivityReceiver : BroadcastReceiver() { */ override fun onReceive(context: Context, intent: Intent) { if (ActivityTransitionResult.hasResult(intent)) { - val emojiLoader = EmojiLoader.get(context) + val emojiLoader = EmojiLoader.loadAsync(context) val result = ActivityTransitionResult.extractResult(intent)!! for (event in result.transitionEvents) { if (event.transitionType != @@ -74,7 +74,7 @@ class ActivityReceiver : BroadcastReceiver() { private suspend fun putNotification( notificationsHandler: NotificationsHandler, - emojiLoader: CompletableDeferred, + emojiLoader: Deferred, detectedActivity: Int, context: Context ) { diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/jobs/alarms/AlarmReceiver.kt b/app/src/main/java/com/javinator9889/handwashingreminder/jobs/alarms/AlarmReceiver.kt index 70b6bfa..7c436fd 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/jobs/alarms/AlarmReceiver.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/jobs/alarms/AlarmReceiver.kt @@ -21,6 +21,7 @@ package com.javinator9889.handwashingreminder.jobs.alarms import android.content.BroadcastReceiver import android.content.Context import android.content.Intent +import com.javinator9889.handwashingreminder.application.HandwashingApplication import com.javinator9889.handwashingreminder.jobs.workers.BreakfastNotificationWorker import com.javinator9889.handwashingreminder.jobs.workers.DinnerNotificationWorker import com.javinator9889.handwashingreminder.jobs.workers.LunchNotificationWorker @@ -35,6 +36,6 @@ class AlarmReceiver : BroadcastReceiver() { Alarms.DINNER_ALARM.identifier -> DinnerNotificationWorker(context) else -> return } - goAsync { worker.doWork() } + goAsync(coroutineScope = HandwashingApplication.scope) { worker.doWork() } } } \ No newline at end of file diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/jobs/workers/ScheduledNotificationWorker.kt b/app/src/main/java/com/javinator9889/handwashingreminder/jobs/workers/ScheduledNotificationWorker.kt index 10d633c..11a44f8 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/jobs/workers/ScheduledNotificationWorker.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/jobs/workers/ScheduledNotificationWorker.kt @@ -43,7 +43,7 @@ abstract class ScheduledNotificationWorker(context: Context) { suspend fun doWork() = coroutineScope { try { val startTime = System.currentTimeMillis() - val emojiLoader = EmojiLoader.get(context) + val emojiLoader = EmojiLoader.loadAsync(context) val notificationsHandler = NotificationsHandler( context = context, channelId = TIME_CHANNEL_ID, From 2a4376e97bb2cd9f013f1ee786064c09d57b1375 Mon Sep 17 00:00:00 2001 From: Javinator9889 Date: Wed, 17 Jun 2020 10:50:20 +0200 Subject: [PATCH 50/95] Updated structure of ActivityHandler.kt for trying to solve possible errors with recognition API (issue #12) --- .../activities/LauncherActivity.kt | 6 +- .../settings/ActivityMultiSelectList.kt | 6 +- .../application/HandwashingApplication.kt | 2 +- .../gms/activity/ActivityHandler.kt | 61 ++++++++++++------- .../gms/activity/ActivityReceiver.kt | 6 +- .../jobs/BootCompletedJob.kt | 8 +-- .../appintro/IntroActivity.kt | 47 ++++++++------ 7 files changed, 85 insertions(+), 51 deletions(-) diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/activities/LauncherActivity.kt b/app/src/main/java/com/javinator9889/handwashingreminder/activities/LauncherActivity.kt index 4e882c1..4cd1bfb 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/activities/LauncherActivity.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/activities/LauncherActivity.kt @@ -42,6 +42,7 @@ import com.javinator9889.handwashingreminder.R import com.javinator9889.handwashingreminder.application.HandwashingApplication import com.javinator9889.handwashingreminder.data.UserProperties import com.javinator9889.handwashingreminder.emoji.EmojiLoader +import com.javinator9889.handwashingreminder.gms.activity.ActivityHandler import com.javinator9889.handwashingreminder.gms.ads.AdLoader import com.javinator9889.handwashingreminder.gms.ads.AdsEnabler import com.javinator9889.handwashingreminder.jobs.alarms.AlarmHandler @@ -250,6 +251,7 @@ class LauncherActivity : AppCompatActivity() { Timber.d("Setting-up security providers") Security.insertProviderAt(Conscrypt.newProvider(), 1) Timber.d("Setting-up activity recognition") + val activityHandler = ActivityHandler.getInstance(this) if (sharedPreferences.getBoolean( Preferences.ACTIVITY_TRACKING_ENABLED, false ) && with(GoogleApiAvailability.getInstance()) { @@ -257,9 +259,9 @@ class LauncherActivity : AppCompatActivity() { ConnectionResult.SUCCESS } ) { - app.activityHandler.startTrackingActivity() + activityHandler.startTrackingActivity() } else { - app.activityHandler.disableActivityTracker() + activityHandler.disableActivityTracker() } with(AlarmHandler(this)) { scheduleAllAlarms() diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/settings/ActivityMultiSelectList.kt b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/settings/ActivityMultiSelectList.kt index 8d0db50..d0c67c5 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/settings/ActivityMultiSelectList.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/settings/ActivityMultiSelectList.kt @@ -22,7 +22,7 @@ import android.content.Context import android.util.AttributeSet import androidx.preference.MultiSelectListPreference import com.javinator9889.handwashingreminder.R -import com.javinator9889.handwashingreminder.application.HandwashingApplication +import com.javinator9889.handwashingreminder.gms.activity.ActivityHandler import com.mikepenz.iconics.IconicsDrawable import com.mikepenz.iconics.typeface.library.ionicons.Ionicons import com.mikepenz.iconics.utils.sizeDp @@ -82,8 +82,8 @@ class ActivityMultiSelectList : MultiSelectListPreference { } private fun reloadActivityHandler() { - with(HandwashingApplication.instance) { - activityHandler.reload() + with(ActivityHandler.getInstance(context)) { + reload() } } } \ No newline at end of file diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/application/HandwashingApplication.kt b/app/src/main/java/com/javinator9889/handwashingreminder/application/HandwashingApplication.kt index b51199b..f8b2eda 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/application/HandwashingApplication.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/application/HandwashingApplication.kt @@ -59,7 +59,7 @@ class HandwashingApplication : BaseApplication() { override fun onCreate() { super.onCreate() instance = this - activityHandler = ActivityHandler(this) + activityHandler = ActivityHandler.getInstance(this) firebaseInitDeferred = initFirebaseAppAsync() } diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/gms/activity/ActivityHandler.kt b/app/src/main/java/com/javinator9889/handwashingreminder/gms/activity/ActivityHandler.kt index ac29b8d..03c399b 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/gms/activity/ActivityHandler.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/gms/activity/ActivityHandler.kt @@ -21,27 +21,44 @@ package com.javinator9889.handwashingreminder.gms.activity import android.app.PendingIntent import android.content.Context import android.content.Intent +import android.content.IntentFilter +import androidx.localbroadcastmanager.content.LocalBroadcastManager import androidx.preference.PreferenceManager import com.google.android.gms.location.ActivityRecognition import com.google.android.gms.location.ActivityTransition import com.google.android.gms.location.ActivityTransitionRequest import com.google.android.gms.tasks.Task +import com.javinator9889.handwashingreminder.BuildConfig import com.javinator9889.handwashingreminder.utils.Preferences import timber.log.Timber +internal const val ACTIVITY_REQUEST_CODE = 64 +internal const val TRANSITIONS_RECEIVER_ACTION = + "${BuildConfig.APPLICATION_ID}/TRANSITIONS_RECEIVER_ACTION" -class ActivityHandler(private val context: Context) { - private val requestCode = 51824210 +class ActivityHandler private constructor(private val context: Context) { private val transitions: MutableList = mutableListOf() private var pendingIntent: PendingIntent private var activityRegistered = false + private val transitionsReceiver = ActivityReceiver() init { val activitiesSet = createSetOfTransitions() addTransitions(activitiesSet, transitions) + registerActivityReceiver() pendingIntent = createPendingIntent() } + companion object { + private var instance: ActivityHandler? = null + + fun getInstance(context: Context): ActivityHandler { + instance?.let { return it } + instance = ActivityHandler(context) + return instance!! + } + } + fun startTrackingActivity() { if (transitions.size == 0) return @@ -60,28 +77,28 @@ class ActivityHandler(private val context: Context) { if (!activityRegistered) return null return ActivityRecognition.getClient(context) - .removeActivityTransitionUpdates(pendingIntent).apply { - addOnSuccessListener { pendingIntent.cancel() } - addOnFailureListener { e: Exception -> Timber.e(e) } + .removeActivityTransitionUpdates(pendingIntent).also { + it.addOnSuccessListener { + pendingIntent.cancel(); activityRegistered = false + } + it.addOnFailureListener { e: Exception -> Timber.e(e) } } } - fun reload() { - with(createSetOfTransitions()) { - transitions.clear() - addTransitions(this, transitions) - disableActivityTracker()?.let { - it.addOnSuccessListener { - pendingIntent = createPendingIntent() - startTrackingActivity() - } + fun reload() = with(createSetOfTransitions()) { + transitions.clear() + addTransitions(this, transitions) + disableActivityTracker()?.let { + it.addOnCompleteListener { + pendingIntent = createPendingIntent() + startTrackingActivity() } } } private fun createSetOfTransitions(): Set { val preferences = PreferenceManager.getDefaultSharedPreferences(context) - with(hashSetOf()) { + with(mutableSetOf()) { preferences.getStringSet( Preferences.ACTIVITIES_ENABLED, Preferences.DEFAULT_ACTIVITY_SET @@ -105,11 +122,13 @@ class ActivityHandler(private val context: Context) { } } - private fun createPendingIntent(): PendingIntent { - with(Intent(context, ActivityReceiver::class.java)) { - return PendingIntent.getBroadcast( - context, requestCode, this, PendingIntent.FLAG_UPDATE_CURRENT - ) + private fun createPendingIntent(): PendingIntent = + with(Intent(TRANSITIONS_RECEIVER_ACTION)) { + PendingIntent.getBroadcast(context, ACTIVITY_REQUEST_CODE, this, 0) } - } + + private fun registerActivityReceiver() = + LocalBroadcastManager.getInstance(context).registerReceiver( + transitionsReceiver, IntentFilter(TRANSITIONS_RECEIVER_ACTION) + ) } \ No newline at end of file diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/gms/activity/ActivityReceiver.kt b/app/src/main/java/com/javinator9889/handwashingreminder/gms/activity/ActivityReceiver.kt index fa02a18..b9212e6 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/gms/activity/ActivityReceiver.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/gms/activity/ActivityReceiver.kt @@ -21,6 +21,7 @@ package com.javinator9889.handwashingreminder.gms.activity import android.content.BroadcastReceiver import android.content.Context import android.content.Intent +import android.text.TextUtils import androidx.annotation.StringRes import androidx.emoji.text.EmojiCompat import com.google.android.gms.location.ActivityTransition @@ -40,7 +41,8 @@ class ActivityReceiver : BroadcastReceiver() { * {@inheritDoc} */ override fun onReceive(context: Context, intent: Intent) { - if (ActivityTransitionResult.hasResult(intent)) { + if (ActivityTransitionResult.hasResult(intent) + && TextUtils.equals(TRANSITIONS_RECEIVER_ACTION, intent.action)) { val emojiLoader = EmojiLoader.loadAsync(context) val result = ActivityTransitionResult.extractResult(intent)!! for (event in result.transitionEvents) { @@ -103,10 +105,10 @@ class ActivityReceiver : BroadcastReceiver() { "Activity not recognized" ) } - val emojiCompat = emojiLoader.await() var title = context.getText(notificationContent.title) var content = context.getText(notificationContent.content) try { + val emojiCompat = emojiLoader.await() title = emojiCompat.process(title) content = emojiCompat.process(content) } catch (_: IllegalStateException) { diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/jobs/BootCompletedJob.kt b/app/src/main/java/com/javinator9889/handwashingreminder/jobs/BootCompletedJob.kt index 1a614c9..326a904 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/jobs/BootCompletedJob.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/jobs/BootCompletedJob.kt @@ -22,7 +22,7 @@ import android.content.BroadcastReceiver import android.content.Context import android.content.Intent import androidx.preference.PreferenceManager -import com.javinator9889.handwashingreminder.application.HandwashingApplication +import com.javinator9889.handwashingreminder.gms.activity.ActivityHandler import com.javinator9889.handwashingreminder.jobs.alarms.AlarmHandler import com.javinator9889.handwashingreminder.utils.Preferences import timber.log.Timber @@ -30,16 +30,16 @@ import timber.log.Timber class BootCompletedJob : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { if (intent.action == Intent.ACTION_BOOT_COMPLETED) { - val app = HandwashingApplication.instance + val activityHandler = ActivityHandler.getInstance(context) val preferences = PreferenceManager.getDefaultSharedPreferences(context) if (preferences.getBoolean( Preferences.ACTIVITY_TRACKING_ENABLED, false ) ) - app.activityHandler.startTrackingActivity() + activityHandler.startTrackingActivity() else - app.activityHandler.disableActivityTracker() + activityHandler.disableActivityTracker() Timber.d("Enqueuing notifications as the device has rebooted") with(AlarmHandler(context)) { scheduleAllAlarms() diff --git a/appintro/src/main/java/com/javinator9889/handwashingreminder/appintro/IntroActivity.kt b/appintro/src/main/java/com/javinator9889/handwashingreminder/appintro/IntroActivity.kt index 50eaf6d..33c9e43 100644 --- a/appintro/src/main/java/com/javinator9889/handwashingreminder/appintro/IntroActivity.kt +++ b/appintro/src/main/java/com/javinator9889/handwashingreminder/appintro/IntroActivity.kt @@ -28,6 +28,7 @@ import android.os.Bundle import android.view.View import android.widget.FrameLayout import androidx.annotation.Keep +import androidx.core.app.ActivityCompat import androidx.core.content.edit import androidx.core.util.set import androidx.fragment.app.Fragment @@ -50,7 +51,7 @@ import com.javinator9889.handwashingreminder.appintro.fragments.TimeConfigIntroF import com.javinator9889.handwashingreminder.appintro.fragments.TimeContainer import com.javinator9889.handwashingreminder.appintro.timeconfig.TimeConfigItem import com.javinator9889.handwashingreminder.appintro.utils.AnimatedResources -import com.javinator9889.handwashingreminder.application.HandwashingApplication +import com.javinator9889.handwashingreminder.gms.activity.ActivityHandler import com.javinator9889.handwashingreminder.jobs.alarms.AlarmHandler import com.javinator9889.handwashingreminder.utils.* import kotlinx.android.synthetic.main.animated_intro.* @@ -173,21 +174,22 @@ class IntroActivity : AppIntro2(), super.onDonePressed(currentFragment) lifecycleScope.launch { val deferreds = mutableSetOf>() - val app = HandwashingApplication.instance + val activityHandler = + ActivityHandler.getInstance(this@IntroActivity) val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this@IntroActivity) sharedPreferences.edit { timeConfigSlide.itemAdapter.adapterItems.forEach { item -> - val time = "${item.hours}:${item.minutes}" - when (item.id) { - TimeConfig.BREAKFAST_ID -> - putString(Preferences.BREAKFAST_TIME, time) - TimeConfig.LUNCH_ID -> - putString(Preferences.LUNCH_TIME, time) - TimeConfig.DINNER_ID -> - putString(Preferences.DINNER_TIME, time) - } + val time = "${item.hours}:${item.minutes}" + when (item.id) { + TimeConfig.BREAKFAST_ID -> + putString(Preferences.BREAKFAST_TIME, time) + TimeConfig.LUNCH_ID -> + putString(Preferences.LUNCH_TIME, time) + TimeConfig.DINNER_ID -> + putString(Preferences.DINNER_TIME, time) } + } putBoolean( Preferences.ANALYTICS_ENABLED, policySlide.firebaseAnalytics.isChecked @@ -200,8 +202,8 @@ class IntroActivity : AppIntro2(), Preferences.ADS_ENABLED, sharedPreferences.getBoolean(Preferences.ADS_ENABLED, true) ) - if (!isAtLeast(AndroidVersion.Q)) - activityRecognitionPermissionGranted = true + activityRecognitionPermissionGranted = + activityRecognitionPermissionApproved() putBoolean( Preferences.ACTIVITY_TRACKING_ENABLED, activityRecognitionPermissionGranted @@ -219,9 +221,9 @@ class IntroActivity : AppIntro2(), splitInstallManager.deferredUninstall(listOf(AppIntro.MODULE_NAME)) async { if (activityRecognitionPermissionGranted) - app.activityHandler.startTrackingActivity() + activityHandler.startTrackingActivity() else - app.activityHandler.disableActivityTracker() + activityHandler.disableActivityTracker() with(AlarmHandler(this@IntroActivity)) { scheduleAllAlarms() } @@ -384,9 +386,8 @@ class IntroActivity : AppIntro2(), ) { if (requestCode == PERMISSIONS_REQUEST_CODE) { activityRecognitionPermissionGranted = - (grantResults.isNotEmpty() && - grantResults[0] == PERMISSION_GRANTED) || - !isAtLeast(AndroidVersion.Q) + (grantResults.isNotEmpty() && grantResults[0] == PERMISSION_GRANTED) + || !isAtLeast(AndroidVersion.Q) } super.onRequestPermissionsResult(requestCode, permissions, grantResults) } @@ -417,4 +418,14 @@ class IntroActivity : AppIntro2(), Timber.e("Requested next slide illegally (not exists)") } } + + private fun activityRecognitionPermissionApproved(): Boolean = + if (!isAtLeast(AndroidVersion.Q)) + true + else { + activityRecognitionPermissionGranted + || PERMISSION_GRANTED == ActivityCompat.checkSelfPermission( + this, Manifest.permission.ACTIVITY_RECOGNITION + ) + } } From 5072c7a5e83a3e1e834d729526f0e9ab68e9e812 Mon Sep 17 00:00:00 2001 From: Javinator9889 Date: Wed, 24 Jun 2020 14:45:55 +0200 Subject: [PATCH 51/95] First approach for displaying more information on "Diseases" page --- app/build.gradle | 6 +- .../fragments/diseases/DiseasesFragment.kt | 32 ++++- app/src/main/res/drawable/divider_shape.xml | 7 ++ .../main/res/drawable/shadowed_divider.xml | 16 +++ app/src/main/res/layout/handwash_count.xml | 112 ++++++++++++++++++ .../main/res/layout/loading_recycler_view.xml | 3 +- app/src/main/res/layout/main_disease_view.xml | 59 +++++++++ .../src/main/res/raw/wash_your_hands.json | 0 app/src/main/res/values/dimens.xml | 2 + app/src/main/res/values/styles.xml | 7 ++ .../appintro/utils/AnimatedResources.kt | 3 +- build.gradle | 4 +- 12 files changed, 243 insertions(+), 8 deletions(-) create mode 100644 app/src/main/res/drawable/divider_shape.xml create mode 100644 app/src/main/res/drawable/shadowed_divider.xml create mode 100644 app/src/main/res/layout/handwash_count.xml create mode 100644 app/src/main/res/layout/main_disease_view.xml rename {appintro => app}/src/main/res/raw/wash_your_hands.json (100%) diff --git a/app/build.gradle b/app/build.gradle index a191a8f..c40f3c4 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -147,11 +147,11 @@ dependencies { // https://firebase.google.com/docs/android/setup#add-sdks api 'com.google.firebase:firebase-common-ktx:19.3.0' api 'com.google.firebase:firebase-analytics:17.4.3' - api 'com.google.firebase:firebase-crashlytics:17.0.1' + api 'com.google.firebase:firebase-crashlytics:17.1.0' api 'com.google.firebase:firebase-perf:19.0.7' implementation 'com.google.firebase:firebase-auth:19.3.1' // http://airbnb.io/lottie/#/android?id=getting-started - api "com.airbnb.android:lottie:3.4.0" + api "com.airbnb.android:lottie:3.4.1" // https://firebase.google.com/docs/remote-config/use-config-android implementation 'com.google.firebase:firebase-config:19.1.4' implementation 'com.google.firebase:firebase-config-ktx:19.1.4' @@ -197,5 +197,7 @@ dependencies { implementation 'org.conscrypt:conscrypt-android:2.4.0' // https://github.com/deano2390/MaterialShowcaseView implementation 'com.github.deano2390:MaterialShowcaseView:1.3.4' + // https://github.com/PhilJay/MPAndroidChart + implementation 'com.github.PhilJay:MPAndroidChart:v3.1.0' } apply plugin: 'com.google.gms.google-services' diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/diseases/DiseasesFragment.kt b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/diseases/DiseasesFragment.kt index faff425..1b54574 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/diseases/DiseasesFragment.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/diseases/DiseasesFragment.kt @@ -28,6 +28,9 @@ import androidx.lifecycle.lifecycleScope import androidx.lifecycle.whenStarted import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView +import com.github.mikephil.charting.data.BarData +import com.github.mikephil.charting.data.BarDataSet +import com.github.mikephil.charting.data.BarEntry import com.javinator9889.handwashingreminder.R import com.javinator9889.handwashingreminder.activities.base.BaseFragmentView import com.javinator9889.handwashingreminder.activities.base.LayoutVisibilityChange @@ -40,13 +43,16 @@ import com.mikepenz.fastadapter.FastAdapter import com.mikepenz.fastadapter.GenericItem import com.mikepenz.fastadapter.adapters.ItemAdapter import com.mikepenz.fastadapter.listeners.ClickEventHook +import kotlinx.android.synthetic.main.handwash_count.view.* import kotlinx.android.synthetic.main.loading_recycler_view.* import kotlinx.android.synthetic.main.loading_recycler_view.view.* import kotlinx.coroutines.launch import timber.log.Timber + class DiseasesFragment : BaseFragmentView(), LayoutVisibilityChange { - override val layoutId: Int = R.layout.loading_recycler_view + override val layoutId: Int = R.layout.main_disease_view +// override val layoutId: Int = R.layout.loading_recycler_view private lateinit var parsedHTMLTexts: List private lateinit var fastAdapter: FastAdapter @@ -97,6 +103,28 @@ class DiseasesFragment : BaseFragmentView(), LayoutVisibilityChange { } fastAdapter.addEventHook(DiseaseClickEventHook()) fastAdapter.withSavedInstanceState(savedInstanceState) + val entries = listOf( + BarEntry(0F, 12F), + BarEntry(-1F, 16F), + BarEntry(-2F, 11F), + BarEntry(-3F, 11F), + BarEntry(-4F, 13F), + BarEntry(-5F, 3F), + BarEntry(-6F, 8F), + BarEntry(-7F, 1F), + BarEntry(-8F, 20F), + BarEntry(-9F, 12F), + BarEntry(-10F, 8F) + ) + val barDataSet = BarDataSet(entries, "label") + view.countChart.data = BarData(barDataSet) + view.countChart.setDrawGridBackground(false) + view.countChart.setVisibleXRangeMaximum(7F) + view.countChart.moveViewToX(0F) + view.countChart.axisLeft.setDrawGridLines(false) + view.countChart.axisRight.setDrawGridLines(false) + view.countChart.xAxis.setDrawGridLines(false) + view.countChart.invalidate() } override fun onSaveInstanceState(outState: Bundle) { @@ -146,4 +174,4 @@ class DiseasesFragment : BaseFragmentView(), LayoutVisibilityChange { ) } } -} \ No newline at end of file +} diff --git a/app/src/main/res/drawable/divider_shape.xml b/app/src/main/res/drawable/divider_shape.xml new file mode 100644 index 0000000..a778e11 --- /dev/null +++ b/app/src/main/res/drawable/divider_shape.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/app/src/main/res/drawable/shadowed_divider.xml b/app/src/main/res/drawable/shadowed_divider.xml new file mode 100644 index 0000000..e13f54f --- /dev/null +++ b/app/src/main/res/drawable/shadowed_divider.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/handwash_count.xml b/app/src/main/res/layout/handwash_count.xml new file mode 100644 index 0000000..b101679 --- /dev/null +++ b/app/src/main/res/layout/handwash_count.xml @@ -0,0 +1,112 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/loading_recycler_view.xml b/app/src/main/res/layout/loading_recycler_view.xml index b211ad1..aa8201c 100644 --- a/app/src/main/res/layout/loading_recycler_view.xml +++ b/app/src/main/res/layout/loading_recycler_view.xml @@ -2,7 +2,8 @@ + android:layout_width="match_parent" android:layout_height="match_parent" + android:background="@android:color/white"> + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/appintro/src/main/res/raw/wash_your_hands.json b/app/src/main/res/raw/wash_your_hands.json similarity index 100% rename from appintro/src/main/res/raw/wash_your_hands.json rename to app/src/main/res/raw/wash_your_hands.json diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index c66f051..fd69e76 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -3,4 +3,6 @@ 6dp 8dp 80dp + 16dp + 5dp \ No newline at end of file diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 2f049ea..f62f238 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -31,4 +31,11 @@ 15dp + + diff --git a/appintro/src/main/java/com/javinator9889/handwashingreminder/appintro/utils/AnimatedResources.kt b/appintro/src/main/java/com/javinator9889/handwashingreminder/appintro/utils/AnimatedResources.kt index 60f3be1..19054f8 100644 --- a/appintro/src/main/java/com/javinator9889/handwashingreminder/appintro/utils/AnimatedResources.kt +++ b/appintro/src/main/java/com/javinator9889/handwashingreminder/appintro/utils/AnimatedResources.kt @@ -20,10 +20,11 @@ package com.javinator9889.handwashingreminder.appintro.utils import androidx.annotation.RawRes import com.javinator9889.handwashingreminder.appintro.R +import com.javinator9889.handwashingreminder.R as RBase enum class AnimatedResources(@RawRes val res: Int) { - WASH_HANDS(R.raw.wash_your_hands), + WASH_HANDS(RBase.raw.wash_your_hands), TIMER(R.raw.pending_timer), ACTIVITY(R.raw.travelers_walking), PRIVACY(R.raw.padlock_animation) diff --git a/build.gradle b/build.gradle index 02d6767..346ced4 100644 --- a/build.gradle +++ b/build.gradle @@ -2,8 +2,8 @@ buildscript { ext.kotlin_version = '1.3.72' - ext.latestAboutLibsRelease = '8.1.6' - ext.latestFastAdapterRelease = '5.0.2' + ext.latestAboutLibsRelease = '8.2.0' + ext.latestFastAdapterRelease = '5.1.0' repositories { google() jcenter() From 016f9fdeb3ee9bc86d1475bf52a98b102f406322 Mon Sep 17 00:00:00 2001 From: Javinator9889 Date: Wed, 24 Jun 2020 20:32:14 +0200 Subject: [PATCH 52/95] First Room database approach for counting the amount of times the user washes his hands --- app/build.gradle | 9 +- .../fragments/diseases/DiseasesFragment.kt | 56 +++++++---- .../repositories/HandwashingRepository.kt | 37 ++++++++ .../data/room/dao/HandwashingDao.kt | 54 +++++++++++ .../data/room/db/HandwashingDatabase.kt | 52 ++++++++++ .../data/room/entities/Handwashing.kt | 30 ++++++ .../data/viewmodels/HandwashingViewModel.kt | 95 +++++++++++++++++++ .../handwashingreminder/utils/Collections.kt | 19 ++++ .../handwashingreminder/utils/Time.kt | 45 +-------- .../utils/calendar/Calendar.kt | 44 +++++++++ .../utils/room/Converters.kt | 30 ++++++ gradle.properties | 2 + 12 files changed, 410 insertions(+), 63 deletions(-) create mode 100644 app/src/main/java/com/javinator9889/handwashingreminder/data/repositories/HandwashingRepository.kt create mode 100644 app/src/main/java/com/javinator9889/handwashingreminder/data/room/dao/HandwashingDao.kt create mode 100644 app/src/main/java/com/javinator9889/handwashingreminder/data/room/db/HandwashingDatabase.kt create mode 100644 app/src/main/java/com/javinator9889/handwashingreminder/data/room/entities/Handwashing.kt create mode 100644 app/src/main/java/com/javinator9889/handwashingreminder/data/viewmodels/HandwashingViewModel.kt create mode 100644 app/src/main/java/com/javinator9889/handwashingreminder/utils/calendar/Calendar.kt create mode 100644 app/src/main/java/com/javinator9889/handwashingreminder/utils/room/Converters.kt diff --git a/app/build.gradle b/app/build.gradle index c40f3c4..54e985a 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -32,12 +32,10 @@ def gitCommitHash = { -> } android { - viewBinding.enabled = true + buildFeatures.viewBinding = true compileSdkVersion 29 buildToolsVersion "29.0.3" - android.defaultConfig.vectorDrawables.useSupportLibrary = true - defaultConfig { applicationId "com.javinator9889.handwashingreminder" minSdkVersion 17 @@ -46,6 +44,7 @@ android { versionName "1.2.0-${gitCommitHash}" multiDexEnabled true resConfigs "en", "es" + vectorDrawables.useSupportLibrary = true testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } @@ -104,6 +103,7 @@ android { } dependencies { + def room_version = "2.2.5" implementation fileTree(dir: 'libs', include: ['*.jar']) api "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" api 'androidx.appcompat:appcompat:1.1.0' @@ -199,5 +199,8 @@ dependencies { implementation 'com.github.deano2390:MaterialShowcaseView:1.3.4' // https://github.com/PhilJay/MPAndroidChart implementation 'com.github.PhilJay:MPAndroidChart:v3.1.0' + // https://developer.android.com/training/data-storage/room + implementation "androidx.room:room-ktx:$room_version" + kapt "androidx.room:room-compiler:$room_version" } apply plugin: 'com.google.gms.google-services' diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/diseases/DiseasesFragment.kt b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/diseases/DiseasesFragment.kt index 1b54574..9ae7c9c 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/diseases/DiseasesFragment.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/diseases/DiseasesFragment.kt @@ -22,15 +22,16 @@ import android.content.Intent import android.os.Bundle import android.view.View import androidx.core.app.ActivityCompat +import androidx.fragment.app.activityViewModels import androidx.fragment.app.viewModels import androidx.lifecycle.Observer import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.observe import androidx.lifecycle.whenStarted import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import com.github.mikephil.charting.data.BarData import com.github.mikephil.charting.data.BarDataSet -import com.github.mikephil.charting.data.BarEntry import com.javinator9889.handwashingreminder.R import com.javinator9889.handwashingreminder.activities.base.BaseFragmentView import com.javinator9889.handwashingreminder.activities.base.LayoutVisibilityChange @@ -39,10 +40,15 @@ import com.javinator9889.handwashingreminder.activities.views.fragments.diseases import com.javinator9889.handwashingreminder.activities.views.viewmodels.DiseaseInformationViewModel import com.javinator9889.handwashingreminder.activities.views.viewmodels.SavedViewModelFactory import com.javinator9889.handwashingreminder.data.ParsedHTMLText +import com.javinator9889.handwashingreminder.data.room.entities.Handwashing +import com.javinator9889.handwashingreminder.data.viewmodels.HandwashingViewModel +import com.javinator9889.handwashingreminder.utils.calendar.CalendarUtils +import com.javinator9889.handwashingreminder.utils.toBarEntry import com.mikepenz.fastadapter.FastAdapter import com.mikepenz.fastadapter.GenericItem import com.mikepenz.fastadapter.adapters.ItemAdapter import com.mikepenz.fastadapter.listeners.ClickEventHook +import kotlinx.android.synthetic.main.handwash_count.* import kotlinx.android.synthetic.main.handwash_count.view.* import kotlinx.android.synthetic.main.loading_recycler_view.* import kotlinx.android.synthetic.main.loading_recycler_view.view.* @@ -52,7 +58,6 @@ import timber.log.Timber class DiseasesFragment : BaseFragmentView(), LayoutVisibilityChange { override val layoutId: Int = R.layout.main_disease_view -// override val layoutId: Int = R.layout.loading_recycler_view private lateinit var parsedHTMLTexts: List private lateinit var fastAdapter: FastAdapter @@ -62,6 +67,7 @@ class DiseasesFragment : BaseFragmentView(), LayoutVisibilityChange { private val informationViewModel: DiseaseInformationViewModel by viewModels { SavedViewModelFactory(DiseaseInformationViewModel.Factory, this) } + private val handwashingViewModel: HandwashingViewModel by activityViewModels() init { lifecycleScope.launch { @@ -88,6 +94,20 @@ class DiseasesFragment : BaseFragmentView(), LayoutVisibilityChange { loading.visibility = View.INVISIBLE container.visibility = View.VISIBLE }) + handwashingViewModel.allData.observe(viewLifecycleOwner) { + val dataSet = BarDataSet(it.toBarEntry(), "label") + countChart.data = BarData(dataSet) + countChart.notifyDataSetChanged() + countChart.setVisibleXRangeMaximum(7F) + countChart.moveViewToX(0F) + val todayAmount = + handwashingViewModel.getAsync(CalendarUtils.today.time) + lifecycleScope.launch { + val count = todayAmount.await()?.amount ?: return@launch + countDailyTextView.text = + "Today you washed your hands $count times" + } + } } } } @@ -103,28 +123,26 @@ class DiseasesFragment : BaseFragmentView(), LayoutVisibilityChange { } fastAdapter.addEventHook(DiseaseClickEventHook()) fastAdapter.withSavedInstanceState(savedInstanceState) - val entries = listOf( - BarEntry(0F, 12F), - BarEntry(-1F, 16F), - BarEntry(-2F, 11F), - BarEntry(-3F, 11F), - BarEntry(-4F, 13F), - BarEntry(-5F, 3F), - BarEntry(-6F, 8F), - BarEntry(-7F, 1F), - BarEntry(-8F, 20F), - BarEntry(-9F, 12F), - BarEntry(-10F, 8F) - ) - val barDataSet = BarDataSet(entries, "label") - view.countChart.data = BarData(barDataSet) view.countChart.setDrawGridBackground(false) - view.countChart.setVisibleXRangeMaximum(7F) - view.countChart.moveViewToX(0F) view.countChart.axisLeft.setDrawGridLines(false) view.countChart.axisRight.setDrawGridLines(false) view.countChart.xAxis.setDrawGridLines(false) view.countChart.invalidate() + view.countUpButton.setOnClickListener { + lifecycleScope.launch { + val createdItem = + handwashingViewModel.getAsync(CalendarUtils.today.time) + .await() + if (createdItem == null) + handwashingViewModel.create( + Handwashing( + CalendarUtils.today.time, + 0 + ) + ) + handwashingViewModel.increment(CalendarUtils.today.time) + } + } } override fun onSaveInstanceState(outState: Bundle) { diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/data/repositories/HandwashingRepository.kt b/app/src/main/java/com/javinator9889/handwashingreminder/data/repositories/HandwashingRepository.kt new file mode 100644 index 0000000..13d0d6b --- /dev/null +++ b/app/src/main/java/com/javinator9889/handwashingreminder/data/repositories/HandwashingRepository.kt @@ -0,0 +1,37 @@ +/* + * Copyright © 2020 - present | Handwashing reminder by Javinator9889 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see https://www.gnu.org/licenses/. + * + * Created by Javinator9889 on 24/06/20 - Handwashing reminder. + */ +package com.javinator9889.handwashingreminder.data.repositories + +import androidx.lifecycle.LiveData +import com.javinator9889.handwashingreminder.data.room.dao.HandwashingDao +import com.javinator9889.handwashingreminder.data.room.entities.Handwashing +import java.util.* + +class HandwashingRepository(private val dao: HandwashingDao) { + val allData: LiveData> = dao.getAll() + + suspend fun create(handwashing: Handwashing) = dao.create(handwashing) + suspend fun increment(date: Date) = dao.increment(date) + suspend fun decrement(date: Date) = dao.decrement(date) + suspend fun update(handwashing: Handwashing) = dao.update(handwashing) + suspend fun get(date: Date) = dao.get(date) + suspend fun getBetween(from: Date, to: Date) = dao.getBetween(from, to) + suspend fun delete(date: Date) = dao.delete(date) + suspend fun delete(handwashing: Handwashing) = dao.delete(handwashing) +} \ No newline at end of file diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/data/room/dao/HandwashingDao.kt b/app/src/main/java/com/javinator9889/handwashingreminder/data/room/dao/HandwashingDao.kt new file mode 100644 index 0000000..b483c6b --- /dev/null +++ b/app/src/main/java/com/javinator9889/handwashingreminder/data/room/dao/HandwashingDao.kt @@ -0,0 +1,54 @@ +/* + * Copyright © 2020 - present | Handwashing reminder by Javinator9889 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see https://www.gnu.org/licenses/. + * + * Created by Javinator9889 on 24/06/20 - Handwashing reminder. + */ +package com.javinator9889.handwashingreminder.data.room.dao + +import androidx.lifecycle.LiveData +import androidx.room.* +import com.javinator9889.handwashingreminder.data.room.entities.Handwashing +import java.util.* + +@Dao +interface HandwashingDao { + @Query("SELECT * FROM handwashing ORDER BY date ASC") + fun getAll(): LiveData> + + @Query("SELECT * FROM handwashing WHERE date BETWEEN :from AND :to") + suspend fun getBetween(from: Date, to: Date): List + + @Query("SELECT * FROM handwashing WHERE date == :date") + suspend fun get(date: Date): Handwashing? + + @Insert(onConflict = OnConflictStrategy.IGNORE) + suspend fun create(handwashing: Handwashing) + + @Update + suspend fun update(handwashing: Handwashing) + + @Query("UPDATE handwashing SET amount = amount + 1 WHERE date == :date") + suspend fun increment(date: Date) + + @Query("UPDATE handwashing SET amount = amount - 1 WHERE date == :date") + suspend fun decrement(date: Date) + + @Query("DELETE FROM handwashing WHERE date == :date") + suspend fun delete(date: Date) + + @Delete + suspend fun delete(handwashing: Handwashing) +} \ No newline at end of file diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/data/room/db/HandwashingDatabase.kt b/app/src/main/java/com/javinator9889/handwashingreminder/data/room/db/HandwashingDatabase.kt new file mode 100644 index 0000000..f015888 --- /dev/null +++ b/app/src/main/java/com/javinator9889/handwashingreminder/data/room/db/HandwashingDatabase.kt @@ -0,0 +1,52 @@ +/* + * Copyright © 2020 - present | Handwashing reminder by Javinator9889 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see https://www.gnu.org/licenses/. + * + * Created by Javinator9889 on 24/06/20 - Handwashing reminder. + */ +package com.javinator9889.handwashingreminder.data.room.db + +import android.content.Context +import androidx.room.Database +import androidx.room.Room +import androidx.room.RoomDatabase +import androidx.room.TypeConverters +import com.javinator9889.handwashingreminder.data.room.dao.HandwashingDao +import com.javinator9889.handwashingreminder.data.room.entities.Handwashing +import com.javinator9889.handwashingreminder.utils.room.Converters + +@Database(entities = [Handwashing::class], version = 1, exportSchema = false) +@TypeConverters(Converters::class) +abstract class HandwashingDatabase : RoomDatabase() { + abstract fun handwashingDao(): HandwashingDao + + companion object { + @Volatile + private var instance: HandwashingDatabase? = null + + fun getDatabase(context: Context): HandwashingDatabase { + instance?.let { return it } + synchronized(this) { + val instance = Room.databaseBuilder( + context.applicationContext, + HandwashingDatabase::class.java, + "handwashing_db" + ).build() + this.instance = instance + return instance + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/data/room/entities/Handwashing.kt b/app/src/main/java/com/javinator9889/handwashingreminder/data/room/entities/Handwashing.kt new file mode 100644 index 0000000..7c3414c --- /dev/null +++ b/app/src/main/java/com/javinator9889/handwashingreminder/data/room/entities/Handwashing.kt @@ -0,0 +1,30 @@ +/* + * Copyright © 2020 - present | Handwashing reminder by Javinator9889 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see https://www.gnu.org/licenses/. + * + * Created by Javinator9889 on 24/06/20 - Handwashing reminder. + */ +package com.javinator9889.handwashingreminder.data.room.entities + +import androidx.room.ColumnInfo +import androidx.room.Entity +import androidx.room.PrimaryKey +import java.util.* + +@Entity(tableName = "handwashing") +data class Handwashing( + @PrimaryKey @ColumnInfo(name = "date", index = true) val date: Date, + @ColumnInfo(name = "amount") val amount: Int +) \ No newline at end of file diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/data/viewmodels/HandwashingViewModel.kt b/app/src/main/java/com/javinator9889/handwashingreminder/data/viewmodels/HandwashingViewModel.kt new file mode 100644 index 0000000..8403d8e --- /dev/null +++ b/app/src/main/java/com/javinator9889/handwashingreminder/data/viewmodels/HandwashingViewModel.kt @@ -0,0 +1,95 @@ +/* + * Copyright © 2020 - present | Handwashing reminder by Javinator9889 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see https://www.gnu.org/licenses/. + * + * Created by Javinator9889 on 24/06/20 - Handwashing reminder. + */ +package com.javinator9889.handwashingreminder.data.viewmodels + +import android.app.Application +import androidx.lifecycle.AndroidViewModel +import androidx.lifecycle.LiveData +import androidx.lifecycle.viewModelScope +import com.javinator9889.handwashingreminder.data.repositories.HandwashingRepository +import com.javinator9889.handwashingreminder.data.room.db.HandwashingDatabase +import com.javinator9889.handwashingreminder.data.room.entities.Handwashing +import com.javinator9889.handwashingreminder.utils.calendar.CalendarUtils +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.async +import kotlinx.coroutines.launch +import java.util.* + +class HandwashingViewModel(application: Application) : + AndroidViewModel(application) { + private val repository: HandwashingRepository + val allData: LiveData> + + init { + val handwashingDao = + HandwashingDatabase.getDatabase(application).handwashingDao() + repository = HandwashingRepository(handwashingDao) + allData = repository.allData + } + + fun create(handwashing: Handwashing) = + viewModelScope.launch(Dispatchers.IO) { repository.create(handwashing) } + + fun increment(date: Date) = + viewModelScope.launch(Dispatchers.IO) { repository.increment(date) } + + fun decrement(date: Date) = + viewModelScope.launch(Dispatchers.IO) { repository.decrement(date) } + + fun update(handwashing: Handwashing) = + viewModelScope.launch(Dispatchers.IO) { repository.update(handwashing) } + + fun getAsync(date: Date) = + viewModelScope.async(Dispatchers.IO) { repository.get(date) } + + fun getBetweenAsync(from: Date, to: Date) = + viewModelScope.async(Dispatchers.IO) { repository.getBetween(from, to) } + + fun getWeeklyAsync() = getBetweenAsync( + from = CalendarUtils.lastWeek.time, + to = CalendarUtils.today.time + ) + + fun getMonthlyAsync() = getBetweenAsync( + from = CalendarUtils.lastMonth.time, + to = CalendarUtils.today.time + ) + + fun getWeeklyCountAsync() = viewModelScope.async { + val weeklyData = getWeeklyAsync().await() + var amount = 0 + for (data in weeklyData) + amount += data.amount + amount + } + + fun getMonthlyCountAsync() = viewModelScope.async { + val monthlyData = getMonthlyAsync().await() + var amount = 0 + for (data in monthlyData) + amount += data.amount + amount + } + + fun delete(date: Date) = + viewModelScope.launch(Dispatchers.IO) { repository.delete(date) } + + fun delete(handwashing: Handwashing) = + viewModelScope.launch(Dispatchers.IO) { repository.delete(handwashing) } +} \ No newline at end of file diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/utils/Collections.kt b/app/src/main/java/com/javinator9889/handwashingreminder/utils/Collections.kt index d5afe54..1d55204 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/utils/Collections.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/utils/Collections.kt @@ -18,7 +18,11 @@ */ package com.javinator9889.handwashingreminder.utils +import com.github.mikephil.charting.data.BarEntry +import com.javinator9889.handwashingreminder.data.room.entities.Handwashing +import com.javinator9889.handwashingreminder.utils.calendar.CalendarUtils import java.util.* +import java.util.concurrent.TimeUnit import kotlin.Comparator import kotlin.collections.ArrayList import kotlin.math.abs @@ -45,4 +49,19 @@ fun List.closest(): Date { val diff2 = abs(date2.time - now) return@Comparator diff1.compareTo(diff2) }) +} + +fun List.toBarEntry(): List { + val entryBars = mutableListOf() + for (entry in this) { + val daysBetween = + (CalendarUtils.today.time.time - entry.date.time).run { + TimeUnit.DAYS.convert(this, TimeUnit.MILLISECONDS).toFloat() + } + + entryBars.add( + BarEntry(daysBetween, entry.amount.toFloat()) + ) + } + return entryBars } \ No newline at end of file diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/utils/Time.kt b/app/src/main/java/com/javinator9889/handwashingreminder/utils/Time.kt index f49c0c6..16301a5 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/utils/Time.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/utils/Time.kt @@ -19,52 +19,15 @@ package com.javinator9889.handwashingreminder.utils import androidx.annotation.IntRange -import java.time.* +import java.time.LocalDateTime +import java.time.LocalTime +import java.time.ZoneId +import java.time.ZoneOffset import java.time.temporal.ChronoUnit import java.util.* -import kotlin.math.abs - fun formatTime(time: Int) = if (time < 10) "0$time" else time.toString() -fun runAt( - @IntRange(from = 0, to = 23) hour: Int, - @IntRange(from = 0, to = 59) minute: Int -): Long = - if (isAtLeast(AndroidVersion.O)) { - // trigger at hour:minute - val alarmTime = LocalTime.of(hour, minute) - var now = LocalDateTime.now().truncatedTo(ChronoUnit.MINUTES) - val nowTime = now.toLocalTime() - // check if is the same time or if today's time has passed so - // then schedule for next day - if (nowTime == alarmTime || nowTime.isAfter(alarmTime)) { - now = now.plusDays(1) - } - now = now - .withHour(alarmTime.hour) - .withMinute(alarmTime.minute) - abs(Duration.between(LocalDateTime.now(), now).toMillis()) - } else { - // get current time - val now = Calendar.getInstance() - // get again current time but truncate it to minutes and with the - // specified hour:minute provided - val alarm = Calendar.getInstance().apply { - set(Calendar.HOUR_OF_DAY, hour) - set(Calendar.MINUTE, minute) - set(Calendar.SECOND, 0) - } - val nowTime = now.time - val alarmTime = alarm.time - // check if they are the same time or if today's time has passed so - // then schedule for next day - if (nowTime == alarmTime || nowTime.after(alarmTime)) { - alarm.add(Calendar.HOUR_OF_DAY, 24) - } - abs(alarm.timeInMillis - now.timeInMillis) - } - fun timeAt( @IntRange(from = 0, to = 23) hour: Int, @IntRange(from = 0, to = 59) minute: Int diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/utils/calendar/Calendar.kt b/app/src/main/java/com/javinator9889/handwashingreminder/utils/calendar/Calendar.kt new file mode 100644 index 0000000..28118a4 --- /dev/null +++ b/app/src/main/java/com/javinator9889/handwashingreminder/utils/calendar/Calendar.kt @@ -0,0 +1,44 @@ +/* + * Copyright © 2020 - present | Handwashing reminder by Javinator9889 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see https://www.gnu.org/licenses/. + * + * Created by Javinator9889 on 24/06/20 - Handwashing reminder. + */ +package com.javinator9889.handwashingreminder.utils.calendar + +import java.util.* + +object CalendarUtils { + val today: Calendar + get() = with(Calendar.getInstance()) { + this[Calendar.HOUR_OF_DAY] = 0 + this[Calendar.MINUTE] = 0 + this[Calendar.SECOND] = 0 + this[Calendar.MILLISECOND] = 0 + this + } + + val lastWeek: Calendar + get() = with(today) { + today.add(Calendar.DAY_OF_YEAR, -7) + today + } + + val lastMonth: Calendar + get() = with(today) { + today.add(Calendar.MONTH, -1) + today + } +} \ No newline at end of file diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/utils/room/Converters.kt b/app/src/main/java/com/javinator9889/handwashingreminder/utils/room/Converters.kt new file mode 100644 index 0000000..1ba8dbd --- /dev/null +++ b/app/src/main/java/com/javinator9889/handwashingreminder/utils/room/Converters.kt @@ -0,0 +1,30 @@ +/* + * Copyright © 2020 - present | Handwashing reminder by Javinator9889 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see https://www.gnu.org/licenses/. + * + * Created by Javinator9889 on 24/06/20 - Handwashing reminder. + */ +package com.javinator9889.handwashingreminder.utils.room + +import androidx.room.TypeConverter +import java.util.* + +class Converters { + @TypeConverter + fun fromTimestamp(value: Long?): Date? = value?.let { Date(it) } + + @TypeConverter + fun dateToTimestamp(date: Date?): Long? = date?.time +} \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index db512ab..a89146e 100644 --- a/gradle.properties +++ b/gradle.properties @@ -18,3 +18,5 @@ kotlin.code.style=official org.gradle.caching=true org.gradle.jvmargs=-Xms512M -Xmx2048M -XX\:MaxPermSize\=512M -XX\:MaxMetaspaceSize\=512M -Dkotlin.daemon.jvm.options\="-Xmx2048M" org.gradle.parallel=true +kapt.use.worker.api=true +kapt.include.compile.classpath=false From f7154ade660cc27f7b5e6ef03e571ff53db68db6 Mon Sep 17 00:00:00 2001 From: Javinator9889 Date: Wed, 24 Jun 2020 21:09:47 +0200 Subject: [PATCH 53/95] Swipe refresh layout for news view --- .../activities/MainActivity.kt | 22 ++++++++++-------- .../views/fragments/news/NewsFragment.kt | 23 ++++++++++++++++++- .../views/fragments/news/adapter/News.kt | 5 ++-- .../graphics/CustomGraphicsModule.java | 6 ++++- .../main/res/layout/loading_recycler_view.xml | 1 + app/src/main/res/layout/refreshing_layout.xml | 9 ++++++++ 6 files changed, 51 insertions(+), 15 deletions(-) create mode 100644 app/src/main/res/layout/refreshing_layout.xml diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/activities/MainActivity.kt b/app/src/main/java/com/javinator9889/handwashingreminder/activities/MainActivity.kt index 6a18693..59c306d 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/activities/MainActivity.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/activities/MainActivity.kt @@ -33,6 +33,7 @@ import com.google.firebase.remoteconfig.ktx.remoteConfig import com.javinator9889.handwashingreminder.R import com.javinator9889.handwashingreminder.activities.support.ActionBarBase import com.javinator9889.handwashingreminder.activities.views.fragments.diseases.DiseasesFragment +import com.javinator9889.handwashingreminder.activities.views.fragments.news.NewsFragment import com.javinator9889.handwashingreminder.activities.views.fragments.washinghands.WashingHandsFragment import com.javinator9889.handwashingreminder.custom.libraries.AppRate import com.javinator9889.handwashingreminder.data.MainActivityDataHandler @@ -48,7 +49,8 @@ import uk.co.deanwild.materialshowcaseview.MaterialShowcaseSequence class MainActivity : ActionBarBase(), - BottomNavigationView.OnNavigationItemSelectedListener { + BottomNavigationView.OnNavigationItemSelectedListener, + BottomNavigationView.OnNavigationItemReselectedListener { override val layoutId: Int = R.layout.activity_main private val dataHandler = MainActivityDataHandler() private lateinit var deferredRating: Deferred @@ -60,6 +62,7 @@ class MainActivity : ActionBarBase(), with(Firebase.remoteConfig) { fetchAndActivate() } launch { dataHandler.setMenuIcons(menu, this@MainActivity) } menu.setOnNavigationItemSelectedListener(this@MainActivity) + menu.setOnNavigationItemReselectedListener(this@MainActivity) deferredShowcase = dataHandler.asyncLoadShowcase( activity = this@MainActivity, lifecycleOwner = this@MainActivity @@ -93,15 +96,6 @@ class MainActivity : ActionBarBase(), dataHandler.loadFragmentView(supportFragmentManager) } - /*override fun onPostCreate(savedInstanceState: Bundle?) { - super.onPostCreate(savedInstanceState) - savedInstanceState.notNull { - Timber.d("Activity recreated") - } - if (savedInstanceState == null) - dataHandler.loadFragmentView(supportFragmentManager) - }*/ - override fun onDestroy() { dataHandler.clear() super.onDestroy() @@ -166,6 +160,14 @@ class MainActivity : ActionBarBase(), return onItemSelected(item.itemId) } + override fun onNavigationItemReselected(item: MenuItem) { + when (item.itemId) { + R.id.news -> with(dataHandler.activeFragment as NewsFragment) { + goTop() + } + } + } + private fun onItemSelected(@IdRes id: Int): Boolean { return try { loadFragment(id) diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/news/NewsFragment.kt b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/news/NewsFragment.kt index 369c84d..5ace171 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/news/NewsFragment.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/news/NewsFragment.kt @@ -29,6 +29,7 @@ import androidx.lifecycle.lifecycleScope import androidx.lifecycle.whenStarted import androidx.recyclerview.widget.DefaultItemAnimator import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.LinearSmoothScroller import androidx.recyclerview.widget.RecyclerView import com.afollestad.materialdialogs.MaterialDialog import com.javinator9889.handwashingreminder.R @@ -46,12 +47,13 @@ import com.mikepenz.fastadapter.scroll.EndlessRecyclerOnScrollListener import com.mikepenz.fastadapter.ui.items.ProgressItem import kotlinx.android.synthetic.main.loading_recycler_view.* import kotlinx.android.synthetic.main.loading_recycler_view.view.* +import kotlinx.android.synthetic.main.refreshing_layout.* import kotlinx.coroutines.launch import timber.log.Timber class NewsFragment : BaseFragmentView(), LayoutVisibilityChange { @LayoutRes - override val layoutId: Int = R.layout.loading_recycler_view + override val layoutId: Int = R.layout.refreshing_layout private lateinit var fastAdapter: FastAdapter private lateinit var footerAdapter: GenericItemAdapter private var viewCreated = false @@ -63,6 +65,7 @@ class NewsFragment : BaseFragmentView(), LayoutVisibilityChange { lifecycleScope.launch { whenStarted { loading.visibility = View.VISIBLE + refreshLayout.isEnabled = false newsViewModel.newsData.observe(viewLifecycleOwner, Observer { if (::footerAdapter.isInitialized) footerAdapter.clear() @@ -80,6 +83,7 @@ class NewsFragment : BaseFragmentView(), LayoutVisibilityChange { newsAdapter.add(newsObject) loading.visibility = View.INVISIBLE container.visibility = View.VISIBLE + refreshLayout.isEnabled = true activeItems.add(it.id) } }) @@ -120,6 +124,15 @@ class NewsFragment : BaseFragmentView(), LayoutVisibilityChange { fastAdapter.addEventHooks(listOf(NewsClickHook(), ShareClickHook())) fastAdapter.withSavedInstanceState(savedInstanceState) viewCreated = savedInstanceState == null + refreshLayout.setOnRefreshListener { + refreshLayout.isRefreshing = true + newsAdapter.clear() + activeItems.clear() + lifecycleScope.launch { + newsViewModel.populateData(language = UserProperties.language) + }.invokeOnCompletion { refreshLayout.isRefreshing = false } + container.visibility = View.INVISIBLE + } } override fun onSaveInstanceState(outState: Bundle) { @@ -127,6 +140,14 @@ class NewsFragment : BaseFragmentView(), LayoutVisibilityChange { fastAdapter.saveInstanceState(outState) } + fun goTop() { + val smoothScroller = object : LinearSmoothScroller(requireContext()) { + override fun getVerticalSnapPreference(): Int = SNAP_TO_START + } + smoothScroller.targetPosition = 0 + container.layoutManager?.startSmoothScroll(smoothScroller) + } + private inner class NewsClickHook : ClickEventHook() { override fun onBind(viewHolder: RecyclerView.ViewHolder) = if (viewHolder is News.ViewHolder) viewHolder.cardContainer diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/news/adapter/News.kt b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/news/adapter/News.kt index 8d5371c..b5bea1e 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/news/adapter/News.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/news/adapter/News.kt @@ -27,7 +27,6 @@ import com.airbnb.lottie.LottieAnimationView import com.google.android.material.card.MaterialCardView import com.javinator9889.handwashingreminder.R import com.javinator9889.handwashingreminder.graphics.GlideApp -import com.javinator9889.handwashingreminder.utils.isHighPerformingDevice import com.mikepenz.fastadapter.FastAdapter import com.mikepenz.fastadapter.items.AbstractItem import kotlinx.coroutines.* @@ -61,9 +60,9 @@ data class News( val cardContainer: MaterialCardView = view.findViewById(R.id.root) val shareImage: ImageView = view.findViewById(R.id.share) - init { + /*init { setIsRecyclable(!isHighPerformingDevice()) - } + }*/ @SuppressLint("SetTextI18n") override fun bindView(item: News, payloads: List) { diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/graphics/CustomGraphicsModule.java b/app/src/main/java/com/javinator9889/handwashingreminder/graphics/CustomGraphicsModule.java index fda998a..c1a1739 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/graphics/CustomGraphicsModule.java +++ b/app/src/main/java/com/javinator9889/handwashingreminder/graphics/CustomGraphicsModule.java @@ -29,6 +29,8 @@ import com.bumptech.glide.module.AppGlideModule; import com.bumptech.glide.request.RequestOptions; +import static com.javinator9889.handwashingreminder.utils.AndroidKt.isHighPerformingDevice; + @GlideModule public final class CustomGraphicsModule extends AppGlideModule { @Override @@ -37,7 +39,9 @@ public void applyOptions(@NonNull Context context, @NonNull GlideBuilder builder new RequestOptions() .format(DecodeFormat.PREFER_ARGB_8888) ); - int diskCacheSizeBytes = 1024 * 1024 * 20; // 20 MB + int diskCacheSizeBytes = isHighPerformingDevice() + ? (2 << 20) * 200 + : (2 << 20) * 20; // 200 MB or 20 MB builder.setDiskCache( new InternalCacheDiskCacheFactory(context, diskCacheSizeBytes) ); diff --git a/app/src/main/res/layout/loading_recycler_view.xml b/app/src/main/res/layout/loading_recycler_view.xml index aa8201c..44e8cf6 100644 --- a/app/src/main/res/layout/loading_recycler_view.xml +++ b/app/src/main/res/layout/loading_recycler_view.xml @@ -24,4 +24,5 @@ app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> + \ No newline at end of file diff --git a/app/src/main/res/layout/refreshing_layout.xml b/app/src/main/res/layout/refreshing_layout.xml new file mode 100644 index 0000000..a7d5744 --- /dev/null +++ b/app/src/main/res/layout/refreshing_layout.xml @@ -0,0 +1,9 @@ + + + + + + \ No newline at end of file From 3222c4efab7dc9559bb88e825999072fcb687d61 Mon Sep 17 00:00:00 2001 From: Javinator9889 Date: Fri, 26 Jun 2020 14:41:55 +0200 Subject: [PATCH 54/95] Reviewed new model for main fragment screen (isse #13) - need tests with Lottie performance impacts --- app/build.gradle | 2 +- .../fragments/diseases/DiseasesFragment.kt | 151 ++++++++--- .../viewmodels/DiseaseInformationViewModel.kt | 80 +++--- .../data/room/dao/HandwashingDao.kt | 2 +- .../data/viewmodels/HandwashingViewModel.kt | 6 +- .../main/res/drawable/ic_calendar_month.xml | 9 + .../main/res/drawable/ic_calendar_today.xml | 9 + .../main/res/drawable/ic_calendar_week.xml | 9 + app/src/main/res/layout/handwash_count.xml | 250 +++++++++++------- app/src/main/res/layout/main_disease_view.xml | 2 +- app/src/main/res/raw/leaves.json | 1 + app/src/main/res/values-es/strings.xml | 10 + app/src/main/res/values/dimens.xml | 1 + app/src/main/res/values/strings.xml | 10 + extras/25160-leaves.json | 1 + extras/calendar_today-black-24dp.svg | 1 + extras/date_range-black-24dp.svg | 1 + extras/today-black-24dp.svg | 1 + 18 files changed, 373 insertions(+), 173 deletions(-) create mode 100644 app/src/main/res/drawable/ic_calendar_month.xml create mode 100644 app/src/main/res/drawable/ic_calendar_today.xml create mode 100644 app/src/main/res/drawable/ic_calendar_week.xml create mode 100644 app/src/main/res/raw/leaves.json create mode 100644 extras/25160-leaves.json create mode 100644 extras/calendar_today-black-24dp.svg create mode 100644 extras/date_range-black-24dp.svg create mode 100644 extras/today-black-24dp.svg diff --git a/app/build.gradle b/app/build.gradle index 54e985a..5d8f177 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -40,7 +40,7 @@ android { applicationId "com.javinator9889.handwashingreminder" minSdkVersion 17 targetSdkVersion 29 - versionCode 126 + versionCode 130 versionName "1.2.0-${gitCommitHash}" multiDexEnabled true resConfigs "en", "es" diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/diseases/DiseasesFragment.kt b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/diseases/DiseasesFragment.kt index 9ae7c9c..c6d69c0 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/diseases/DiseasesFragment.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/diseases/DiseasesFragment.kt @@ -21,17 +21,23 @@ package com.javinator9889.handwashingreminder.activities.views.fragments.disease import android.content.Intent import android.os.Bundle import android.view.View +import android.widget.LinearLayout +import androidx.annotation.StringRes import androidx.core.app.ActivityCompat +import androidx.core.view.ViewCompat +import androidx.emoji.text.EmojiCompat import androidx.fragment.app.activityViewModels import androidx.fragment.app.viewModels -import androidx.lifecycle.Observer import androidx.lifecycle.lifecycleScope import androidx.lifecycle.observe -import androidx.lifecycle.whenStarted import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import com.github.mikephil.charting.data.BarData import com.github.mikephil.charting.data.BarDataSet +import com.google.android.material.bottomsheet.BottomSheetBehavior +import com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_COLLAPSED +import com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_EXPANDED +import com.google.android.material.textview.MaterialTextView import com.javinator9889.handwashingreminder.R import com.javinator9889.handwashingreminder.activities.base.BaseFragmentView import com.javinator9889.handwashingreminder.activities.base.LayoutVisibilityChange @@ -42,6 +48,7 @@ import com.javinator9889.handwashingreminder.activities.views.viewmodels.SavedVi import com.javinator9889.handwashingreminder.data.ParsedHTMLText import com.javinator9889.handwashingreminder.data.room.entities.Handwashing import com.javinator9889.handwashingreminder.data.viewmodels.HandwashingViewModel +import com.javinator9889.handwashingreminder.emoji.EmojiLoader import com.javinator9889.handwashingreminder.utils.calendar.CalendarUtils import com.javinator9889.handwashingreminder.utils.toBarEntry import com.mikepenz.fastadapter.FastAdapter @@ -52,6 +59,8 @@ import kotlinx.android.synthetic.main.handwash_count.* import kotlinx.android.synthetic.main.handwash_count.view.* import kotlinx.android.synthetic.main.loading_recycler_view.* import kotlinx.android.synthetic.main.loading_recycler_view.view.* +import kotlinx.android.synthetic.main.main_disease_view.view.* +import kotlinx.coroutines.Deferred import kotlinx.coroutines.launch import timber.log.Timber @@ -61,6 +70,8 @@ class DiseasesFragment : BaseFragmentView(), LayoutVisibilityChange { private lateinit var parsedHTMLTexts: List private lateinit var fastAdapter: FastAdapter + private lateinit var emojiLoader: Deferred + private lateinit var behavior: BottomSheetBehavior private val upperAdsAdapter: ItemAdapter = ItemAdapter() private val lowerAdsAdapter: ItemAdapter = ItemAdapter() private val diseasesAdapter: ItemAdapter = ItemAdapter() @@ -70,31 +81,32 @@ class DiseasesFragment : BaseFragmentView(), LayoutVisibilityChange { private val handwashingViewModel: HandwashingViewModel by activityViewModels() init { - lifecycleScope.launch { - whenStarted { - loading.visibility = View.VISIBLE - informationViewModel.parsedHTMLText - .observe(viewLifecycleOwner, Observer { - if (it.isEmpty()) - return@Observer - parsedHTMLTexts = it - upperAdsAdapter.add(Ads()) - lowerAdsAdapter.add(Ads()) - it.forEachIndexed { i, parsedText -> - val animation = - if (i % 2 == 0) R.raw.virus_red - else R.raw.virus_loader - val layoutId = - if (i % 2 == 0) R.layout.disease_card_layout - else R.layout.disease_card_alt_layout - diseasesAdapter.add( - Disease(animation, parsedText, layoutId, i) - ) - } - loading.visibility = View.INVISIBLE - container.visibility = View.VISIBLE - }) - handwashingViewModel.allData.observe(viewLifecycleOwner) { + lifecycleScope.launchWhenStarted { + loading.visibility = View.VISIBLE + countLoader.visibility = View.VISIBLE + informationViewModel.parsedHTMLText.observe(viewLifecycleOwner) { + Timber.d("Parsed HTML text changed - $it | ${it.isEmpty()}") + if (it.isEmpty()) + return@observe + parsedHTMLTexts = it + upperAdsAdapter.add(Ads()) + lowerAdsAdapter.add(Ads()) + it.forEachIndexed { i, parsedText -> + val animation = + if (i % 2 == 0) R.raw.virus_red + else R.raw.virus_loader + val layoutId = + if (i % 2 == 0) R.layout.disease_card_layout + else R.layout.disease_card_alt_layout + diseasesAdapter.add( + Disease(animation, parsedText, layoutId, i) + ) + } + loading.visibility = View.INVISIBLE + container.visibility = View.VISIBLE + } + handwashingViewModel.allData.observe(viewLifecycleOwner) { + lifecycleScope.launch { val dataSet = BarDataSet(it.toBarEntry(), "label") countChart.data = BarData(dataSet) countChart.notifyDataSetChanged() @@ -102,11 +114,27 @@ class DiseasesFragment : BaseFragmentView(), LayoutVisibilityChange { countChart.moveViewToX(0F) val todayAmount = handwashingViewModel.getAsync(CalendarUtils.today.time) - lifecycleScope.launch { - val count = todayAmount.await()?.amount ?: return@launch - countDailyTextView.text = - "Today you washed your hands $count times" - } + val weeklyAmount = + handwashingViewModel.getWeeklyCountAsync() + val monthlyAmount = + handwashingViewModel.getMonthlyCountAsync() + setCountText( + countDailyTextView, + R.string.today_washed, + todayAmount.await()?.amount ?: 0 + ) + setCountText( + countWeeklyTextView, + R.string.week_washed, + weeklyAmount.await() + ) + setCountText( + countMonthlyTextView, + R.string.month_washed, + monthlyAmount.await() + ) + countLoader.visibility = View.INVISIBLE + scrollView.visibility = View.VISIBLE } } } @@ -114,6 +142,7 @@ class DiseasesFragment : BaseFragmentView(), LayoutVisibilityChange { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) + emojiLoader = EmojiLoader.loadAsync(view.context) val adapters = listOf(upperAdsAdapter, diseasesAdapter, lowerAdsAdapter) fastAdapter = FastAdapter.with(adapters) val rvManager = LinearLayoutManager(context) @@ -123,11 +152,16 @@ class DiseasesFragment : BaseFragmentView(), LayoutVisibilityChange { } fastAdapter.addEventHook(DiseaseClickEventHook()) fastAdapter.withSavedInstanceState(savedInstanceState) + behavior = BottomSheetBehavior.from(view.contentLayout) view.countChart.setDrawGridBackground(false) view.countChart.axisLeft.setDrawGridLines(false) view.countChart.axisRight.setDrawGridLines(false) view.countChart.xAxis.setDrawGridLines(false) view.countChart.invalidate() + ViewCompat.setElevation( + view.contentLayout, + resources.getDimension(R.dimen.menu_elevation) + ) view.countUpButton.setOnClickListener { lifecycleScope.launch { val createdItem = @@ -141,6 +175,36 @@ class DiseasesFragment : BaseFragmentView(), LayoutVisibilityChange { ) ) handwashingViewModel.increment(CalendarUtils.today.time) + leaves.visibility = View.VISIBLE + if (!leaves.isAnimating) + leaves.playAnimation() + } + } + view.countDownButton.setOnClickListener { + lifecycleScope.launch { + val createdItem = + handwashingViewModel.getAsync(CalendarUtils.today.time) + .await() + if (createdItem == null) + handwashingViewModel.create( + Handwashing( + CalendarUtils.today.time, + 0 + ) + ) + handwashingViewModel.decrement(CalendarUtils.today.time) + } + } + lifecycleScope.launch { + val countUpText = getText(R.string.add_another) + val countDownText = getText(R.string.reduce_count) + val emojiCompat = emojiLoader.await() + try { + countUpButton.text = emojiCompat.process(countUpText) + countDownButton.text = emojiCompat.process(countDownText) + } catch (_: IllegalStateException) { + countUpButton.text = countUpText + countDownButton.text = countDownText } } } @@ -151,6 +215,12 @@ class DiseasesFragment : BaseFragmentView(), LayoutVisibilityChange { } fun onBackPressed() { + if (::behavior.isInitialized) { + if (behavior.state == STATE_EXPANDED) { + behavior.state = STATE_COLLAPSED + return + } + } try { container.adapter = null diseasesAdapter.clear() @@ -166,6 +236,23 @@ class DiseasesFragment : BaseFragmentView(), LayoutVisibilityChange { lifecycleScope.launchWhenCreated { informationViewModel.parseHtml() } } + private suspend fun setCountText( + view: MaterialTextView, + @StringRes id: Int, + times: Int + ) { + val emojiCompat = emojiLoader.await() + val text = getString( + id, + resources.getQuantityString(R.plurals.times, times, times) + ) + view.text = try { + emojiCompat.process(text) + } catch (_: IllegalStateException) { + text + } + } + private inner class DiseaseClickEventHook : ClickEventHook() { override fun onBind(viewHolder: RecyclerView.ViewHolder) = if (viewHolder is Disease.ViewHolder) viewHolder.cardContainer diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/viewmodels/DiseaseInformationViewModel.kt b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/viewmodels/DiseaseInformationViewModel.kt index 64c5b1e..276d164 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/viewmodels/DiseaseInformationViewModel.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/viewmodels/DiseaseInformationViewModel.kt @@ -34,6 +34,7 @@ import com.javinator9889.handwashingreminder.utils.notNull import kotlinx.coroutines.* import org.sufficientlysecure.htmltextview.HtmlFormatter import org.sufficientlysecure.htmltextview.HtmlFormatterBuilder +import timber.log.Timber private const val DATA_KEY = "text:html:text" private const val PARSED_JSON_KEY = "text:json:parsed" @@ -57,49 +58,48 @@ class DiseaseInformationViewModel( ?: DiseasesList(emptyList()) } - fun parseHtml() { - viewModelScope.launch { - if (!state.get>(DATA_KEY).isNullOrEmpty()) - return@launch - val parsedItemsList = - ArrayList(informationList.diseases.size) - val deferreds = mutableListOf>>() - informationList.diseases.forEach { disease -> - deferreds.add( - listOf( - async { createHTML(disease.name) }, - async { createHTML(disease.shortDescription) }, - async { createHTML(disease.longDescription) }, - async { createHTML(disease.provider) }, - async { createHTML(disease.website) }, - async { createHTML(disease.symptoms) }, - async { createHTML(disease.prevention) } - ) + fun parseHtml() = viewModelScope.launch { + Timber.d("Parsing HTML") + if (!state.get>(DATA_KEY).isNullOrEmpty()) + return@launch + val parsedItemsList = + ArrayList(informationList.diseases.size) + val deferreds = mutableListOf>>() + informationList.diseases.forEach { disease -> + deferreds.add( + listOf( + async { createHTML(disease.name) }, + async { createHTML(disease.shortDescription) }, + async { createHTML(disease.longDescription) }, + async { createHTML(disease.provider) }, + async { createHTML(disease.website) }, + async { createHTML(disease.symptoms) }, + async { createHTML(disease.prevention) } ) - } - deferreds.forEachIndexed { i, htmlData -> - launch { - val data = htmlData.awaitAll() - parsedItemsList.add( - i, ParsedHTMLText( - name = data[0], - shortDescription = data[1], - longDescription = data[2], - provider = data[3], - website = data[4], - symptoms = data[5], - prevention = data[6] - ) + ) + } + deferreds.forEachIndexed { i, htmlData -> + launch { + val data = htmlData.awaitAll() + parsedItemsList.add( + i, ParsedHTMLText( + name = data[0], + shortDescription = data[1], + longDescription = data[2], + provider = data[3], + website = data[4], + symptoms = data[5], + prevention = data[6] ) - withContext(Dispatchers.Main) { + ) + withContext(Dispatchers.Main) { + state[DATA_KEY] = parsedItemsList + } + }.invokeOnCompletion { + it.notNull { + viewModelScope.launch(context = Dispatchers.Main) { state[DATA_KEY] = parsedItemsList - } - }.invokeOnCompletion { - it.notNull { - viewModelScope.launch(context = Dispatchers.Main) { - state[DATA_KEY] = parsedItemsList - parsedHTMLText.value = parsedItemsList - } + parsedHTMLText.value = parsedItemsList } } } diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/data/room/dao/HandwashingDao.kt b/app/src/main/java/com/javinator9889/handwashingreminder/data/room/dao/HandwashingDao.kt index b483c6b..b2bda5a 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/data/room/dao/HandwashingDao.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/data/room/dao/HandwashingDao.kt @@ -43,7 +43,7 @@ interface HandwashingDao { @Query("UPDATE handwashing SET amount = amount + 1 WHERE date == :date") suspend fun increment(date: Date) - @Query("UPDATE handwashing SET amount = amount - 1 WHERE date == :date") + @Query("UPDATE handwashing SET amount = CASE WHEN (amount == 0) THEN 0 ELSE amount - 1 END WHERE date == :date") suspend fun decrement(date: Date) @Query("DELETE FROM handwashing WHERE date == :date") diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/data/viewmodels/HandwashingViewModel.kt b/app/src/main/java/com/javinator9889/handwashingreminder/data/viewmodels/HandwashingViewModel.kt index 8403d8e..97d6cf3 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/data/viewmodels/HandwashingViewModel.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/data/viewmodels/HandwashingViewModel.kt @@ -55,7 +55,7 @@ class HandwashingViewModel(application: Application) : fun update(handwashing: Handwashing) = viewModelScope.launch(Dispatchers.IO) { repository.update(handwashing) } - fun getAsync(date: Date) = + fun getAsync(date: Date = CalendarUtils.today.time) = viewModelScope.async(Dispatchers.IO) { repository.get(date) } fun getBetweenAsync(from: Date, to: Date) = @@ -71,6 +71,10 @@ class HandwashingViewModel(application: Application) : to = CalendarUtils.today.time ) + fun getTodayCountAsync() = viewModelScope.async { + return@async getAsync().await()?.amount + } + fun getWeeklyCountAsync() = viewModelScope.async { val weeklyData = getWeeklyAsync().await() var amount = 0 diff --git a/app/src/main/res/drawable/ic_calendar_month.xml b/app/src/main/res/drawable/ic_calendar_month.xml new file mode 100644 index 0000000..4e887d9 --- /dev/null +++ b/app/src/main/res/drawable/ic_calendar_month.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_calendar_today.xml b/app/src/main/res/drawable/ic_calendar_today.xml new file mode 100644 index 0000000..d8c83b9 --- /dev/null +++ b/app/src/main/res/drawable/ic_calendar_today.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_calendar_week.xml b/app/src/main/res/drawable/ic_calendar_week.xml new file mode 100644 index 0000000..0d93165 --- /dev/null +++ b/app/src/main/res/drawable/ic_calendar_week.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/layout/handwash_count.xml b/app/src/main/res/layout/handwash_count.xml index b101679..b8c67ee 100644 --- a/app/src/main/res/layout/handwash_count.xml +++ b/app/src/main/res/layout/handwash_count.xml @@ -1,112 +1,168 @@ - + android:layout_height="match_parent"> - + + + android:layout_height="match_parent" + android:fillViewport="true" + android:visibility="invisible" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent"> - + android:layout_height="wrap_content"> - + - + - + + - + - + - + - - \ No newline at end of file + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/main_disease_view.xml b/app/src/main/res/layout/main_disease_view.xml index e80380b..e5e2822 100644 --- a/app/src/main/res/layout/main_disease_view.xml +++ b/app/src/main/res/layout/main_disease_view.xml @@ -43,7 +43,7 @@ diff --git a/app/src/main/res/raw/leaves.json b/app/src/main/res/raw/leaves.json new file mode 100644 index 0000000..49b5ab4 --- /dev/null +++ b/app/src/main/res/raw/leaves.json @@ -0,0 +1 @@ +{"v":"5.6.10","fr":30,"ip":0,"op":105,"w":512,"h":512,"nm":"tree-1781554","ddd":0,"assets":[{"id":"image_0","w":952,"h":954,"u":"","p":"data:image/png;base64,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","e":1},{"id":"image_1","w":58,"h":106,"u":"","p":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADoAAABqCAMAAADa67LtAAAAJHpUWHRDcmVhdG9yAAAImXNMyU9KVXBMK0ktUnBNS0tNLikGAEF6Bs5qehXFAAAACXBIWXMAAAABAAAAAQBPJcTWAAAA+VBMVEVHcEzI6jvW6joe6tNO7o0U7+RH9VnJ6ztE8os7pP9J9Fw0sPov+I1Y80OV7jwa9c0Z5uk39ncf+7MY+sY2qv1+7zwg+q8m+aPa6jrF6zsj+qk79m9L9E4c+7sQ+9gz94GE7zwkz/Fh8kUoyPMox/Oe7Twc4eoN+uM59nFO80FH9FAS8+UN/OIQ9uQU7ucb4esX6OkM/eIe2+0Q/NkS/NIW+8kj0fAZ+8Ed+7gg+rAox/Mk+ad/7z0n+Z957z1y8D4r+Jcu+I6G7j1l8T5r8T4tvfYx94aM7jw1935e8j5Y8j+T7Tw/9WVC9Vwzsfqd7Tyr7Dy56zs5pf5yJmjnAAAAJ3RSTlMAQP4vIGNa/hBANIF5nV1zordVgsLc1ECArqeX17zQ26raelC84ECyq+TNAAADUElEQVRYw63YZ1fbMBQGYKVRjDCxTwaJCQlZBcpqAyU0UOiktIVC1///MfWUh14N+1Qf0XlA1hXSvZcQPuoNUnU0Fq2qtLU4rWpni9NXFe1rnx53q8j9kL6ssleNiL6oYFsxrWB3E3pS2r7h9KxTTtYzdFkvRZ9n6PlFKbuRpctVGbuboxd9VuIY5umqhH1boJc7xhss0Ktt0w0W6XXTcIMBfWd2NFqIfrRN6Cakn0zsHaYTVpl+bWvluox+H2pjI6UHli42cnpgV6ePVB0bFR2Z03GBPjkq+jNDz1pFemQZ0pNxq0iP5Fu1nqNndYGOmCGl4yI9lH7uRp52GgI9tMxol6wEek+N6Jh0RSqJ7maeLgkV6MP93ISeU7Ij0lvbhHZIB9AeitDfAvXTgr5IvzgG1L++u4B+qOnpmBCKaE9Pl/5XbQP6XtjldYF6hHiI3lAtDdKnPqJTLQ2euSain2s66u8TYZD2dPQiiP42ot9cHfX3iXiQ7ulomGZOEP3hamiYDjQh3dPQy+DnDNJfrpquwti3Id3T0DAZ6ED6u6amUTo+gXSgplHa1IT0D1PScJ8IxdRV0yjBbEM6UNMoabIgXeOUIRrneZjW5LeET/vR1BDSLSW9iqZsSAfSezikXjQ3QXRNTeMazUL0mey5yuXDDFKqpNfx5BDRmuRVj2n8my0lpZAmmfRIRQmkSfI/BHRLna3Fh4LYSopzRMZXrKD1O1XZMFd9K5khmlQrTElhscILLEdFKUxqk1lLRYuVZES9ZMXyMxw0CBBtpitWUDIDtJ2uWPb/mu1L5BJ4PvsguyV4D0ZamDlKug8o/1hbdq3F1aRI07pshC/TJAQLsVjhkw6+wnl5JlKPrzhLt0ABLNC06L7NUJArsrG8CHUyFDYKjouFGX8PLfi+ZiNUpGmlAV/13KGSlr4Op7J6qZGnEz5hJXQgb7Lli1B+zzOU/BRtjqbhmYKUS7BZmoZnHtGpujGYren4ptgRpZpG0GVK0/D0AjrVNpF4nfOYOVABrWmbHCytOPjPXDELx4Pn/nzF1KeuWeerH9H0QPVujP5osOhhlJmmkb1xienwJgHlK3Z7xHywpk/5HlOXlBm0/XTESMVhjeaV2/rEcmzyP8c/16Z1ezJhv0cAAAAASUVORK5CYII=","e":1},{"id":"image_2","w":58,"h":106,"u":"","p":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADoAAABqCAMAAADa67LtAAAAJHpUWHRDcmVhdG9yAAAImXNMyU9KVXBMK0ktUnBNS0tNLikGAEF6Bs5qehXFAAAACXBIWXMAAAABAAAAAQBPJcTWAAAAwFBMVEVHcEy80SG40zbp2w/P1hzv3QzX2Bqr0TrE1BvX2BoTrkvt3A3D1CPs3A231DojsUXu3A3g2hMQrExwwiw0tT+30zXv3Q52wyoPrUuWySD33gXI1RtZvTMGq03U2CPL1yrC1TL93wHg2hm81Dfp3BLv3Q210zzl2xDz3QYmskPY2SDP1yfG1i7b2R0Eq01PujdavTNmvy/43gZDuDpywix9xCg3tj6VySGJxiWgyx2szhq30Bbb1wvD0xPP1Q8TrknxkElrAAAAHnRSTlMAIH62MHxQQBBgPMqhm7xZQPCC197V2Wiom+jYoNDbFk4TAAADgUlEQVRYw63Ya3uaMBgG4CAIQaWIWs9d1WLt2q7d3Hnr4f//qxEO4ZAnB7iWj+O695QkhvcNIXxYPuk6ertRVzraPXa1l7vH6452l9DrXhfpMfrw4HeaJZb68MHvMksZ7WAvCnrf2t5w+sltJ50KjZxW1K3QQzvbq9J29qJGT0PaYhvWadzC3tToIY6H5hPcSI2PgfEEN1OPx77pBAupxzuzbTUCqXfnsdnagNTz2mR5b2Hqfk5b0zz1vNcvkYdTz/u9dolcWepeO809aerdadyOVlJPkaVeG0Xqy0b9u6nSYT315U15sN9W6Iegkfr26hvSeztopL7+dRTLWntXR6CX1JBawyb9s5KvTY26vkB/L8xon0QC/ekYUZsEIp3KdkSNRsQS6a+FCT1YxBbpd8+EusQFdErhjqjT5Cyci/Tb1oDahPQB/TLQ03lSgCE61dND8lY2oJ8nYB82aHIyjBF9trSUfTPmiG61NJkn0kf060BHk3kiFNKZjp7Y6geI/gh1lNU/Y0hnOpp+W9eIfgw1lM0T6UM6U9M4Yv9OIa3FgtQ4XXsb0islPcRpnehC+j5Qp2Y1yBrSpTo1nSfSh/SJKlPjrPfBNFSmxtnhaUO6VKdmnygf0idOKUrNi4g1pAP5KXHi5WUA6URO2fc1rzshXVY+6qCWyMuPIaJX0iM8Tc0LUx/RJ9nnKku18zmEdCClLHWdPwyU1EOpx/zgdJXUQqm8xNsAGuIKpkgtKssRoBN1tTYvWgolxTVi8dvaqKgDa8Sio+wppok1VyC16FaokvZQKq/eRypqwXq4eOqraKOTbPQ5VL6H2X+MUnlrtlJRcglS+cu60h9dGou6K/5UekpkCw9qf15TrpTUAx0Hf1lPdqzlkyz2dDZ/KDlMiyXYiX0Of7iq0iuxPRP7HN5aOVU6AQ2wkFo23dMKBbUi3TRTyyZ0VdJ3eFFw3ezp+PfQg9/X6go1e7ryFqikIW4D/EZq2fquOJX1S349dc4fLAq6lHZpfr1/5WUsRcVP09Y6ycrygJJLtJXUci8uMjpRdsK1no5PipNRS3MRFJW07HunjC61l0hDTsu/eMvoQHvJQXnHEZUvK1bhePDan28oK6Gh2c3XPKPlBcH02SiU/dFBSiM+x9vnkJiO8ZpVa3yOFzNiPmi/eqVhhaTNsOy3V0o6DnfTI52Hv/LI/xz/AG9w2PZ68exoAAAAAElFTkSuQmCC","e":1},{"id":"image_3","w":109,"h":53,"u":"","p":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAG0AAAA1CAMAAAB8+5X+AAAAJHpUWHRDcmVhdG9yAAAImXNMyU9KVXBMK0ktUnBNS0tNLikGAEF6Bs5qehXFAAAACXBIWXMAAAABAAAAAQBPJcTWAAAArlBMVEVHcExHgXdyUiYNwecZtNBRd2ZlYkI9kZQunKcvm6UcsMlEhX4PvuFDhoAopLQSvN5YcFotnakJxe0IxvBbblhyUyghqr9uVy5SdWNba1FeaU08jo00lpwrn6wnpLRLfXAiqb1PeGc9jIpDhX9ba1FHgngWttMersVjY0JUc2AbssxXcFlvVSs3kpYwmqI6kJBAiYUSu9xnXzt3TRxgZklqWzUHxvANweYunKcPvuGOHF3zAAAAHHRSTlMAxM1CI302EIdAzESkn2F0XcjKhiCQoHLurt3wCSH7ygAAAxJJREFUWMPtmFl7ojAYhQUEZJNFkWIXd3Sm2BZxqf//j03CZiJLEqS9mnPJzfucnHxL6PUeUV8ZDgoNlX7vx6QNuM/9fv/y8vHx/Qr0BjS3J0O5c5JsGc/LzxQGad85DWjbMVAzvr6WS0D7RGmvOW27/asPO0KNjdVqVdBeSt62kAZkdhCixm02KA0/yXlmDtKCQO8/mNbTbNNEQ7wFQNEDPFkELJy2r8stpQVRWx5gLWZARG83WgR0jUZyK9aCTJu/FbT0KKPr9eIIzKw/ELZIYBvCnZzfcktol4vTZ2MBEb2Vc8tpl4tJC7Oe3t8xGHVuBe18PjtU6UnT9XtOS3j1d7KcW+EN4M7k9Mb+GsIQbxT1Nq/yBjQiBOYe1+t15q19blfHGY1MQWg2Zx0TGHKSM5o7eaOpumkKdNdRmu6Ox4SH0ejqTZ3QYrJD3O12BSzPbUG+k+pkwDzBrd2hoDXlht1JMLaVNhOMPxxKtMaTbAmC8g6nnLYm52YbrUHwdvCn0wn1Vp/bszFQmBp8qeK8MERoiLe73DhDG7MaMUf3xhLYqTq3vJcYosa6dgoj3Tk7+Ec3hKr2lubGGRaTJVlIOKn698YAK6zLjRM1mdqLYJq6qqJdGW/LbhyHcY23J1+USARFgVv6xFbVfAZEWFc2MWMIDPM29a0K0ljTtPQNYAN9oH2yYgZc8AngxVDlk4QoSRJTGVwitN6yh0DFDIDegttJ6kiWfMJKaGFYeyfL05RyL8FgVpwJ8k509UbcFNDZje4JLsKqyI1tBlTuJQiMj+MSjrZP3u/KyF4S3HLDNiDJLXBV3ppnN8Veot9VqezxqbW43hvrXlLkplfNNAikyG3GsJcktLpNa+zyrLk1vAPS3K5Nb1TJ5ZvqjTm3gLSiACB9bvvG3CKVpotLHt9JvVE/Ncaez7IFVeWmMi16suVPjy125Sw3s8VK5Pnt6m3S9nkviX5DvVXmpj7260ITOXKfzHObKL3HJYFpSvamDjr8j6cBZD2tU1RxWzULbg0YzbYHwx/8O5lglUy9//pt/QOW6sIbig4V/AAAAABJRU5ErkJggg==","e":1},{"id":"image_4","w":79,"h":106,"u":"","p":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAE8AAABqCAMAAADKi/wxAAAAJHpUWHRDcmVhdG9yAAAImXNMyU9KVXBMK0ktUnBNS0tNLikGAEF6Bs5qehXFAAAACXBIWXMAAAABAAAAAQBPJcTWAAAAsVBMVEVHcEy30zfp2xLz3Qfg2hbs3A7u3A2rzie/0yLZ2R+ozy3Y2Bi61DO60iwHq04Vr0oOrUtrwS4UrkqYyyVLuTglsUTN1yjC1TH93wHy3QrT2CTs3A7X2SDg2hnk2xa20zvI1i333gV6xCnm2w9Quza91Datzhk3tj7H0xHd2Rza2R4Vr0huwS1mvzCHxiVdvTKQyCIAqk+YyiCgyx1EuDq60RUqs0LT1g7b2AshsUQJrEzu01uEAAAAFnRSTlMAfsnbWnukECQ9VJa32Yc4wtBinojg8k4vYQAABBlJREFUaN6tmml/ojAQxiNHAggeta0HFaX2sNfudrf39/9gi4jAhJkkKLyV379PZiZh5kkZwx7XY10+wWLhdsnrLxZXVncSxeUiAy6crni9jHd19fjARTe8qOAtLb8LnJcvd8dbPnSx5v7lQd8ySTpY87TO25yc5+AS8OLEOXm5gBfHJ9W2mAJekvHm1glBdNYNffO5fXzhhOumvuw5NohijelL09Q9drmovnQ2O64SQ0rfbGaLY7KL6st5s7h9VpwprW+WblpnJVTpS+O2QDFV6os3X/12e3eq0ff12QrY1+r73IYt0nyr17fdRsZAf2qgb/tuDOxNTfS935kCIzN9d3f/jA5t71bmPVC8vybAXoNnLSneHwNg2OS5JO+XHnjb4CViSfKudUAf47k0byx04WvyfI/m6YARwnMYp3nPSqBYITyXeQrey1h1tmA8zjKBNO/3uSJ8GM/K0qTi3dDAEOMl2Q+WindzRvEyHMLLisxX8t6GRPWtUH27T5qt5F0IInwob9cWOEre/YgIH8rLPxe2kneP5iTCeVY+Lql5H0gIvRXOs/NSj9U8JITBCs/vhhUCVbzvAZYOVN8m71k8De9piKYD4+07DK7hjZB04Pr2faSv4T3J22RF8fj+d0vD+xHy7sB5sVU0cjreQE4vEb+4eMPW8F6FnF5UX3JoSh0dD0RwQusrWkiRangjqVwofYdBgWt4r8NGuaD6+GFH6ngDqVwIffbhHa7hjerfNrL+4risKQ3vVSo/Ql81ddga3lAuZ1xfOSM4Gt6Zmb6yoxcbNW8AtgcZP6tq/015PZW+pDrE1byRAW/XT1aNHj+Zl+mbV2Ol04m+2mhudxC/Oa8ZjR3kt9pxWcmY8ZT1F8/rE6P5/iD11RLCfCOep9ZX90oik/OAqfXx+pfB5LzKz1NSXz0hTBidp6HifMn6ZzDDm5z3PWX86glhAcVrtmukPmBeRQbfN7J/yfWlHHyrDb6/+/aZ1GeDtRj0B2R/WvgHYDYN9f0L2Y8X8zTwmRyMNzSbPwp9wBUSTd7PheG8VeizwKsTfT+5i6BCXwq7uyZPmM3npV8CrcQGb2Dmb1T6oB07kXjfAp/QaX0wgIF2/igNIsLfncM3Ie9DkPYp6V/BAE4A78zcjz3EbwYDGNR5Fy387FIfDCCr84ZqC5Xwx+FrE908XSVljft/0CoOSt6bzlwUEepPcvhXS9652f1gQ58N3xkXvLGRMWsh/in05yYFb2hmlPeb/qlUMTp/qCFR1gcXLHLemJk/7qPk73pSADNeq8s5wSFPOmMyXtD2KpPXebYUwOdz1vrx+H5/5P4BWJ13PWbHPF4/PvhX8K5ifOzNpnDtPU/K8Am3kD7P/YhO7pcLNY6dxrzTfyPwXIs7gnXL1PD+A+5YMssNyq+nAAAAAElFTkSuQmCC","e":1},{"id":"image_5","w":58,"h":106,"u":"","p":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADoAAABqCAMAAADa67LtAAAAJHpUWHRDcmVhdG9yAAAImXNMyU9KVXBMK0ktUnBNS0tNLikGAEF6Bs5qehXFAAAACXBIWXMAAAABAAAAAQBPJcTWAAAA81BMVEVHcEwm87VL9FQz94Mi7cQh+bDW6jrI6zs4qf188EAq7a8Q/Nti71DW6jwa79eK8D8Z5uk69nEe+7czsfo193w2qv3S6zpS80InyfN08D7E6zsl+qQQ+9hy8D158D2S7jwu+I4e+rVa805A9WEkz/Eox/Or7DwN+uNb8j459nFO80FA9WFH9FAS8+UN/OIQ9uQU7ucb4esX6OkM/eIe2+0Q/NkS/NIW+8kj0fAZ+8Ed+7gg+rAox/Mk+ad/7z0n+Z957z1y8D4r+Jcu+I6G7j1r8T5l8T4tvfYx94aM7jw1936T7Twzsfqd7Tyr7Dy56zs5pf66o1QyAAAAJ3RSTlMAEJlHMH7+/kBeIGAQQ00porq3hWXCgudrQq6c0Nur3tjWe9javOC6cK4rAAADWElEQVRYw63YeXuaMBwH8AC6FkTR6ry1Hu3We7Nd7bRbd9/n+381A4EEyDcHeZY/i5+nIQnJ7xtCaPP6xLT1V01T2lxdm9qj1fVzQ/sipJdVE7m/o89qJqMU06cGtplQA3uc0qvS9iWlN41yspKh60op2sjQ27tStp2l600Ze5yjd3OrxDLM000J+6pAtwfaA8zR+5HuCubpa81PoQ3om47eCkb0vaNDH0P6QWd6v2A6s4zp15Z6gEX0u21OT1XD3BbTU8ecnnvyuZHRpT4dF+i5dKh+ZuhNs0gvXE16NW4WaS8Qz02O3lSK9GLp69JxkZ4JX7efp40aR89Er9vO0yrZcFT0ugU6JiOOnk0E05qna+Jx9ORkoUNvPXLA088DHdogDUAnaIb+Fmi4F855+snWoOH2XQX03UBNx2EBhuhETdfhbnYA6FtulPc5Gp7rDqIPgZJGZ8Yc0amSRsdcFdGPAxUNx4lYkB6q6F20648Q/eaqaFT/OJAequjubJ0h+sNV0F050IH0UEG30d8tSH+5crrZbfkjSJ8o6K5OdCD9XZfTuAaZQTqU07hsqkL6x5fSbZx9MHXlNK5AWpAO5TSuxTuQ7lFqIZrUeZjWxbtESOfxIxvSrpTeJ3UnpEPhPryjSfmxRHRPTpPCtIPoI9FxFdOklvYhDaT0dfLQRrQuONUTmtRLjpR6kKYl3lJGCaRp8W8D2pVXa7O0T1Lah4WeRXssoRVI05p0IXtXcoRomlZ8KYVhhQaslox6sKhNn7ri1cQnyXzO8XvCNRyNMaI0mtkySo4AbbEei77X7L1EroCnT3uiXYLewQiDmS2l+4DSkDMQbWtJmuRpK9NjuJmme+qKDyuE9Rhu4fRugqc0WgVZ2gUBmKMsdPcytM5TaywOoXaGwouCy2Iwo+ehC8/X7OsWKcuRjArSR00YfW1KRXmplqcsRropHQpTWi0fQml69VNaF2fDmiBwT0HJxdksZdOziGlXmoRzmY4OShDTQHERtGWU9XgS0anqtsFjOYf1eBrRgfKSw2KJg00PX4XjRmt/9vWE1NW7+UpKabagJg9a/zRaBElRS8d4+uAS3ebMIkp/705KXC9ai5DSMQ5cUqZ5rfML3/R+3VkujK/1SccekP/Z/gEsXGwqa2751AAAAABJRU5ErkJggg==","e":1},{"id":"image_6","w":81,"h":159,"u":"","p":"data:image/png;base64,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","e":1},{"id":"image_7","w":91,"h":124,"u":"","p":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFsAAAB8CAMAAAAxQTwlAAAAJHpUWHRDcmVhdG9yAAAImXNMyU9KVXBMK0ktUnBNS0tNLikGAEF6Bs5qehXFAAAACXBIWXMAAAABAAAAAQBPJcTWAAAAP1BMVEVHcEzsXCzfNCruZCzqVyvpUCvsYCzoVSzpUCvqVirtaS/vWyjsXyzlTCzwVSXoVS3jQyvhOyrfMyrdLCncJSgCZFRkAAAACnRSTlMAhOD/ZKLsPhXGN5MQSwAABixJREFUaN61mgu2gyAMRPUBAoJaP/tf60NUICGobW0XMOd2mAQIVtWHP1VXv/rxhvFfSbOWNeon0nXL3O8nroi2Zav68+DKSXvuVjwurdt252byF9JembX6UWl5SG/qT4LLpm0jN2P6B9K73+y5AorUBzdrHqc+1O1T4LxpsSNO/JHKdy2kbTN1ax+ofG4ZoczMA+BAOnXFfA3OrfvlymYl/xJ8lW4txW2+dZzbgea2q/hX4Bwpo9W0pv6iZIYSt5O234A76U2Z8HvNoDXmU/BVusztQ+jAv5OG2JHbrtzmk66iVunNkyL3upofgCs9dB57OONmn4Ar3W3YQ5l7W8z3wZ10FzxpSW6f7xX8zZ1TdJu2PcmJYV7ZmPd2zrpzv+HgLvi9BcW+Cc69dGo41WF9+XjwNw5Z8tUFbi8dlZu0n3hmr3678GX3SrlBTkQ4n9id3P/uFr7627C7HXtoE8Ml7oPmrRjql/sFbgtcaaqE2zC7+236e/UjNmU6J6KC++UufTOG/PUC3DAnMuXePPHy/Z0YyvEFuC3gdraC/dIE7hsxVK/xwKa4RRW5zXr4OQzvTX8ZQz168T2EA+7fEnPbwH25mvU4nnGvSYN+s8jdXJk97txJXSYrKSrAbRO7Td+frqYax4ObzolMub3hhyuO+3w19TglluR++39N95OV++w0wacxgJN+iwr6nRRPv4qXV1NOU7Ak5iTNN4fcdtvSgil92ZS/yYu/ytwKcKOi708iXm/S41j022Op7HzCjrXs+7royOSXspwTb4kscTvpvtCw9FTiDglUQRtw22BJT5vC5+kQL9XltlKAO+47+2pSpqhp9sqHKVQ/4RXm3oQPv/uCKWKegScUtyK4LeImTJHzrj0lEUT53pGA375T2UN41c5N0U56Btx5TniVczNrIXZePnJeoicrePfK8y0Jbht3nt3wzBSH7bXHck7aozvjfBsEjnqKXOYZcuf5DvfIGuY76bGkKTpoT0W/D0s2bYp7NwXuPmo5tGEEgd9NVeJmkBsaLhbITfXBOA0scpt9MVPD1bxAv4n+ba0scOOYQMP5Ej0p9u84IUXcLNkb8rLfLZmnk/5tRTqOBdwWrWVquFtJyJ3nJBblGirMnZkik3A76WU+r0uroDa8FwNL0pYiaG7odxLaEnck10nhIL9fRB+sqyK3xZYk2kvGPb7wvcHKE26TLSZcyqXMva5lWsdNlm+G/A5BkRk3zrfjTsuhRftlRh2DIjPuEdblys2hNgP3eZuJ8yI3zonjVmXuYDgRQrlkOcH9ewCbN5xXJXepYAnUvqjLdH+VKbcFp/sC93Kabwm1WWGfNzjgspDvtC6rEjezgBtrq4Wuy+RcJXJt0u+Mu7rON9i5+Y2cCNBPlpP+PUj4UJf4ncwhDJGTqr7KSYMeARPuZA5BNllO5btLuAWhHedVhHTcjS/3HXhQ0jDfYS1TU6KJfxd9UGFtnG9T6oOb4Vm+u8CNTuuIGxwisn1nrZ4833G/RAfqJpniGXjUpA4of3m+kzkbOpfmc2QW7sUG2+2Tkp1PIrfKtMG+k+8NQwUPbeX+jeyW19wws4I4fx/9pM61yfNJOMaii4M64eaX3BYups5vgKV8I7s5ObdPI4iHP4rOtwNvsm8KMm5YPJq6uhL5dp4IQhufq1hqiaKu3HS+8SRHl7j78n2+LuRbEtqk3yVH/FiazEk28mtKOfHzqoae4kky3xkH9S4VL2qlWZiguLF9inpPC9zFUZii8o3/o6S4j7nPyQRcpou5e4ITxfN3qThcP5sjCzTi6IamIuNNzX3OpV1W8F1KE9/jQO4w99EX02+5F08wPKsEjd9dzU1pZ2c4Vm3anIo3yMn+5qVvPB0JcK7KYqKy98uNW9x5lVJ/ALyqyjtDmEOwu49p6VNG91dREcTzk/uvrjLmO6/4muBu3nhe5ONlTFJu8dbLf3yXqsmYpNzNu6/Q4T2tFJPArd//wuoQP+1Un36KJ7aiV3Q32dX1Z99YKE3GO6345uPPn5R20g29lP6MXH/xRcsqrmm73U9/95GPE9eFqtTff38nNNW82+aRL/uEyO1unvr0jteocvSDX8WCJeP64Q80P//9A1mjB5KZFiSAAAAAAElFTkSuQmCC","e":1},{"id":"image_8","w":73,"h":156,"u":"","p":"data:image/png;base64,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","e":1},{"id":"image_9","w":77,"h":122,"u":"","p":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAE0AAAB6CAMAAADNqC6XAAAAJHpUWHRDcmVhdG9yAAAImXNMyU9KVXBMK0ktUnBNS0tNLikGAEF6Bs5qehXFAAAACXBIWXMAAAABAAAAAQBPJcTWAAAAqFBMVEVHcEw3fKORd2VieoUxfqnZczOKeGp9eXWpdlV+eHLnciorfqztcSUXf7mCeG4Rf72hdlpBfJtren/Mczzlciubd13ocinTczc1faO1dUzDdELKcz1AfJyEeG2ndlYjfq96eHSddlxZeopveXzccjCNd2cZfrfvcSQMf8BJe5ZQe5G6dUhieoWvdU+sdVK+dEahdlmYd2CTd2Mvfajici0rfavociloeoFd5XxuAAAAF3RSTlMAXZ+sMlR6EzFagYLAq8zWvMzgzqDZ3a2z8rEAAAReSURBVGjevZrpduIwDIUdstghUCBAC4WWlraULjMDtGXe/80mIQteJMUJOeO/4Xzn6spxJBnG9CV81uLyF2PeHm28uB2LtmB8sbj98yFaC3Rxm+BaMm+S0eZBK7RFTpt3WoCJm4I271ye2m5J+/V7ejFuLNHuL8bdyLT73WU7xddoj+Iy2zTaXlxkm07bh81fqxuTdggvsM2kvYTNbQNoTXFjmNYMx59h2t1dkzPAR2kPDc6ALkprgusRtPq4Z4r25Na1jaI9PIU1baNp9XCTKtrfOrjrKlodnPiu1Pb30xrnf1dr+/z0bJNgRbPF9axo67Ud7tuSFnOrJOi0IUx7H3GbJBi0IUhbr0c2STAiFTDt/d21SIJB41OE9uZUvgmAthCjVeH40aQFDKW90fvEB2gdFmC0JZ3YLkAbMo7SlqO6tB1jHZS2jKiUArR7zgTm23I5IzJxPB5NWlIgDVFtsxmeiVdIW/JRDgkamgkfpKVfvSlO++rXog2TJwHqW4JDrOuCtF26rXFts68vONYJTEt/3CFoP3CsPZgWnsQRtBW4644w7VRpXeG+/ax+oG3yCtPSNLCQ0rYCYhUIbcpycTht4wAbBKY9np4GFG214ta0U/PBCd8ScZG53V6h93T3mBW8HUrbZuMBNFBbVlEKmtY3Ni9CG2bPr0iaLq6H0fbZ85DybbMZmDTQt32eMFrb1rPUlhdsAU3ra68CRsu7GE7TVHE4LU8DcynfNttIp8G+HYp3j9Y2sNNWpIHFJE0JlaAVjX1A0/p2tKKZ5KRvSqi4b/uyW3NJbXKohLZpeQbitBTnWNEO5fEVk9oiO1o5XwlQ31LawMq3Q9nTc1LbwE7buWl2Kd+2drQyDcyjtEm0a4L2cv6KxIRvW5vzTZn8BJQ2bnG+JbTzaIVTvnnVX5mUdnWOwSW0WdJezrTQyjef8k1KA4ttcipIbVIzH9jsN6ziymjS0IfbvAtYNZjRpDQwFznfFFqXot1JP3QwbX2lgKNoQgm18kRCepmCFiihwjSlKJxQ2uTZm4f45lX1pyVNTgOLYW2VnXg5U72Ti9EI3G/9qgmGNF2Rh1Ec1KaVq5yiKRPaEeSbV2MqpRjnQNpqTfO4EqrpW7/WFFSZ4rmmNqfWvFcxzjHOtw14/YTSFON4RaVK3i+cOkquhqrR4B56gtJCNdQlVeGXMYwxWqCGqtKwQYtYIDTFOOaSvYyCg+eWinEO2WcpF6gwTTGOkz2ggrO5Xxide+fVgB7FgbSYaaGW2irmhP4HNO8VWqgFbVA5+50DtEALtaBVTkSZGJs0dSwbFbRKaWkkQ3Oyre2RnOYwm9Wh72V4Thswu+XrNDXUUUazvW9IotUm26pxxIwLXgFxZ+SdaF4NWiJPpqmhprSo5v2bPAWNDeNqX8FzaTYYasY5rP4S5cTMVY0bsUYrLCY/ymd11vivBmFs3rV5rPkK44TmstZWmHSULf6Bh3mu67SIY9yJ2P9a/wDoln8oZFJ6SgAAAABJRU5ErkJggg==","e":1},{"id":"image_10","w":77,"h":122,"u":"","p":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAE0AAAB6CAMAAADNqC6XAAAAJHpUWHRDcmVhdG9yAAAImXNMyU9KVXBMK0ktUnBNS0tNLikGAEF6Bs5qehXFAAAACXBIWXMAAAABAAAAAQBPJcTWAAAAxlBMVEVHcEwOo1KJS30JpVG4NYCuSHC5L4YJpVGXVG/GSWLlVz8MpFIpkFvrYDKHTHy/M38xil7INnfqXjPWRVu4LIvpXTWITH1vXXQNo1LKNnZXbmzTQWQEqE/bS1IBq04sjF3QPmkflVm/KIzLOHLELoITnlToWzihOoXXRlrtYi3UQ2DgUkatMYrjVkBHeWdOdGk1hmBUcGtBfWQ7gWLHM3pabG1gZ29mY3FtX3NzWnV5Vnh/UnqFTXyLSX6SRYCYQILeT0y3Ko2R6U16AAAAHXRSTlMAxL6BXUOf/hEnWUkokXtzZLq6zdzi6aCg5M6H3YTrOv0AAARGSURBVGjevdlpe5swDABghxpqSAIhZ9M2vZs2PbZsbbeea/f//9RykdhGkgXhGV/J80aWDNiyEPbVaIkKr9Z0X1Wn1aYP+42qMHU8ffj5rSqutdC+V5S8vaX2oxrueLrUngYVYI3jVWxPvwbbl3Zno/3efqbUNO15a+5U115ew62wuqX1t+J2LO1tK65ma+8fsvxjdZrX/sjyaQO0T1k6bZB2JcumDdTKceoS0e6SMmlDtbugRNpw7W9xrkZoZ4W5S0orytVp7UwWTButXRTi9lxaIe7QqRXgGrtu7ZHN1Tnao88tAksbMl+fNZZ2fs7jdpnakPMlaxzZ2gDWzjsMrp7TvAGsnaSMIuS0Zx/RTtyPbC2vKQ/Rbpxvz0MgNolpN5HjLX6U12LRh7UZFzqKkNcGIkY1urA7gOYJhWrXnaLaixADVLs+IEsKaEqEuDYiKrELabMXhodr13gljiAtFsLHtS+0EnVQm6/L+6g2+mpimw5Q82Z3YlwbfUVoSQHtdT6tKe0LTt0erM0TExDabRubIJA2/wooSruNkAkCafH8nkdptyE8QSBtXgYhKe2+Db3GYa2/uNultPsInG6Q9ra4m5BaT7G1xcdYkdp9E5puoLYogwgobTwOi2khGdu4DUxeUPOW91MytrGfn7yg9r68L8nYJm2utsoJHdskZGqrQSS01sw9CrC2LIMIac0MDtdWZRABpVnB4Vp/9Qufjq3H0z6y56ZLxmYMldCyuZSQsRlDJbQ4W6jQWo+nZWUQAanpQyW0rAzCJ/M2iVjan/Xrq0vG1uRp60c6IWNr87R4vWAkY2Nq6zLM6kDENuFp6zLM6kDENtGX0Lj2ufmKDOH3m63VKG3zZk14sZFavFm4U3nz3V+ZubYpgwiI2BRP+9xokjXSOqVdaY/gkKM1SE3bzCec+YatuJaatuUL0dh67tXgUtPKIFLGk4Wte1d7QO2HkqM1SE1bGaghkremcy+TafrmNkBii6x1Da7pO28fiS107U/XWlf/5RCOzbkT33Qw9D8+AGNr23sjQtObUSEYm7WWVlSvxmhZdFzfeldXykhcAsVWqJunzKHmtGahLqjRxUvzWlSo32skTua0MdTmOkU1I3HKsVLNZgnefzO2P6mtwXvoPVQzEhdZWg/pJtVYXXdlfU+b7pM7UzMSNxuqERvaGGlMkb6lModK7bP0WYdo0hyqHhvVA25xzhc6Wmw9+ngX1MzEJVpsjj5hC+z3GqkOtZ24s/cLaYk1VLxLkOP285rZ5T3I+iHO0OZF2893ts05ksUWCc41oM9l1ErrCd7l25o1R5Ya97xBKM/qbJuJI3pc8BX38TMjf6EVOvoNPV0zhzoiWo3YJfUuaC5xhY/gVYCcZ80SF4niV+hlWmAmrlPuzNfvrror+sjUqPTpu+zmz9p8Uf6Ss83uRSoqu2R68RhWxwk/TWWFnAjlgfhf1z+KRQLZH3C47QAAAABJRU5ErkJggg==","e":1},{"id":"image_11","w":75,"h":124,"u":"","p":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEsAAAB8CAMAAAAW773NAAAAJHpUWHRDcmVhdG9yAAAImXNMyU9KVXBMK0ktUnBNS0tNLikGAEF6Bs5qehXFAAAACXBIWXMAAAABAAAAAQBPJcTWAAAAn1BMVEVHcEypZGiMJZGKIJSnYWvLszfTxSuraWaoaGukWW/XzyWLIpSudmO+lkm5iVGsbGSEEp27jU+tbWOACaLAmUiDDp/OujKdSnnGpz6hU3OKH5SYPYGTM4e3hVSRLovAmkeGFpqnYWqze1qOJ4+9k0ukWm+BC6G6jU+ra2TKsDnTxCzNtjWbRXyfT3aZQn7PvDGVOYTXziWvdF/CnkTDoUKCFThUAAAAF3RSTlMAn1uifEGzNw/FdDYgiLZVwmbYgNna3K2z7zEAAAQsSURBVGjerdrreqIwEAbg0AIJKiDWdq2t4qFSa4+6e//Xth6o5DAzJIB/d5/3mfkINsnImNtH+KyzT/Rx0xXl/fn46ow6Wv1OKP/zZN12QaWPZ+tp0J7ij6XVvjC++bW+REtKjK7WU8tlIaJNZf20XA1L2eq3o2SrVfrpUrF+npunz7e61Tj9wdawfhqvBtN6bpi+twWsZun7h+0ZU615k/TTwwGqa94gfX6Aredhg9xXSF1z528eb3WxzLzmrulnqxVa171jWDPCcktfzChr7dRkUllAXuu1wxKLZyfrgNa1tm8yeJ3RPdo3Kca11tS2yeRVtsC8ppZNZi+lReQ1tWuSv7y81vdo1+QYsiLDsmkyzjXrnJdnWBZNBnllVXlF0adu1TcpxnkO9OjrPa6ni9om47+gxc28apsM/sEWA6yFqOlQsa55eaBFNxlPgLq2y00KWmSTwWQC9yhAa0F1+IBYEWOPkNWnOtSs37x8xLrDtyFvkwmcF0cs/E9IiFjLEUMsdFUEb5jlodYNFrxplXmlx3+FLWRV9N7RuuSzgmIVcGCDd9TyUGtRgBuLELd8woICC74h65IXJywosPAbrWvECOseKgu3vPNmE7EKAZSFWyluFUBgwe4bz0uQdRmvZLhD6zpEjLAWxdAoC7e2PiPrKoyyCItfriawvLTVOtjtiLwYYZ3qUsPv7Yi6PNIywt/tjxRmpXV1KeH39lRdgzpLWfnhnshrVP6nDWrJD1LsibpWHm2dsL7WIlpXSlqnsmQrpOo6lK+uIOqSHuSeskbGHYyZV2UFpOVZWEOtRczirhZR1/WwvEHfR8naU1ZyPcMTdV0X2IC0fCcrIC3uZPVIq7pZtbXQ55i4W2hdWWeWFFcHdVUv2qatlXRo+bVWoXyxUs+RS1e1Nu8jtVaZnXVrYSWW1p3Fux1L13NUXtV39AOaF6+3znUJi+8vZmEtlL+PPcxK5KEJUZf0pwP9vo8tLfmshuWV2lnaFgCuS95XLfG87tStCWiNmYVVaFs5eD+hRM/RuvSzArzPyews7ZQM778CZRqAWcYFMPgclRtu1Oqbu3vTSqws82QF7aNjZVaIWcBxG9jfZxbWFLpSAM4dXFlesAXfvJvnIWZhwRfvxjlNiZ7DFnbLpJ8f41oLv/jVz7WZuiQAixhTqOdtZdVD1nxI3MkN1LrUUTRgkeOmnmwpXzhsZFr05Eo8SFaiLQndqhuCBZKlP0bNqr89DisrIy2Li2hRWfpjdJ+xBldLaCNW2bIcGIa/lv4YKyuynT2K0lKXhDwLiOxHOeVdbYLMaz88lwnT5Q5ZWRJpZTmOHceG5V0t7jpzHB8tZUlEpRW5D1a5vrzKeUejn61kec6V6E91RZw1+sS5surLu+2GnyRTB9xNi7pUJmGjbcsf+FR7QuG1/U1Ig89/KERy//wbfUMAAAAASUVORK5CYII=","e":1},{"id":"image_12","w":77,"h":120,"u":"","p":"data:image/png;base64,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","e":1},{"id":"image_13","w":107,"h":194,"u":"","p":"data:image/png;base64,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","e":1},{"id":"image_14","w":104,"h":194,"u":"","p":"data:image/png;base64,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","e":1},{"id":"image_15","w":110,"h":192,"u":"","p":"data:image/png;base64,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","e":1},{"id":"image_16","w":89,"h":189,"u":"","p":"data:image/png;base64,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","e":1},{"id":"image_17","w":80,"h":162,"u":"","p":"data:image/png;base64,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","e":1},{"id":"image_18","w":82,"h":193,"u":"","p":"data:image/png;base64,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","e":1},{"id":"image_19","w":104,"h":183,"u":"","p":"data:image/png;base64,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","e":1},{"id":"image_20","w":89,"h":188,"u":"","p":"data:image/png;base64,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","e":1},{"id":"image_21","w":176,"h":97,"u":"","p":"data:image/png;base64,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","e":1},{"id":"image_22","w":144,"h":143,"u":"","p":"data:image/png;base64,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","e":1},{"id":"image_23","w":142,"h":88,"u":"","p":"data:image/png;base64,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","e":1},{"id":"image_24","w":112,"h":83,"u":"","p":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAHAAAABTCAMAAABwBnPhAAAAJHpUWHRDcmVhdG9yAAAImXNMyU9KVXBMK0ktUnBNS0tNLikGAEF6Bs5qehXFAAAACXBIWXMAAAABAAAAAQBPJcTWAAAA3lBMVEVHcEwkZIIwqlZavVDX3Fstj2l/xlIpSIs1l2XT2lrk311CuU3r4l2/1Ffr4l4khG8jaoG+01YprlHm3lsijWxdv08oVYYhgHcimmEpSYuJyFJkwU8jpFfN1ligzVXi21nl3VtkwE9Xvk4rtUsilWXv5V8ijWt8xlGFx1Jvw1CSylOoz1Xq4V1Hu00ihnE/uU02t0yx0FUksU3C1FckrFHP11cjpVcjoFvc2Vgjm2AhenwjbIEhgHb362N1xFGhzVSdzFROvE6LyFOYy1S30Va70lYjqFTI1VcmW4bV2FjNYsnuAAAAH3RSTlMAYWE9EiWD/hA9c5DCWJKE36HQ59W4O7DptODnp8rIiTVi+gAAA9ZJREFUaN7tmWl3mkAUQGVRGQQRFHcDmpDESPZFo6WtbdKS//+HChqpy5thZsBvma+e4+W+ZZh5FAr8S5HEUmWzRFGSCkdcsmj3hsPhaDRaXl/f3t7dnZ4+PDxYfeM4VN2++D4ej3eBEfHp6enlxRKLeeO6lxcxcAgA/7xMp9PXtiHmift1uQVc7gMj4uvr2VnbyMdTPrm62gGOMMCz8/OGoWTX+/mbHnh+c9PKpqmYPxiBN99aAj8PnUwOgcsU4Ld5jReJJpMJu2FE5ETq9/dcwPl8vlhwINW3LMBFWGuy8cx3DHBJB+wwKpqP7xkMawJrQ5qPGYCtJnP+zL/8QJ4drvpMAJJzyLW56c/PfIYNvo7XP/iADc43FJpxAdu8L0RUTwGCOeTGFeT6jN2wTSiVtBbRHGbglFSZzRRg1WEG9ol9J5C7UnXSgbs5tMh/WOwQf0eOw2ZokQ+lilEukwuGEZhSmkK5TAZqDhOwQt7FhFrM6xALxnXoc5iSPKEWhjGQsNnpjkNteG2JKbhFuAY2SQl0qYHkaEa4xQZISKBLabhc2qRoKkJtfaZZAfEpVCMenWGP1ApFY3VMTIAGtgNdl9KwRMA1W1unthWwiU1gDKQwJERTERs7x8QVENsRCY4IJESz2G/vnUtjIC6FuuvSGJYUvNzqSHMAFNIDigfaEl4OOnmH5bCs4DqCwrCEk7MwR/2I2KEIKAZow88q9vF3iwgo4ANKNuyC0ZQqxMtMiIuo5qcZQsVSFK14TkMEwhHVfZdseCIBibPv1pMhIlCAA+qTDQ+LRbSp7odhCLe87/sEwxP5IHG0F9IQ3EdRzPNxhhN9n9ajvwGHRUxA8Ybmjp5U6g3HDFdusGRUPwEeGL7pO7Qu45U7hF4UyPP+C+4ZbulFNPahQg1sQc9PFHcNByh5qFKXa4oB9YTq+RjDjZ5u8k4xIEHZ90DDjzr6pEVtyDfFmIOCVQ82rEZ6sm5mGZuAglHFAIazSE9Ws0wxVsAmWDH7hvGqInXAcQPeA7bAioEMtbozm33wTjESYBHaYzwPyuEsDyCmYsAcOjkAoYDKQQAbst6AISC0a2uBFxzLEAooWgtyGaYO2cFNNDiaIchTg09g/jlsgCe1ehBsYpqzIcyrBlkMSUCYJwfBkQxh3lrwGDlswbxPwfwNcddrdZuXXw4b2LsxqmpJSHMzbBspY1+1qiWK2XPYppvlyzHW9X3fzWZoiUyfDiKsqmku9dhkD8j9hVtGeuSrMQGtipj9m28BRcbqYDAgAW27UpHy/na/skZIQqWtJUVLKXytr/W1Mq5/KJN593N99JIAAAAASUVORK5CYII=","e":1},{"id":"image_25","w":214,"h":159,"u":"","p":"data:image/png;base64,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","e":1},{"id":"image_26","w":114,"h":259,"u":"","p":"data:image/png;base64,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","e":1},{"id":"image_27","w":134,"h":247,"u":"","p":"data:image/png;base64,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","e":1},{"id":"image_28","w":112,"h":259,"u":"","p":"data:image/png;base64,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","e":1},{"id":"image_29","w":56,"h":89,"u":"","p":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADgAAABZCAMAAABc8BfTAAAAJHpUWHRDcmVhdG9yAAAImXNMyU9KVXBMK0ktUnBNS0tNLikGAEF6Bs5qehXFAAAACXBIWXMAAAABAAAAAQBPJcTWAAAAolBMVEVHcEz+2CL7vSX/xRz6uir3oS/1mTT/7DL2nyjuazr5sCfqSjrvkTf4qSj6uRv+1h74qyb2njLsWzvvbTz9yRL/4yrtYzzyiDv/7zXrUTv9yA3yiz7wfD3vdT30lTrtaTz5siD9yxH2njL3oy3+0Rb7vRXxgT7xhT76uBrzkD/+3CLubz31mTfrTTvsXzz3pyn8xA/8wRH+1Rv4rCXrWDv+4yolL4/gAAAAGnRSTlMAeE0QILeG/jBCZYAP8NChmeC7aenomtLA41K69JoAAALoSURBVFjDtdh3d9sgEABwJIGCsLU8KsfxkjOatOlI637/r1ZN5oHGe/G/vJ+Bu5M4hNDY3zJEs360OEaz3Mvh+GWGW78cDsdihvtawyOe6rKHFq6nuxauJrEleXvoZvw+yRUnASckJChOElyMdrh4l+HoTdL4qsDzBKfCaKR7vGozLkY6A65GuV8mPI9xfyFIRzgQrgfdBYYDz2SQXCzwfsC92uDPpdvZoasEyLMDOkogfVbhmwLtJeB9aPBEZGiNDvutwzyU4c2WwL0BmTLjDY4O3miweqyWhQLXcCL2xowhUoJzA2sn3ZsQUxVCYfX2JiTIVyEQVloCMEKhCn+YG9wAMK8KSYNGWNMSgAyhfAD6TwCMq4GTBhf6QiFYHd+RDrV8pE8QrE5EpkM1H/4fCJJqJNShmo8tCOs4EB0q+fC+QTCvh04uGGxByOqyNyGVJ4RgXL+YfBNG8oQQzFATGwf0/oGw6U6ICcUTuQUhacZyE/LS8e9AyJre4d0BUxDGzVjkgncgbLvM0AEpDNvGjTigB8I2NCh3wBSEfttXXR1wB8GkHYsmw64BD11wC8GupyUQjHg2TNiFBuVTIet6zisEqR3GfTGCENmDk6E+NgC8d8B+MQSCK2QtgKQfiiHIa9wsOa+v4kcIUvE46jDo+wEIitcq1iHhFw4ISieADv1+IIeg9HLcqXDDrxxQZyWfHJ4CP1K++aFeDqszUrFFE6pH1U6CYqUoAaB6HCsv5Iy/34HuUTtUsQxFfg14LvQ2dyegWGlmzmjcAZiAmdiiAYE2TpyP/E8Do0OG2j9+IouV+joEuz/cw1RsUYOWG0faQV6nKFeh7YMF7qDoCNVbgP1q1DRIZSICrUDHbQM3LZknkiHBwvnJwashTwaWrg8D32OqdrUUyWAc5oP3YlaWIhlJD8Pl8CU1KT2x0hYSikb8cOmLlTa9HBt575diWsF4zCr1+8DrJQ6D6R+MgixhMxjyPR993u8/91BMybEguJYAAAAASUVORK5CYII=","e":1},{"id":"image_30","w":58,"h":106,"u":"","p":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADoAAABqCAMAAADa67LtAAAAJHpUWHRDcmVhdG9yAAAImXNMyU9KVXBMK0ktUnBNS0tNLikGAEF6Bs5qehXFAAAACXBIWXMAAAABAAAAAQBPJcTWAAABC1BMVEVHcExf5SasPki4JU2XZEOzMEt74yWTeD4p5imzLktmrjO/FFAi8ifDC1KoSkW3KE2iUkMp5yh8hDt56iS3JU2fVEMs5ClukDmqR0WExCx+eT6B0CmWkDa7Gk5muDBGvjCgdDxdzysg9SZ4dT2FYEFjkzh56iOLtC+OqzF64ybHBVOhcj2XjjeUlzWbhDmeezukaD6RoTOOqjGKtC+oX0CHvS2rVUKuTESxQkaExyu1OUi4L0paoDa7JkyB0Cm+HE5hljhUqjTCE1BojDpvgjxNtDJ2eD59bj/BClJGvTF92ie6FFCDZEGKWkOzHk6RUEWtKE2mMks/xy+fPEmYRkd55iQ40S0x2ysk7yfCQ+mDAAAAKnRSTlMAQTC2IEa9D/5mQJzF6MWFh6iUgOiiaXfjZzWA0NCjwNTMgODW2+bQ4OAWB1D2AAADZklEQVRYw53Ya1fbIBgHcJoQMalt05q00VnrmXY6d3Pq1GnddPf75tzt+3+S5QoBngAhbzm/EwIE/g8ItXnIGrJ8yO6Rrdw4PAos5dPDozU7+Silm1byYUZ3LeTW45w+s5BPStp6nOIHFW07TvEBpXdaymNGl1rJ8KRGn7eR/ilHcQt5xtOOsfTOBWo8Tk4kUtOliKMLkRoOMZleSvSFGZ2+BCgxkd1XEDUZ4sE+SA1Wsb8PU/3sOL0Gqh1i0vtgS6efm+gn3eB+s6X+DwVVzo5zY0vJrUi36nRVQfsijQ8MafhLpD5Hm2fH+y7SviHFXyXqhxy910R3JJqQPkd/N8jJF4mG6NiErr+XKcYCBSeWbMu0jzwTOnonUw+FBjR4I9MkXSICBdYEmQN0gFCip6PXAMWInGhpcAVQN11eIpWX0xyinnhIQnSyAGiU/Ug6iq8h6qctiY5ugjQ9JciphgZvIdrNfkKJCr/ODKRe9udLlF//wUeI9rImV0NnIM0GCZ2qaecnSLOjFJ+p6Qyk3XyrUtPOH5B6+SmrpkOQ9opNWUnJX5AO8sZISccwzSOdc66kKyB1izMPoGw14WWQekUWAShbw2OQFoOEXCUdgrRbNF4o6TJIi9zrKSn5B9FpmQwgSre1Dkj98lOVdAzRqAyREUTp7n8XouUg4UslHULUK094kLK1BNCkSl4Qva+kg7LNheiSklbFDBi5VlW0X4UZTVoDqF9FRZASFa0aXU0elmlcNfU0AV6eV68KqGAyrdWD8mqiqRii9bNKorGyzqnXKtKf49BPhShS0B26Z0EpnK9yBBqyGgmgfEW3wlPa3y5AhaqM39Zof1FPpntCiuA30xH7VJmKdyH8Fr6uKAdHUtaqHxzbrMCS6J5c9Q5rdFT7VJECVzf1k47215GqqwmUvRll/fVFOgITPzvVWbsr0A24vB9TSvuLhHJwq+FigFDKaiyBrjfVRcOSsv4OeOo01nFV5GKDGHHUUdSdJaUfhOs1XaKSaJzTOZuaGu2rr05wTll/3Vp1pbt02cwoXWmEq3M0T57C5RuCyDe4rJldL9gFllvSqWNyQxRcL+inkjIPd43ultLXLthfXiRTz/QGLliw24U8IxJk/ND+4jT8RB6yefzbm4EVRDiJsZ0MY7u+tnj+AweapLiIlbMnAAAAAElFTkSuQmCC","e":1},{"id":"image_31","w":109,"h":53,"u":"","p":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAG0AAAA1CAMAAAB8+5X+AAAAJHpUWHRDcmVhdG9yAAAImXNMyU9KVXBMK0ktUnBNS0tNLikGAEF6Bs5qehXFAAAACXBIWXMAAAABAAAAAQBPJcTWAAAA5FBMVEVHcEyZYE9oNXKjyDuDVl5sNnBiMnd1TGeVdVKdvkCHkVOSKFiVKFZzaWZoL3OhxD1zamW6JD1vY2hgNHeWr0fCIzeLmFGjxzyFjVaixT2JllG2JEDLIjGNnU+LKVxxZmaoJkhXMH1oVG9dP3hfLnlnLXOGjlVvLG5/KmSHKV+Qo0xhRnSPKVlvYmhsXGtkTHJ1bmOlJkp6d19+gVuUqkmBhlicJ1GKl1F1K2qWKFWuJUSYskVzamVaOXtZNnx4c2FxZ2Z8fF2buEOlzDp5K2eevkCNnU+1JUDCIzfNIjC7JD2hxD3ddNRjAAAAIXRSTlMAIH3HMFrAQBA8WaNwgN96kVe3l8KmoO7El3vQ6tjM4OABCBPCAAACjUlEQVRYw+3Y6XaaQBQHcFmHYdEgiOCSNGLcs5AYI6Rp2mjikvd/n46ctDkgBGYYzumH3hf4+Z8L945UKhnFA1k7+ShNAFKlrGJky767+3V7+3xz8/3n5eXVVa/Xu66zfAlUyx4M7u+PtevptGoINCmp1X14GKRqU9d1DVoJeWvy9vaV5rpPTz/OqyyFJkJzPsnUXKShKhoQWQjLq/X7olDEenycz+cYWv+iSugxzssLvnZB5tWar2TacIjtQe79lVhbLrE8yVm9F9CGy/UovwebCCukIW50CvJ1bIWquDYanzLZp8itaGnjsZI1XyQIYa3mcBwNbTxT8z+YiDXNItpsNsvZvs+xLLesLqnm+75CMK6RaZNk8/3FQiXcdECrn+FqiGuTb4eQxMq2WHhKoe0Xkrk1z/O+6QmDv4a1JgRWzKt5QeM4HgexHx/BELNP0gu8IDiKxzRXnFODEu65smJmtiDYNOKzDIaDa9U8mHg5edaopmcLtc1GTxvK4eQyTaslw9xZGcGoJmcLQm2/j3fPSZ7Ktm23NE0GIPPdQceacpJI23di8bjsHXBWR6WxGisAVAnJgaqkaNttNN5h6RBsHDEsgw1LRT8CgfG+hdq2A2IcyX47/3svWaf3DWG73a4RvSvQ2abJJ3nwIpMTceVpu7D0CPdHm1DQFkfZUEXScXSzRfu268TGisSV1rdO43ghSGY5WkNPnkwOLe2zb201fQjKdPvWVr++z8IutZPMosLmWVQ0RWfyLRG5qOYrOsZylqwC2qkCcK8e0CbTFIEhutDJXUxtrahFvmfIdv5/wqJa/NMQb+XQRIPw9JICWulaVWQF6t/zePmkHtUOtwMBVEosCXxU5X/9A/UbTDkfoLCHclMAAAAASUVORK5CYII=","e":1},{"id":"image_32","w":52,"h":110,"u":"","p":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADQAAABuCAMAAABfs8BIAAAAJHpUWHRDcmVhdG9yAAAImXNMyU9KVXBMK0ktUnBNS0tNLikGAEF6Bs5qehXFAAAACXBIWXMAAAABAAAAAQBPJcTWAAAAn1BMVEVHcExbNcI6YNcRpfQ+Zdc0ct01a9sMrPYUo/MOq/Yblu1HS80Vn/F5CK11DbA7X9YQp/UMrPcre+JDVNFzErJ6BawjiOcdkuwXm+8Ro/MkguUuduAMrfYGt/sxbN0Pp/UUn/FjKLwZlu0ejeorf+I3bdpvFrQBvv48ZNZCW9I5X9coe+NIUc57BKxOSMotct9UP8ZaNsI1Zdo9VtQKsPjF03fHAAAAFnRSTlMAP7lcECVZgj6/eX2kjF2b1ujc3LjcmWYWHgAAAtJJREFUWMO12Hl32jAMAHAncRLnKIRQoAeMdivrsbGr/f6fbaQmh2TJR9+Wf3m/JyNfkoWgviQVwZ9cFOGo3G/LYNTstwsZaJLr/fatDg3UoZ0KMuruHTVBqNZolweY9OqMdgG5yHv06J8LuRnQo3cuks8j8l4XzQTtEs98b6bIc13UAG290p5+gcgrVInQtU/aW4yu3NsxWxvInfbaRBvXDMsbArlClRRaO0K1JGrtk3RDoofMhuYMsoZ6YNCtJVT2jUOWUDGLDmwoecujJTs6gBqAnrhQMUAFREwo+ROgHKJnOlSGUAERHSqGKKkh+h5Ro/sEkcoRiqnRYaQQ+kFs4RijFKM5MTqMBEa/pDk6AzUYVcauMFGB0W+MnjA6XTkGmqE9+8dEiYEuIaoIpAx0D7MeE0iY6AKgFwo1BroHCSdRYaJpKuYkyk00TcXSQIvu9jXR18mqeDFQd4ArAo3ji2gkCDSOr2LQzkTj+GIGFQSajXmgUU2gfnzylUE5gY59HjikKHQeXxWEzutvziFBodU5eQTSNceCQEed9GcC6ZqooJA+NV9ZlFPo/U+lgehSZ5xFikKrj6CjniYWCRJFDrT4CCooNHOgnEIX/wMlHMosSHHIMk9CcIlIQ1FkX7CnnHNoGYhSbhPmQwdGrz3yjLAgfYilFqSYTXja70Fo1l9PLJLcGXG6CFkkuCOsGx+LGuawpK7PARXMsXzKH49q5gKg6ogBlQhNSonIG0WgRmSQgmg1LT8iTzSzlaNDT5eydUT3I4MEQBe4tvRBuIyVBxo1E1TZu5oRFRMk7U0XiSqqc2hhFdY32T1aki8GCtZ75yUxoMjZUZsodjf8JmLfGGQ7dgAIVZZOfI1RplFs7d8xUhqlPo9NJUSZ64lKN5IAzd0PWwZqPR6C8q45nqCD1wNrCZHn41uyfRtRJjy/ZEBlwDNp/1CchLxcDpMt/tX3F+UidwW7EEGyAAAAAElFTkSuQmCC","e":1},{"id":"image_33","w":58,"h":106,"u":"","p":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADoAAABqCAMAAADa67LtAAAAJHpUWHRDcmVhdG9yAAAImXNMyU9KVXBMK0ktUnBNS0tNLikGAEF6Bs5qehXFAAAACXBIWXMAAAABAAAAAQBPJcTWAAAA81BMVEVHcEzOwR7QxR88R8RGSKyJWBfTyh+Jaj+FViCDXS98SBqSYhE7SMh+RxVZR3h/UCVrRUVTRohmRVNVRoCriRZ9SBhBSLbIuR08SMVtRUCUZBGUZhG9pxqhehVpRUrEsxx1RSk4SM7UzR/Wzh9QR4/TyR/WzyB9RBN3RCNgRmJuRTtlRVKlgBW0mBmwkhisjBeohhahehS4nxm8pRrAqxudcxQ5SM7DsRyZbROVZxLHtx2RYRGNWxCKVQ9LR52GTw6CSA5NR5bLvR5IR6VQR45TR4ZFR61WRn5ZRnZCR7VzRS/PxB9cRm4/SL1qRUfUzB88SMU2grNyAAAAJ3RSTlMAQIDAP4jADzggqMCl3ItQbSmr3tJoYmd702Lm16PqnPDYq+ao4FBlIB95AAADZklEQVRYw53YZ3ubMBAAYCWGCAEGYyeOR+w6adoMd6UZTZrYaevu4fb//5oCBs1DEuijed7H0h1Id0KozsD7qOHAB1eN5eXVVjO5+/byar+ZfJnSJ43kq4weNJGvc/qugXxT0Npxmrwoad04TS4orRmnyTWjO7Wke8vR93Xk6E6gpIa8F6l9iL0HiW7bS5nahpgsFjK1DDFuPyrUMsTtDyr9hG2kfwJRmxC7JyC1eIv3TmFqzo43q6BHxuDOTiuoMTvDn1X0l0F2z5vSzrmGarOTrJtSPJXpLk912RnOJTq5sKTRXKYjgVZnJz6TaWBJyZlCR65An1ZmVKFjHAj0b9VCfyvURdc21PmuUkIkCiYW91QaoNiGRt9UGiNXos+h6X5R6RihQKLb0HQB6iI0NtPoM0AJwrdG6nwEqJ9uNTJVt5geRGP5kEyp8iYOlgBtpw8CE01WEN1Ln4xNNARpekrgOwN1vkLUzzZkhUqfTgukXnZ+KFR8/50fEM2ChAIDbYE0CxIa66nzB6TZUUru9bQFUj/fqvTU+QfSOD9l9TQEaR4kFGgpvgGpmz980NIBTAlYcon0EKT+ps4DKHubkhuQxptaBKDsHe6DdLZ5GGhpCNJNkNBCSw9Buql7PYgesdRAdFgUTxCl25oD0k5R62npQKXr9bQoItsQ3dLQ+bpbHLaPWhpC1CtOeJCyDw6g07I4hegzLS2ChAKI7mhp2cyAJde2jg7LwtZQrQG0nO8eSLGOlg99Qz2s0m75yFT7q3mNy6WClPDbi0xp4wBR/qxSaFfb5/C9ivLlJLS5gijS0B4tNKEqXCxBJBqxpQJU7OikDSZhS1Wp1JWJ21qP9ZEqle9RxM2UzpcAHYdcWIpbeKJpB9Vmmz842Hx9hR6oXW/I0YgtVaFAHdznKJ2vp3RXUH/Dna9svh2ZTsCKn53qEbdUke7C7X2fUof+JrWDFRJhSlm/LVGvqi8KC3rMrgdEWilRv6B9+stUoJ6m7ywoXRDhe7qxTqJBTrnUcDTQX50kOWWpGXLdlenSJcwoTQ0W+hzDyKtw9YZgMbK4rGmtlsfctcSGtj2bGyJntaSpwUU97FrdLaV/u6RL7eR0FtvewDlLNt+8RsTIetD5kqxu8lCT0ZlPO7iRJNMuaQRR1G021xrjP5N8dkjccU8kAAAAAElFTkSuQmCC","e":1},{"id":"image_34","w":81,"h":159,"u":"","p":"data:image/png;base64,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","e":1},{"id":"image_35","w":91,"h":124,"u":"","p":"data:image/png;base64,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","e":1},{"id":"image_36","w":77,"h":122,"u":"","p":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAE0AAAB6CAMAAADNqC6XAAAAJHpUWHRDcmVhdG9yAAAImXNMyU9KVXBMK0ktUnBNS0tNLikGAEF6Bs5qehXFAAAACXBIWXMAAAABAAAAAQBPJcTWAAAAxlBMVEVHcEyiUXPehjWfTnePSoSBOpOVRYGGW4ZxWpVoRZ8gcL3ojioQeMMYc7/pkCkrabiFOZGeTXjXfz05X7JjR6FLVarVfj4TdcHpkChuQZ3GcU7HcU3iiTC+aFbHcU20YGFCWq7Nd0anVG+BN5XiijCdTHjagjg5X7IxZLWQQIaKOo4paLgQd8JUUKchbbt3PJlgSaJvQJy4ZFzCbVJnRZ+uXGYZcb6rWWnSfEHVfj3rkieiUHSYSH6URYJKVapOU6kFfcZaTaTrzM/OAAAAHXRSTlMAwjqON5xbECBfOX+4Wr1/2OxZr6jQrt7g0HXX3Ak7JjIAAAQrSURBVGjerdp9X6owFAfwIQiDICQ1M29mXc0se7zdbk+3uu//TV1EkY3tnB0Q/o3Pt21nDPabjNW+jj3W2OWNxseNYc5oPD5sCmud/BqPzxrq6sFJph00gh0/ZlozXbVP19rZqIFiWqe5dtbaHftdaLvOEW79EbQdB85JzkXtZVdM1nYZODd5L2l7O2CzWVmrP3Duh6q91sb+6rSaD5e70Gr1Bq690Gv7tbArQLuohYFa9RnX/glre9UxRNuvjiFaxYFr3+FapYHz7wxalTXOfzBph1Uwo/aDjj2ZtalHxigasQz8nqTR5q9zT9NI89f7R9SOKFgf0KyyNuVmrX+j12xVM5ehdwNojtJTcxniL0CzPFUzlcH/hDRX0zbD0xB8QlrCbFWb4uWcgJqt1dAy9CcTQEs8ZqnaG/ZS7U5AzWbsXNM2pKjRHNY444p2MX2DixrMYc1KH15d247gBwrRnPQLWqe9gYN2CWsJgzSgqNElornpDZZW0xeVX2Laask+12r6oobXiGavVtAKWnyNaatlzNVruinCrzGtnW2K9JpuioS3mOawrAhaTTNFoltEW3SyexJAU96p3i2mXbnZPe+Apky47jOqZf/doWr8GdXa6w9zSCtPkRDX1m85m6gFS1Rb14BZRC3ENXd91wzS9ktNQ7VN0xyiFuJaO98EkjS+xLXN5LRBTXq0YlzbNI1ZoCY9WgNccza3JSQt+ka1Tj4eM5IW4poP78R12gDX8gXCJmn8G9XyGqRFoGgRruU1SItA0WJUG2678EHSQlTz0QRD1Qaotl2kbZr2jWnbGjCrAW1bA/aXpHmYNizmEaYVa0iAab4h+ammFS9Ke3etV4yuhWl7JK2oAVvQNKSmQ2G3imotiuYbEzP1OwTWuBiZIdoRIzynQg1YB9P2KZrQ0bQIiCa+6sEVSYwMFljbWsy8WoodtTFNepsGgOYQcstsDyh/0yy1Wl+85Qprm/wtqH8DijVwMK20E4+1mhhzuJhW2hcFOq1PS2gv1JBgqdHEjrIOoilbtlCjiVsAXinNi1StJ4XklVJQT9V8Yhatiy3VL1VPHjZIe9XljJGsfX51pabD+Zt+1/xcapvUURfUgHg2ljW5o1Dq/gId3XG5p1JHWQdqGxjOyvssqaMcyC3HcBoVSZr0T31Aw6KtgaB1KecL6HFRJGix9Bd9Qms4exoUmpS5OlrNFKQWKUHfePbx6BhT2UGuyR0dqtqIEBhHuRZIj5WabFukQ4DBRisdpZQ14tlJtNbk+dEraw4jXlnGJQ+bV0q2LfoPCIKsbQF8ZpS4Vc6buqu2lY6zBM3iVTDmpZq8UIq5ZaWGrYPLeQycZ9k1fnLRlYetl2uWw2pcfC53dJP81LJWdVXP2jp+Mz8rGaZaUxbjTw/DpizPH/Yc1hQW+w3+4qjG9R92vwL6rN3fRAAAAABJRU5ErkJggg==","e":1},{"id":"image_37","w":75,"h":124,"u":"","p":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEsAAAB8CAMAAAAW773NAAAAJHpUWHRDcmVhdG9yAAAImXNMyU9KVXBMK0ktUnBNS0tNLikGAEF6Bs5qehXFAAAACXBIWXMAAAABAAAAAQBPJcTWAAAA4VBMVEVHcEzr416oz1U+sFS51ViVylSKsWeIul8ph2yWy1Pp4FySylPj31woqVUusVDw5V8ie3osPI+hzVT77mT162IrqlWJyFLf21kpTYqHyFJNvE5bvk8ijGwqRY1vw1ArQo3k3Vri21kjqlPo31wjmmAikWfr4l0+uU3u5V8hgXXS11jL1VfE1Fe90la10VbZ2FiOyVP872Vbv0+u0FUheXx5xVGnzlUjoFsksE4jpFgms0slYIQttUwztkygzVQpSosii20ih3CZy1RKu05RvE7x52BmwVCDx1L26mJtwlAjboFtkvF7AAAAIHRSTlMAV8B6MIUgDz1E7WmaW7+wun/dxXmWqtaww+6z7V3Y6E4TtsEAAAQFSURBVGjevdlpW9pAEADghYSEBBIknIIgoNarh0fFtlaw1hbb//+DShDILtk5cjzdr+Z53Z2dhN0ZIaDRtEVeo3DRyc3qXFxWc6KcTxeX73PCaqH1oZIHZY1X1sc8sOraunUyU7a7saZm5oQYb61Z1jTrSFaQDTPHkvXczhh52boqZYn8WLWuMmRGc8daLMwskVfntbiz0kZ+FLeGKTezqrHu+ilzXmf9MVLl/Ehr/U7zZnYAq5d8M60RYN0kj38VtF76KZYIWDcvCePvjBDrZznxEmGrlyhkLmqd+kmS6wi3ThOErEZZj+yQ2Uek9Wizl0ha3332EmnrGy9k9juO9YUVskLMCnSWzwlZLWYVddakxVli3Ap01qTMWGLcqmite5teYtwytdZXMjHONJYItNa9RyxRa7X11jG+yqrWcgAL30tXYwVCANYxtpfmmca6FaINWAfIKpuAVQGsE+S9bACWBVlP4CrtX4AlAsA62QMzArRKkPUEJVlVby0PXg5oQeF39VZ4IgGtH/rwW6+w1Qatc+15sYBYFdjShr+GWBZsnevyooFYYghbmonZr4C1uie0YetzWRMuzHIQKz6xJmrZiBWfWAO1xD5ixSb2ilsVxPq7MzHrGrKKb982zOruhJ6wBGY9qMnfpKw2ZtXVrKesCmY9qNsIWtN1QFFrIFvXlCWGmCVH36StEmbNLXkbSctDrbq8jbC1/pc2anV51ubaOMSsuS2lBG0ZqDWQUoK2HNSqs6xtnQO1ulJ6wda2ANPHrHkyy0AtK5FlolaZY0UVqx5mDThWcWv1MauezDIyW0H0BchszaJXrZej1edYWN5LNVEjsxXVl0yOVeVZosfI1SbT8hkW9o2eSdVVg/FuC6blgNYh6/dRtgTj+4WcAaazopADRn5Xlz+QPKsFWfK5yYWtQHrMA6xD1hlzOnuWj8j072N47WBZoqe3Bqy7wtKSi7QtvaXesUyeZZDnnFWKgZbcCTDp81cYMdBSWhRay46XJziWr7G6mlo7YO3LT7XIczRYg4lZXtzS3iEbeutKfqZM3js2q6QtQd+HoHrhylI6Cj55T9vsJW21yPsjWkNWksIj77XbkHUoq0zet6OXxI1bSlII1UIbWiG2YwXKA4+yRVRazXivSfm7L1kHVG20sGupbT5Dshg1W3dnXkpSeJG1x+n0ueq81I3cWgesTqLpKvNSOrb21vKYPciObKlJsbaO9wRz2B25F6BuJF0Tjb2bkuVorEQ9rEJkKY1kY2Ul7K2ZwaaPrGxkK7R8kXDYxfW81M90aKXoBVfW/Q4lwZZWWaQY1lsvQA6+NZl4It0ohZYSfE4fBtqC/cWdnPleempVShvmRS03tB3tmyf+x/gHuf5GxB+6iq8AAAAASUVORK5CYII=","e":1},{"id":"image_38","w":77,"h":122,"u":"","p":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAE0AAAB6CAMAAADNqC6XAAAAJHpUWHRDcmVhdG9yAAAImXNMyU9KVXBMK0ktUnBNS0tNLikGAEF6Bs5qehXFAAAACXBIWXMAAAABAAAAAQBPJcTWAAAAjVBMVEVHcEzXiIjipH/ERZHfnILlrHXmrHzVf4jfm4LfmoDyz2D01VnESpH431DptXfajofVgIr321T75kjQdI3fnILbkobrunbdl4TiooDptXjtwm3WhIrUf4vTeozYiYnajojObo/kqH7mrHzsvXPvxmn33FLnsXrz0V3DQZHMaI/KX5D97EPIV5Dxy2PGT5C7ysPAAAAAE3RSTlMAwILMoiDSOVwQYD2CuqbZdN6OeZvAhQAABG1JREFUaN6tmu16qjoQRiElIYDfB0W7t7tVqz2tp/v+L+8ggmSSmSEg/q3P6spMDMkbgmDwZ5EEo32ScL8YDRbP9/twNNjv3/v9PyMNVf+qaPEosMWvOy0aAxb9qWnzMZr5p6E9P9Rk/tbSFk/PjLcHbf/+5ByJ1+s3w+39KZhcQ9pThdNrmzZ5AlYUFu2JwpUwmza8cDrHaPFQGEobVrgox2nhUBhKG1K4aEvS4gEwmjYZACNpX+EQ2JZy++oJO2wZt/evXoUThwPv1meN04cD79anDfp46HJ76QE7drp9J/6wkrY9sG7fnm2Qx2O329e3X+Hi4+VB49y8Cicvl4uX238+z01VwVy30KHJbloJQ90il9bdhtlu19BgT2N3pJ1tEBXMdStC6dK62pBuStrugvRUx71pcrch3OaBcGl/+XbeYJDWuC2CCKGxbVCbTY2z3dZJECI0zbVzcwJuRk/LrSkye/9OuA5sSDdZ7pIQGt2G2IDZdYtuG7g+tKQcJuFW5GW1BUb7lyza6US63bbgEUojmpq+nkwcrNutdfMeNAlhsKfFbcleozS8qdNXxk24u2iWJj5uNKpu8r7zxWgv6Dg/GLfqUCX8adMPzq2qdIjTkCmS/nzQbtv7CW1O0JxnavLzg7tVPa2mR5CsCZozRVafnFue1AcjP1r2+fnD1E3cj7kUzZ4i00/W7f6Uizxp2Zl1q/ejc0/a9My61atrQdFeHDXGTdVrnydtembdRHNu86LJ85muWznf6skp/GirM+vW5B4hSQM/rSXndtg2c7PwoqVXzq3pQSD9aKsr66bJkzhKW/JuzQIhvGjyyro9sqfQi5ZeWbfH+lB40VasmwraJvjQpqybZhMMl7Zk3R6LtPCjXTm3Nv8LB9GgW7tGF7kPTXJuqv1azri1v/qMc9MdyU8nzXRrexCIZ93MHgQhR5t4uWUtLfejMT1Vxn6YpcU+8010JmbuPoR2kyDlomnmY4H8nc7MA85AWutmnnjygqFNAnJFeuCOAWgCQ4sDcrVsYDtzoIKjgadphrpdLrFHbomc2m40dx+izK9wCYZ1Pp2idRNW2UiatSPHn6cSpr00zTrsZpib8ktokeTnjLiB/6gYmnMOx/ZI5hFAMvmbe45JXbcZCOJ6paCJ65aC41y/vHfqzDdw1lG0G5YzprYbGGhC52/4qdl207BsFI24u1tBtx08BeM0+j5Lwp7OYBREuZHhLDhnwY5KKrek06gUuCVI2RwaF20tDbeZcyWAJLSazQMNNwH+skXdNJ8ILls3kLnGaN7bFaSmDzdlzw83i+5OZZeNmzM/bLfQIzBOG7cM/KzcnDz0ugRY1m7W7Yft5nl3kt7dYNlmtpv3tU6VcW1SOFDoFvq/QJBVtNi65jHdet2Cz17tskWmm08vzXGVTVDWQFs33ffOLz2dBHGfJQa8cqFOGezo3S3vOchmMTuBgdZZjRp6MZ+5d21KB6N8VEkbi3W7HxuNlWgVZaO9IyT0iG8cDfj8D45ZCDoCHh+OAAAAAElFTkSuQmCC","e":1},{"id":"image_39","w":104,"h":194,"u":"","p":"data:image/png;base64,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","e":1},{"id":"image_40","w":110,"h":192,"u":"","p":"data:image/png;base64,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","e":1},{"id":"image_41","w":89,"h":189,"u":"","p":"data:image/png;base64,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","e":1},{"id":"image_42","w":84,"h":194,"u":"","p":"data:image/png;base64,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","e":1},{"id":"image_43","w":89,"h":189,"u":"","p":"data:image/png;base64,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","e":1},{"id":"image_44","w":89,"h":188,"u":"","p":"data:image/png;base64,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","e":1},{"id":"image_45","w":176,"h":97,"u":"","p":"data:image/png;base64,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","e":1},{"id":"image_46","w":144,"h":143,"u":"","p":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAJAAAACPCAMAAAAiGKLEAAAAJHpUWHRDcmVhdG9yAAAImXNMyU9KVXBMK0ktUnBNS0tNLikGAEF6Bs5qehXFAAAACXBIWXMAAAABAAAAAQBPJcTWAAAAulBMVEVHcEyyJUOGkFSdvUFsNm+Sp0qevkCBZl6LPlttQm6mzTm4JD7FIzZnMXOQKFhjM3VpVm6Wr0d+gFtsW2x1bGSKllGBKmKmzjmgJ054K2hkLXVXL36QKFhkTXJzamV/gVprLHGoJklxLG1dLnqJKV1cPXlgRXWYKFNoVG9rW2ywJUNvYml3cmF7eV6dvEGDiVe4JD6hxD2JlVKPoE2Sp0pZN3yZtUSGj1SMmk/GIzW/JDmWrkfPIi9XNH29u8I1AAAAFnRSTlMAapMzmGCWEjFiyZXFu8vcy8/K67C4f2s/pgAAB+lJREFUeNrFnOt22joQRg3YlsQ9IYWcNDShuZA7UBIKDn3/1zqSbBxsdBmNgGj1X9dKd77ZozFCbhCUVq1Wi/iKq3G1tKKIUUqCI6/47W2xWHx+fr7zNZ/Pl3/+/Pn7dzKZXF29vvI/Ly8vZ2eMHREs+gISPFtAAkkA/eLr+bnXZew4VDUb0IsEen5+fHzohceAqumAXvOEUp7Hh4eH4bATskMTkRMF0KRUsgyIEw3v71sHDkoQ2RzKE+I89/d3d4fNicR2h0pAHx+d8JAxxWCHhpJHEP37eUikGObQMCcSQD9/tuge6qPZkNwcyoA4kndKsW6LdHJoA3Rzc+NbuHqgJ3J0KAW6ufDquFpdP0bcHRJAFxcXPnWL6oa/c3coBbq4xocU1020GIcE0fU1OqT6IDYRIRySPNfXHYoFGkRmIoRDnOj3798MC2QhQjmEJxJAg5qJCOeQAPovxAJZiFAOCSAM0WBgJ8I4lAIhiDKgOjEToRziQP0QCWQjwjkkiBgSyExURTrEgfoUCTQwbdkbIjeHOFHfmSjnmc1iCxHGIc7TbxNcQgMz0SnOIUHkIjbZSsicETlFOtTv3zqIXdtOyE6Ecqh/e0sRQCKhtzcjEdIhTtTGJDSTSCYiinSIR1RBJDQQCZkzokiHbuFFI4NSzd4WJiKGckgQtRH7UJrQYhEb9o0I45BI6LKC2IfShBaLEwNR9RfKIXjRFAktPk1EZziHLi/brgnN8oQ+AUSuDnEi6pjQ4CshIxHpohyCRqRO6PP9Xf8MSXoYhzgRyOt6gWf2BTTXE9EexiG+iAvQoFCydzMRyqHxZcMnoff5XP95jWEcuhyPqUtCs3JC86WBCOMQKKK40GXFhObLqr75EQ6NIRHFpoRMRF2EQ+OxPaKo+PRRSmi5/EG02xHCIUBENX2XySO95amWqOfqkCBqgIEUXZYd6dW025GbQyKg8Tl1ma6KhMT5kO5nMDeHUqQGtO+VXbYUPH8nuvZnzg6d2yOq2xOaaIlCt31IEJ03gH2v7rLNGaOu/buO+xAHskUUmbtsc+ipaTbSvXfah3jJziuwNtN3WXqCpiGiQ7d9iCd0TmCfO2bGhPhSR01dHbJGZOmyrXNqtdpsN6FrU5fxBex7U0IpkfrQIHRwSPKMGKTNzF2WnaCdKX9Cy82h0agNHR5mhySRSkjSATt0nhJRgNW2LrtKr1qcUqXYLvsQB2pAJAI4xIF+vSiJXBwacSJilwjQZdn5kEpJBp9lIqFRBbBXzwAOCaCXZ9UcCeH7kEhoZJfI2mVfp7DPKrVbLg5ZtK7bu2xSPMnvEkWrQWeZAHpqWCWyd9n2KWxv9zekH9B9SEZErDsR1KH0gOhxV20GnWWC56liq5m1y65K59Shigg0ywTPqAmpGdghSbQrUgjehzgStdQMNMuK59RduiM26HlI4DyZta4DZ1n54HyH6AM2yySSpWbWLrtSfddRVpsC9iFRMp6QUWvi7NDm9KN8F4ZBnofShBqQiJwckh+ly2qHoFkmEnqybEUzhEMP/BF/WBKpBZplI0vNuNYOs6x06MlKrQbrMkvNIqRDguiuuEdSwCwTSNOpecIiHZIfFIuXc9gFqMuepuanIoxD+WFD8XJOaJ9lEqkZWCJy34fy86G7gkgtSJdNLTWL0A7Jm57/woLY9llmrRmP6A3rkCTaFolCumw6NdesBm173Zcv2yIx+yyz1kxcgEc7JD91/NsSKQR02dRSM4J3aPO5bEuklryuY5hlU2vNgsjLodJdWNKxzbKptWaFtxZQl3P541D+T1DbLOM8ieWsiJx4OZQ+5OciMWuXWWsmX1rwcUg+MOYihQWinVkmI7KdEkd+DqVPsJ2NSC1blyWJ9XvP2Nch+Qyb/d4ku4yinmVTSM0EkZ9Dgie/LkxF3xu6LEkS+xexJ74OybuwWf8z/SybAmume6/j1fEOWjZIwhRI12VJAvjakxP5OiSHfVq2tmmWiZqBrn17OrR9OZeauyxJKJDIz6H08UOKRA2zjCOtIN/mq97reEXdYxS/fmjssqQJAgpqfg4V7sK2tLNMEK2AN4vIibdDcnLwD9ukbeiyZAW+MVf1dCh7pm7TgKYXG1SzDF4zUTZ/h+ToYAEzdNlq5XAz9YevQ+lNzzAIdbPMqWbpOwI+Dm1ma5u2tV22Ajb+JqSqj0Nfz0OVrO93ZhkHagZOq3bq6VB2S0/bZauV61sykZ9DmifGLaCKIxCvm6dDt8Yuc61ZhuTn0GXuUGmWSaIAseipn0NpQMouc5dog+TlULnLNgFxonUlwC16tgeHFF2GkShHwjs0Vs8yAbQOAh8knEOGLlutvd7YJWc97D6knmUcqBF4LcJ6OId0XbZuBr6LdTEOqWfZar1a7+HFbxr2UA6pumyNbvxS5bp7mGWJTKgR7GfRcGi6P9SHdtkeJPqKqeXmUHmWpQ6t9/m/B5Cw43j3YzehNQ32umj4Ad2HFLNMEDWCfS8WomcZD2iPEhV0Qs4ygRQcZHEm1Czbv0QFJtdZJrrsABIVfHKcZQeSqNh3HfAsS4mCgy/KWvAuO6BERaFCwCxLkSrBkRYN25AuO6zVu0G1LbPs8FbvBFVp7zhUSGh99P9ETEJpnoeOZrUSKv/iflpIqPE9QPLsutIstH1K1Ay+bxEB1WgWE1oH374E1OpbrVZSNZrJ+jutVpaQR3XEvRqM9T/1gF+5R0MJWgAAAABJRU5ErkJggg==","e":1},{"id":"image_47","w":142,"h":88,"u":"","p":"data:image/png;base64,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","e":1},{"id":"image_48","w":112,"h":83,"u":"","p":"data:image/png;base64,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","e":1},{"id":"image_49","w":214,"h":159,"u":"","p":"data:image/png;base64,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","e":1},{"id":"image_50","w":134,"h":247,"u":"","p":"data:image/png;base64,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","e":1},{"id":"image_51","w":112,"h":259,"u":"","p":"data:image/png;base64,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","e":1}],"layers":[{"ddd":0,"ind":1,"ty":3,"nm":"NULL CONTROL ","sr":1,"ks":{"o":{"a":0,"k":0,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[260.898,561.149,0],"ix":2},"a":{"a":0,"k":[50,50,0],"ix":1},"s":{"a":0,"k":[32,32,100],"ix":6}},"ao":0,"ip":0,"op":105,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":2,"nm":"Layer 2","parent":1,"refId":"image_0","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[19.171,289.451,0],"ix":2},"a":{"a":0,"k":[476,954,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.721,0.721,0.721],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,19.333]},"t":0,"s":[0,0,100]},{"i":{"x":[0.728,0.728,0.728],"y":[1,1,1]},"o":{"x":[0.212,0.212,0.212],"y":[0,0,0]},"t":11,"s":[116,116,100]},{"t":62,"s":[0,0,100]}],"ix":6}},"ao":0,"ip":0,"op":105,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":2,"nm":"Layer 3","parent":1,"refId":"image_1","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.831],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":87.1025390625,"s":[-408.307]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.167},"t":0,"s":[43.284,15.222,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.215},"t":13.188,"s":[-293.512,-1166.629,0],"to":[0,0,0],"ti":[320,-2754,0]},{"t":80.7158203125,"s":[33.872,456.375,0]}],"ix":2},"a":{"a":0,"k":[29,106,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.519,0.519,0.519],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0]},"t":0,"s":[0,0,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":8,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":60.355,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":83.355,"s":[100,-100,100]},{"t":107.35546875,"s":[100,100,100]}],"ix":6}},"ao":0,"ip":0,"op":105,"st":0,"bm":0},{"ddd":0,"ind":4,"ty":2,"nm":"Layer 4","parent":1,"refId":"image_2","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.831],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":80.771484375,"s":[333.811]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.167},"t":0,"s":[43.284,15.222,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.237},"t":9.304,"s":[-453.503,-1143.468,0],"to":[0,0,0],"ti":[320,-2754,0]},{"t":91.4677734375,"s":[-334.128,424.375,0]}],"ix":2},"a":{"a":0,"k":[29,106,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.519,0.519,0.519],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0]},"t":0,"s":[0,0,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":8,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":58.119,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":81.119,"s":[100,-100,100]},{"t":105.119140625,"s":[100,100,100]}],"ix":6}},"ao":0,"ip":0,"op":105,"st":0,"bm":0},{"ddd":0,"ind":5,"ty":2,"nm":"Layer 5","parent":1,"refId":"image_3","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.831],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":112.806640625,"s":[277.128]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.167},"t":0,"s":[43.284,-11.278,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.229},"t":9.865,"s":[-641.354,-661.064,0],"to":[0,0,0],"ti":[-256,-2258,0]},{"t":86.390625,"s":[-590.128,520.375,0]}],"ix":2},"a":{"a":0,"k":[54.5,53,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.519,0.519,0.519],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0]},"t":0,"s":[0,0,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":8,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":42.827,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":65.827,"s":[100,-100,100]},{"t":89.8271484375,"s":[100,100,100]}],"ix":6}},"ao":0,"ip":0,"op":105,"st":0,"bm":0},{"ddd":0,"ind":6,"ty":2,"nm":"Layer 6","parent":1,"refId":"image_4","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.831],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":108.1025390625,"s":[66.474]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.167},"t":0,"s":[43.284,15.222,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.624,"y":0.756},"o":{"x":0.167,"y":0.172},"t":16.247,"s":[-124.158,-1222.782,0],"to":[0,0,0],"ti":[300.62,-2401.303,0]},{"i":{"x":0.722,"y":1},"o":{"x":0.374,"y":0.813},"t":77,"s":[-804.78,411.671,0],"to":[-10.967,87.605,0],"ti":[11.263,-96.936,0]},{"t":97.466796875,"s":[-646.128,432.375,0]}],"ix":2},"a":{"a":0,"k":[39.5,106,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.519,0.519,0.519],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0]},"t":0,"s":[0,0,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":8,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":51.709,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":74.709,"s":[100,-100,100]},{"t":98.708984375,"s":[100,100,100]}],"ix":6}},"ao":0,"ip":0,"op":105,"st":0,"bm":0},{"ddd":0,"ind":7,"ty":2,"nm":"Layer 7","parent":1,"refId":"image_5","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.831],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":76.435546875,"s":[-322.516]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.167},"t":0,"s":[43.284,15.222,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.206},"t":13.507,"s":[-262.006,-845.219,0],"to":[0,0,0],"ti":[240,-2386,0]},{"t":84.5537109375,"s":[-782.128,616.375,0]}],"ix":2},"a":{"a":0,"k":[29,106,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.519,0.519,0.519],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0]},"t":0,"s":[0,0,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":8,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":65.008,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":88.008,"s":[100,-100,100]},{"t":112.0078125,"s":[100,100,100]}],"ix":6}},"ao":0,"ip":0,"op":105,"st":0,"bm":0},{"ddd":0,"ind":8,"ty":2,"nm":"Layer 8","parent":1,"refId":"image_6","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.831],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":83.5185546875,"s":[-412.496]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.167},"t":0,"s":[43.284,41.722,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.183},"t":15.715,"s":[-512.143,-859.842,0],"to":[0,0,0],"ti":[320,-2754,0]},{"t":103,"s":[-350.128,536.375,0]}],"ix":2},"a":{"a":0,"k":[40.5,159,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.519,0.519,0.519],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0]},"t":0,"s":[0,0,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":8,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":58.388,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":81.388,"s":[100,-100,100]},{"t":105.3876953125,"s":[100,100,100]}],"ix":6}},"ao":0,"ip":0,"op":105,"st":0,"bm":0},{"ddd":0,"ind":9,"ty":2,"nm":"Layer 9","parent":1,"refId":"image_7","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.831],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":101.78515625,"s":[343.807]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.167},"t":0,"s":[43.284,24.222,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.249},"t":12.53,"s":[-527.013,-1046.968,0],"to":[0,0,0],"ti":[-407.77,-1996.524,0]},{"t":100.2724609375,"s":[-502.128,488.375,0]}],"ix":2},"a":{"a":0,"k":[45.5,124,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.519,0.519,0.519],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0]},"t":0,"s":[0,0,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":8,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":59.106,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":82.106,"s":[100,-100,100]},{"t":106.1064453125,"s":[100,100,100]}],"ix":6}},"ao":0,"ip":0,"op":105,"st":0,"bm":0},{"ddd":0,"ind":10,"ty":2,"nm":"Layer 10","parent":1,"refId":"image_8","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.831],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":101.15625,"s":[214.936]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.167},"t":0,"s":[43.284,40.222,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.227},"t":10.553,"s":[49.872,-1251.625,0],"to":[0,0,0],"ti":[320,-2754,0]},{"t":90.2548828125,"s":[529.872,440.375,0]}],"ix":2},"a":{"a":0,"k":[36.5,156,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.519,0.519,0.519],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0]},"t":0,"s":[0,0,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":8,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":52.324,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":75.324,"s":[100,-100,100]},{"t":99.32421875,"s":[100,100,100]}],"ix":6}},"ao":0,"ip":0,"op":105,"st":0,"bm":0},{"ddd":0,"ind":11,"ty":2,"nm":"Layer 11","parent":1,"refId":"image_9","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.831],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":82.3828125,"s":[-452.243]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.167},"t":0,"s":[43.284,23.222,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.25},"t":9.722,"s":[17.572,-733.066,0],"to":[0,0,0],"ti":[0,-1.333,0]},{"t":103.9873046875,"s":[57.872,424.375,0]}],"ix":2},"a":{"a":0,"k":[38.5,122,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.519,0.519,0.519],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0]},"t":0,"s":[0,0,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":8,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":52.705,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":75.705,"s":[100,-100,100]},{"t":99.705078125,"s":[100,100,100]}],"ix":6}},"ao":0,"ip":0,"op":105,"st":0,"bm":0},{"ddd":0,"ind":12,"ty":2,"nm":"Layer 12","parent":1,"refId":"image_10","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.831],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":115.109375,"s":[316.435]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.167},"t":0,"s":[43.284,23.222,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.211},"t":11.565,"s":[-183.479,-864.334,0],"to":[0,0,0],"ti":[320,-2754,0]},{"t":100.826171875,"s":[-542.128,480.375,0]}],"ix":2},"a":{"a":0,"k":[38.5,122,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.519,0.519,0.519],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0]},"t":0,"s":[0,0,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":8,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":33,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":55.999,"s":[100,-100,100]},{"t":80,"s":[100,100,100]}],"ix":6}},"ao":0,"ip":0,"op":105,"st":0,"bm":0},{"ddd":0,"ind":13,"ty":2,"nm":"Layer 13","parent":1,"refId":"image_11","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.831],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":84.4169921875,"s":[88.571]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.167},"t":0,"s":[43.284,24.222,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.173},"t":15.813,"s":[-618.369,-911.114,0],"to":[0,0,0],"ti":[320,-2754,0]},{"t":95,"s":[-486.128,536.375,0]}],"ix":2},"a":{"a":0,"k":[37.5,124,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.519,0.519,0.519],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0]},"t":0,"s":[0,0,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":8,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":43.172,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":66.172,"s":[100,-100,100]},{"t":90.171875,"s":[100,100,100]}],"ix":6}},"ao":0,"ip":0,"op":105,"st":0,"bm":0},{"ddd":0,"ind":14,"ty":2,"nm":"Layer 14","parent":1,"refId":"image_12","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.831],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":83.9404296875,"s":[-251.629]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.167},"t":0,"s":[43.284,22.222,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.191},"t":15.252,"s":[-420.029,-756.057,0],"to":[0,0,0],"ti":[304,-2314,0]},{"t":73.033203125,"s":[65.872,464.375,0]}],"ix":2},"a":{"a":0,"k":[38.5,120,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.519,0.519,0.519],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0]},"t":0,"s":[0,0,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":8,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":53.378,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":76.378,"s":[100,-100,100]},{"t":100.3779296875,"s":[100,100,100]}],"ix":6}},"ao":0,"ip":0,"op":105,"st":0,"bm":0},{"ddd":0,"ind":15,"ty":2,"nm":"Layer 15","parent":1,"refId":"image_13","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.831],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":93.32421875,"s":[-221.347]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.167},"t":0,"s":[43.284,59.222,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.243},"t":10.837,"s":[173.826,-1008.003,0],"to":[0,0,0],"ti":[96,-2098,0]},{"t":105.357421875,"s":[-158.128,448.375,0]}],"ix":2},"a":{"a":0,"k":[53.5,194,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.519,0.519,0.519],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0]},"t":0,"s":[0,0,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":8,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":38.935,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":61.935,"s":[100,-100,100]},{"t":85.9345703125,"s":[100,100,100]}],"ix":6}},"ao":0,"ip":0,"op":105,"st":0,"bm":0},{"ddd":0,"ind":16,"ty":2,"nm":"Layer 16","parent":1,"refId":"image_14","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.831],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":110.423828125,"s":[-449.185]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.167},"t":0,"s":[43.284,59.222,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.142},"t":16.032,"s":[-270.448,-966.067,0],"to":[0,0,0],"ti":[184,-2394,0]},{"t":84,"s":[-638.128,552.375,0]}],"ix":2},"a":{"a":0,"k":[52,194,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.519,0.519,0.519],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0]},"t":0,"s":[0,0,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":8,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":38.72,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":61.72,"s":[100,-100,100]},{"t":85.7197265625,"s":[100,100,100]}],"ix":6}},"ao":0,"ip":0,"op":105,"st":0,"bm":0},{"ddd":0,"ind":17,"ty":2,"nm":"Layer 17","parent":1,"refId":"image_15","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.831],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":96.00390625,"s":[183.632]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.167},"t":0,"s":[43.284,58.222,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.218},"t":8.6,"s":[-186.287,-660.152,0],"to":[0,0,0],"ti":[128,-2218,0]},{"t":103.9892578125,"s":[-614.128,488.375,0]}],"ix":2},"a":{"a":0,"k":[55,192,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.519,0.519,0.519],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0]},"t":0,"s":[0,0,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":8,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":55.063,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":78.063,"s":[100,-100,100]},{"t":102.0634765625,"s":[100,100,100]}],"ix":6}},"ao":0,"ip":0,"op":105,"st":0,"bm":0},{"ddd":0,"ind":18,"ty":2,"nm":"Layer 18","parent":1,"refId":"image_16","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.831],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":90.724609375,"s":[-93.048]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.167},"t":0,"s":[43.284,56.722,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.226},"t":9.443,"s":[-308.83,-718.718,0],"to":[0,0,0],"ti":[136,-2018,0]},{"t":93.0234375,"s":[-742.128,544.375,0]}],"ix":2},"a":{"a":0,"k":[44.5,189,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.519,0.519,0.519],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0]},"t":0,"s":[0,0,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":8,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":43.755,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":66.755,"s":[100,-100,100]},{"t":90.7548828125,"s":[100,100,100]}],"ix":6}},"ao":0,"ip":0,"op":105,"st":0,"bm":0},{"ddd":0,"ind":19,"ty":2,"nm":"Layer 19","parent":1,"refId":"image_17","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.831],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":85.91796875,"s":[323.925]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.167},"t":0,"s":[43.284,43.222,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.177},"t":7.259,"s":[131.777,-1201.391,0],"to":[0,0,0],"ti":[320,-2754,0]},{"t":80,"s":[137.872,480.375,0]}],"ix":2},"a":{"a":0,"k":[40,162,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.519,0.519,0.519],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0]},"t":0,"s":[0,0,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":8,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":50.032,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":73.032,"s":[100,-100,100]},{"t":97.0322265625,"s":[100,100,100]}],"ix":6}},"ao":0,"ip":0,"op":105,"st":0,"bm":0},{"ddd":0,"ind":20,"ty":2,"nm":"Layer 20","parent":1,"refId":"image_18","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.831],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":81.072265625,"s":[-63.263]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.167},"t":0,"s":[43.284,58.722,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.211},"t":9.802,"s":[-139.641,-990.282,0],"to":[0,0,0],"ti":[320,-2754,0]},{"t":67.1083984375,"s":[-694.128,544.375,0]}],"ix":2},"a":{"a":0,"k":[41,193,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.519,0.519,0.519],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0]},"t":0,"s":[0,0,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":8,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":37.565,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":60.565,"s":[100,-100,100]},{"t":84.5654296875,"s":[100,100,100]}],"ix":6}},"ao":0,"ip":0,"op":105,"st":0,"bm":0},{"ddd":0,"ind":21,"ty":2,"nm":"Layer 21","parent":1,"refId":"image_19","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.831],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":96.97265625,"s":[-315.838]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.167},"t":0,"s":[43.284,53.722,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.209},"t":8.128,"s":[-206.256,-1145.789,0],"to":[0,0,0],"ti":[320,-2754,0]},{"t":102,"s":[-806.128,480.375,0]}],"ix":2},"a":{"a":0,"k":[52,183,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.519,0.519,0.519],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0]},"t":0,"s":[0,0,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":8,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":65.619,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":88.619,"s":[100,-100,100]},{"t":112.619140625,"s":[100,100,100]}],"ix":6}},"ao":0,"ip":0,"op":105,"st":0,"bm":0},{"ddd":0,"ind":22,"ty":2,"nm":"Layer 22","parent":1,"refId":"image_20","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.831],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":114.46484375,"s":[-259.512]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.167},"t":0,"s":[43.284,56.222,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.238},"t":8.52,"s":[-366.619,-1069.492,0],"to":[0,0,0],"ti":[320,-2754,0]},{"t":96.6259765625,"s":[-686.128,488.375,0]}],"ix":2},"a":{"a":0,"k":[44.5,188,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.519,0.519,0.519],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0]},"t":0,"s":[0,0,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":8,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":40.262,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":63.262,"s":[100,-100,100]},{"t":87.26171875,"s":[100,100,100]}],"ix":6}},"ao":0,"ip":0,"op":105,"st":0,"bm":0},{"ddd":0,"ind":23,"ty":2,"nm":"Layer 23","parent":1,"refId":"image_21","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.831],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":78.58203125,"s":[-236.661]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.167},"t":0,"s":[43.284,10.722,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.173},"t":16.523,"s":[-505.73,-563.466,0],"to":[0,0,0],"ti":[320,-2754,0]},{"t":73.544921875,"s":[81.872,488.375,0]}],"ix":2},"a":{"a":0,"k":[88,97,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.519,0.519,0.519],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0]},"t":0,"s":[0,0,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":8,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":27.63,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":50.63,"s":[100,-100,100]},{"t":74.6298828125,"s":[100,100,100]}],"ix":6}},"ao":0,"ip":0,"op":105,"st":0,"bm":0},{"ddd":0,"ind":24,"ty":2,"nm":"Layer 24","parent":1,"refId":"image_22","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.831],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":79.9482421875,"s":[-65.959]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.167},"t":0,"s":[43.284,33.722,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.182},"t":14.115,"s":[-361.573,-377.534,0],"to":[0,0,0],"ti":[24,-2354,0]},{"t":84.2890625,"s":[-78.128,520.375,0]}],"ix":2},"a":{"a":0,"k":[72,143,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.519,0.519,0.519],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0]},"t":0,"s":[0,0,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":8,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":48.496,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":71.496,"s":[100,-100,100]},{"t":95.49609375,"s":[100,100,100]}],"ix":6}},"ao":0,"ip":0,"op":105,"st":0,"bm":0},{"ddd":0,"ind":25,"ty":2,"nm":"Layer 25","parent":1,"refId":"image_23","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.831],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":104.2109375,"s":[-60.331]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.167},"t":0,"s":[43.284,6.222,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.197},"t":14.122,"s":[-468.314,-460.573,0],"to":[0,0,0],"ti":[296,-2114,0]},{"t":90.72265625,"s":[-198.128,600.375,0]}],"ix":2},"a":{"a":0,"k":[71,88,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.519,0.519,0.519],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0]},"t":0,"s":[0,0,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":8,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":26.418,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":49.418,"s":[100,-100,100]},{"t":73.41796875,"s":[100,100,100]}],"ix":6}},"ao":0,"ip":0,"op":105,"st":0,"bm":0},{"ddd":0,"ind":26,"ty":2,"nm":"Layer 26","parent":1,"refId":"image_24","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.831],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":77.802734375,"s":[328.004]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.167},"t":0,"s":[43.284,3.722,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.133},"t":7.976,"s":[-259.378,-370.437,0],"to":[0,0,0],"ti":[272,-2330,0]},{"t":88,"s":[-70.128,528.375,0]}],"ix":2},"a":{"a":0,"k":[56,83,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.519,0.519,0.519],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0]},"t":0,"s":[0,0,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":8,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":62.791,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":85.791,"s":[100,-100,100]},{"t":109.791015625,"s":[100,100,100]}],"ix":6}},"ao":0,"ip":0,"op":105,"st":0,"bm":0},{"ddd":0,"ind":27,"ty":2,"nm":"Layer 27","parent":1,"refId":"image_25","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.831],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":83.486328125,"s":[-312.749]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.167},"t":0,"s":[43.284,41.722,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.235},"t":13.124,"s":[-585.135,-722.821,0],"to":[0,0,0],"ti":[208,-2018,0]},{"t":105.2685546875,"s":[-422.128,512.375,0]}],"ix":2},"a":{"a":0,"k":[107,159,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.519,0.519,0.519],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0]},"t":0,"s":[0,0,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":8,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":28.163,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":51.163,"s":[100,-100,100]},{"t":75.1630859375,"s":[100,100,100]}],"ix":6}},"ao":0,"ip":0,"op":105,"st":0,"bm":0},{"ddd":0,"ind":28,"ty":2,"nm":"Layer 28","parent":1,"refId":"image_26","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.831],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":110.236328125,"s":[463.981]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.167},"t":0,"s":[43.284,91.722,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.212},"t":7.548,"s":[-29.778,-1062.058,0],"to":[0,0,0],"ti":[320,-2754,0]},{"t":86.9501953125,"s":[457.872,488.375,0]}],"ix":2},"a":{"a":0,"k":[57,259,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.519,0.519,0.519],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0]},"t":0,"s":[0,0,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":8,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":53.864,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":76.863,"s":[100,-100,100]},{"t":100.8642578125,"s":[100,100,100]}],"ix":6}},"ao":0,"ip":0,"op":105,"st":0,"bm":0},{"ddd":0,"ind":29,"ty":2,"nm":"Layer 29","parent":1,"refId":"image_27","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.831],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":89.0361328125,"s":[-484.46]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.167},"t":0,"s":[43.284,85.722,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.241},"t":9.685,"s":[-411.043,-866.994,0],"to":[0,0,0],"ti":[-80,-2138,0]},{"t":99.0771484375,"s":[-678.128,568.375,0]}],"ix":2},"a":{"a":0,"k":[67,247,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.519,0.519,0.519],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0]},"t":0,"s":[0,0,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":8,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":35.429,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":58.429,"s":[100,-100,100]},{"t":82.4287109375,"s":[100,100,100]}],"ix":6}},"ao":0,"ip":0,"op":105,"st":0,"bm":0},{"ddd":0,"ind":30,"ty":2,"nm":"Layer 30","parent":1,"refId":"image_28","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.831],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":104.14453125,"s":[-431.019]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.167},"t":0,"s":[43.284,91.722,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.152},"t":17.008,"s":[-79.428,-780.951,0],"to":[0,0,0],"ti":[320,-2754,0]},{"t":97,"s":[-758.128,584.375,0]}],"ix":2},"a":{"a":0,"k":[56,259,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.519,0.519,0.519],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0]},"t":0,"s":[0,0,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":8,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":62.371,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":85.371,"s":[100,-100,100]},{"t":109.37109375,"s":[100,100,100]}],"ix":6}},"ao":0,"ip":0,"op":105,"st":0,"bm":0},{"ddd":0,"ind":31,"ty":2,"nm":"Layer 31","parent":1,"refId":"image_29","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.831],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":78.328125,"s":[446.848]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.167},"t":0,"s":[43.284,6.722,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.231},"t":8.974,"s":[318.579,-1167.501,0],"to":[0,0,0],"ti":[320,-2754,0]},{"t":80.373046875,"s":[401.872,440.375,0]}],"ix":2},"a":{"a":0,"k":[28,89,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.519,0.519,0.519],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0]},"t":0,"s":[0,0,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":8,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":43.723,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":66.723,"s":[100,-100,100]},{"t":90.72265625,"s":[100,100,100]}],"ix":6}},"ao":0,"ip":0,"op":105,"st":0,"bm":0},{"ddd":0,"ind":32,"ty":2,"nm":"Layer 32","parent":1,"refId":"image_30","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.831],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":95.12890625,"s":[-50.488]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.167},"t":0,"s":[43.284,15.222,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.238},"t":10.871,"s":[492.056,-1143.467,0],"to":[0,0,0],"ti":[296,-2618,0]},{"t":103.08984375,"s":[641.872,448.375,0]}],"ix":2},"a":{"a":0,"k":[29,106,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.519,0.519,0.519],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0]},"t":0,"s":[0,0,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":8,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":41.872,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":64.872,"s":[100,-100,100]},{"t":88.8720703125,"s":[100,100,100]}],"ix":6}},"ao":0,"ip":0,"op":105,"st":0,"bm":0},{"ddd":0,"ind":33,"ty":2,"nm":"Layer 33","parent":1,"refId":"image_31","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.831],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":93.06640625,"s":[-141.337]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.167},"t":0,"s":[43.284,-11.278,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.266},"t":7.979,"s":[679.848,-661.065,0],"to":[0,0,0],"ti":[384,-1434,0]},{"t":83.6396484375,"s":[409.872,408.375,0]}],"ix":2},"a":{"a":0,"k":[54.5,53,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.519,0.519,0.519],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0]},"t":0,"s":[0,0,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":8,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":54.619,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":77.619,"s":[100,-100,100]},{"t":101.619140625,"s":[100,100,100]}],"ix":6}},"ao":0,"ip":0,"op":105,"st":0,"bm":0},{"ddd":0,"ind":34,"ty":2,"nm":"Layer 34","parent":1,"refId":"image_32","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.831],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":95.068359375,"s":[-111.289]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.167},"t":0,"s":[43.284,17.222,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.214},"t":14.7,"s":[80.419,-1124.493,0],"to":[0,0,0],"ti":[320,-2754,0]},{"t":97.6572265625,"s":[345.872,400.375,0]}],"ix":2},"a":{"a":0,"k":[26,110,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.519,0.519,0.519],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0]},"t":0,"s":[0,0,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":8,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":33.359,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":56.359,"s":[100,-100,100]},{"t":80.359375,"s":[100,100,100]}],"ix":6}},"ao":0,"ip":0,"op":105,"st":0,"bm":0},{"ddd":0,"ind":35,"ty":2,"nm":"Layer 35","parent":1,"refId":"image_33","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.831],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":99.09375,"s":[461.811]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.167},"t":0,"s":[43.284,15.222,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.17},"t":16.078,"s":[300.585,-845.216,0],"to":[0,0,0],"ti":[320,-2754,0]},{"t":104,"s":[305.872,472.375,0]}],"ix":2},"a":{"a":0,"k":[29,106,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.519,0.519,0.519],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0]},"t":0,"s":[0,0,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":8,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":30.469,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":53.469,"s":[100,-100,100]},{"t":77.46875,"s":[100,100,100]}],"ix":6}},"ao":0,"ip":0,"op":105,"st":0,"bm":0},{"ddd":0,"ind":36,"ty":2,"nm":"Layer 36","parent":1,"refId":"image_34","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.831],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":120,"s":[-363.787]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.167},"t":0,"s":[43.284,41.722,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.186},"t":15.279,"s":[550.606,-859.847,0],"to":[0,0,0],"ti":[456,-2490,0]},{"t":102,"s":[81.872,424.375,0]}],"ix":2},"a":{"a":0,"k":[40.5,159,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.519,0.519,0.519],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0]},"t":0,"s":[0,0,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":8,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":26.315,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":49.315,"s":[100,-100,100]},{"t":73.3154296875,"s":[100,100,100]}],"ix":6}},"ao":0,"ip":0,"op":105,"st":0,"bm":0},{"ddd":0,"ind":37,"ty":2,"nm":"Layer 37","parent":1,"refId":"image_35","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.831],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":113.2197265625,"s":[-378.462]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.167},"t":0,"s":[43.284,24.222,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.192},"t":8.781,"s":[565.396,-1046.958,0],"to":[0,0,0],"ti":[320,-2754,0]},{"t":103,"s":[665.872,568.375,0]}],"ix":2},"a":{"a":0,"k":[45.5,124,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.519,0.519,0.519],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0]},"t":0,"s":[0,0,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":8,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":55.754,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":78.753,"s":[100,-100,100]},{"t":102.75390625,"s":[100,100,100]}],"ix":6}},"ao":0,"ip":0,"op":105,"st":0,"bm":0},{"ddd":0,"ind":38,"ty":2,"nm":"Layer 38","parent":1,"refId":"image_36","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.831],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":80.3076171875,"s":[54.174]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.167},"t":0,"s":[43.284,23.222,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.189},"t":16.634,"s":[221.723,-864.331,0],"to":[0,0,0],"ti":[320,-2754,0]},{"t":81.63671875,"s":[305.872,448.375,0]}],"ix":2},"a":{"a":0,"k":[38.5,122,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.519,0.519,0.519],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0]},"t":0,"s":[0,0,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":8,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":41.028,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":64.028,"s":[100,-100,100]},{"t":88.0283203125,"s":[100,100,100]}],"ix":6}},"ao":0,"ip":0,"op":105,"st":0,"bm":0},{"ddd":0,"ind":39,"ty":2,"nm":"Layer 39","parent":1,"refId":"image_37","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.831],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":95.115234375,"s":[299.774]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.167},"t":0,"s":[43.284,24.222,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.259},"t":7.976,"s":[657.604,-911.115,0],"to":[0,0,0],"ti":[400,-1810,0]},{"t":74.6279296875,"s":[529.872,440.375,0]}],"ix":2},"a":{"a":0,"k":[37.5,124,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.519,0.519,0.519],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0]},"t":0,"s":[0,0,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":8,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":30.896,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":53.896,"s":[100,-100,100]},{"t":77.896484375,"s":[100,100,100]}],"ix":6}},"ao":0,"ip":0,"op":105,"st":0,"bm":0},{"ddd":0,"ind":40,"ty":2,"nm":"Layer 40","parent":1,"refId":"image_38","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.831],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":94.71484375,"s":[408.356]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.167},"t":0,"s":[43.284,23.222,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.213},"t":14.321,"s":[458.294,-754.048,0],"to":[0,0,0],"ti":[336,-2346,0]},{"t":104.5693359375,"s":[529.872,440.375,0]}],"ix":2},"a":{"a":0,"k":[38.5,122,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.519,0.519,0.519],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0]},"t":0,"s":[0,0,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":8,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":64.818,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":87.818,"s":[100,-100,100]},{"t":111.818359375,"s":[100,100,100]}],"ix":6}},"ao":0,"ip":0,"op":105,"st":0,"bm":0},{"ddd":0,"ind":41,"ty":2,"nm":"Layer 41","parent":1,"refId":"image_39","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.831],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":87.1083984375,"s":[276.287]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.167},"t":0,"s":[43.284,59.222,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.206},"t":13.954,"s":[309.209,-966.075,0],"to":[0,0,0],"ti":[320,-2754,0]},{"t":83.669921875,"s":[529.872,440.375,0]}],"ix":2},"a":{"a":0,"k":[52,194,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.519,0.519,0.519],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0]},"t":0,"s":[0,0,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":8,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":50.654,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":73.654,"s":[100,-100,100]},{"t":97.654296875,"s":[100,100,100]}],"ix":6}},"ao":0,"ip":0,"op":105,"st":0,"bm":0},{"ddd":0,"ind":42,"ty":2,"nm":"Layer 42","parent":1,"refId":"image_40","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.831],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":106.5673828125,"s":[-1.984]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.167},"t":0,"s":[43.284,58.222,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.186},"t":14.708,"s":[225.01,-660.153,0],"to":[0,0,0],"ti":[320,-2754,0]},{"t":92.6083984375,"s":[649.872,544.375,0]}],"ix":2},"a":{"a":0,"k":[55,192,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.519,0.519,0.519],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0]},"t":0,"s":[0,0,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":8,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":35.781,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":58.781,"s":[100,-100,100]},{"t":82.78125,"s":[100,100,100]}],"ix":6}},"ao":0,"ip":0,"op":105,"st":0,"bm":0},{"ddd":0,"ind":43,"ty":2,"nm":"Layer 43","parent":1,"refId":"image_41","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.831],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":87.626953125,"s":[-494.691]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.167},"t":0,"s":[43.284,56.722,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.206},"t":11.514,"s":[32.736,-923.559,0],"to":[0,0,0],"ti":[320,-2754,0]},{"t":88.0556640625,"s":[529.872,440.375,0]}],"ix":2},"a":{"a":0,"k":[44.5,189,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.519,0.519,0.519],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0]},"t":0,"s":[0,0,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":8,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":56.146,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":79.146,"s":[100,-100,100]},{"t":103.146484375,"s":[100,100,100]}],"ix":6}},"ao":0,"ip":0,"op":105,"st":0,"bm":0},{"ddd":0,"ind":44,"ty":2,"nm":"Layer 44","parent":1,"refId":"image_42","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.831],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":78.0693359375,"s":[-385.194]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.167},"t":0,"s":[43.284,59.222,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.216},"t":14.825,"s":[244.176,-1136.626,0],"to":[0,0,0],"ti":[320,-2754,0]},{"t":91.99609375,"s":[529.872,440.375,0]}],"ix":2},"a":{"a":0,"k":[42,194,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.519,0.519,0.519],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0]},"t":0,"s":[0,0,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":8,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":36.859,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":59.859,"s":[100,-100,100]},{"t":83.859375,"s":[100,100,100]}],"ix":6}},"ao":0,"ip":0,"op":105,"st":0,"bm":0},{"ddd":0,"ind":45,"ty":2,"nm":"Layer 45","parent":1,"refId":"image_43","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.735],"y":[0.9]},"o":{"x":[0.159],"y":[0.09]},"t":0,"s":[0]},{"t":87,"s":[445.112]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.167},"t":0,"s":[43.284,56.722,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.178},"t":15.908,"s":[347.991,-718.714,0],"to":[0,0,0],"ti":[144,-2674,0]},{"t":121,"s":[529.872,440.375,0]}],"ix":2},"a":{"a":0,"k":[44.5,189,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.519,0.519,0.519],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0]},"t":0,"s":[0,0,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":8,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":42.171,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":65.171,"s":[100,-100,100]},{"t":89.1708984375,"s":[100,100,100]}],"ix":6}},"ao":0,"ip":0,"op":105,"st":0,"bm":0},{"ddd":0,"ind":46,"ty":2,"nm":"Layer 46","parent":1,"refId":"image_44","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.831],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":91.416015625,"s":[333.236]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.167},"t":0,"s":[43.284,56.222,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.238},"t":8,"s":[400.078,-1076.292,0],"to":[0,0,0],"ti":[320,-2754,0]},{"t":99.583984375,"s":[529.872,440.375,0]}],"ix":2},"a":{"a":0,"k":[44.5,188,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.519,0.519,0.519],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0]},"t":0,"s":[0,0,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":8,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":65.277,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":88.277,"s":[100,-100,100]},{"t":112.27734375,"s":[100,100,100]}],"ix":6}},"ao":0,"ip":0,"op":105,"st":0,"bm":0},{"ddd":0,"ind":47,"ty":2,"nm":"Layer 47","parent":1,"refId":"image_45","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.831],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":113.0849609375,"s":[-485.494]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.167},"t":0,"s":[43.284,10.722,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.208},"t":15.515,"s":[544.431,-563.471,0],"to":[0,0,0],"ti":[384,-1994,0]},{"t":94.806640625,"s":[529.872,440.375,0]}],"ix":2},"a":{"a":0,"k":[88,97,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.519,0.519,0.519],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0]},"t":0,"s":[0,0,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":8,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":54.68,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":77.68,"s":[100,-100,100]},{"t":101.6796875,"s":[100,100,100]}],"ix":6}},"ao":0,"ip":0,"op":105,"st":0,"bm":0},{"ddd":0,"ind":48,"ty":2,"nm":"Layer 48","parent":1,"refId":"image_46","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.831],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":97.6953125,"s":[-409.417]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.167},"t":0,"s":[43.284,33.722,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.215},"t":7.144,"s":[400.007,-377.545,0],"to":[0,0,0],"ti":[240,-1674,0]},{"t":92.912109375,"s":[593.872,448.375,0]}],"ix":2},"a":{"a":0,"k":[72,143,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.519,0.519,0.519],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0]},"t":0,"s":[0,0,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":8,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":42.787,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":65.787,"s":[100,-100,100]},{"t":89.787109375,"s":[100,100,100]}],"ix":6}},"ao":0,"ip":0,"op":105,"st":0,"bm":0},{"ddd":0,"ind":49,"ty":2,"nm":"Layer 49","parent":1,"refId":"image_47","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.831],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":115.154296875,"s":[-336.791]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.167},"t":0,"s":[43.284,6.222,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.173},"t":8.119,"s":[506.829,-460.573,0],"to":[0,0,0],"ti":[320,-2754,0]},{"t":103,"s":[633.872,456.375,0]}],"ix":2},"a":{"a":0,"k":[71,88,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.519,0.519,0.519],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0]},"t":0,"s":[0,0,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":8,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":32.079,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":55.079,"s":[100,-100,100]},{"t":79.0791015625,"s":[100,100,100]}],"ix":6}},"ao":0,"ip":0,"op":105,"st":0,"bm":0},{"ddd":0,"ind":50,"ty":2,"nm":"Layer 50","parent":1,"refId":"image_48","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.831],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":88.5029296875,"s":[-210.512]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.167},"t":0,"s":[43.284,3.722,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.194},"t":7.479,"s":[297.883,-370.437,0],"to":[0,0,0],"ti":[344,-2146,0]},{"t":102.8251953125,"s":[609.872,504.375,0]}],"ix":2},"a":{"a":0,"k":[56,83,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.519,0.519,0.519],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0]},"t":0,"s":[0,0,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":8,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":35.676,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":58.676,"s":[100,-100,100]},{"t":82.67578125,"s":[100,100,100]}],"ix":6}},"ao":0,"ip":0,"op":105,"st":0,"bm":0},{"ddd":0,"ind":51,"ty":2,"nm":"Layer 51","parent":1,"refId":"image_49","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.831],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":92.5166015625,"s":[317.237]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.167},"t":0,"s":[43.284,41.722,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.245},"t":7.901,"s":[623.397,-722.821,0],"to":[0,0,0],"ti":[96,-1842,0]},{"t":71.728515625,"s":[745.872,504.375,0]}],"ix":2},"a":{"a":0,"k":[107,159,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.519,0.519,0.519],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0]},"t":0,"s":[0,0,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":8,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":33.498,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":56.497,"s":[100,-100,100]},{"t":80.498046875,"s":[100,100,100]}],"ix":6}},"ao":0,"ip":0,"op":105,"st":0,"bm":0},{"ddd":0,"ind":52,"ty":2,"nm":"Layer 52","parent":1,"refId":"image_50","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.831],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":84.5986328125,"s":[159.427]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.167},"t":0,"s":[43.284,85.722,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.169},"t":10.031,"s":[449.823,-866.997,0],"to":[0,0,0],"ti":[320,-2754,0]},{"t":95,"s":[601.872,560.375,0]}],"ix":2},"a":{"a":0,"k":[67,247,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.519,0.519,0.519],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0]},"t":0,"s":[0,0,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":8,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":65.121,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":88.121,"s":[100,-100,100]},{"t":112.12109375,"s":[100,100,100]}],"ix":6}},"ao":0,"ip":0,"op":105,"st":0,"bm":0},{"ddd":0,"ind":53,"ty":2,"nm":"Layer 53","parent":1,"refId":"image_51","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.831],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":107.0546875,"s":[146.171]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.167},"t":0,"s":[43.284,91.722,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.194},"t":11.752,"s":[117.819,-780.948,0],"to":[0,0,0],"ti":[72,-3306,0]},{"t":95.6337890625,"s":[697.872,536.375,0]}],"ix":2},"a":{"a":0,"k":[56,259,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.519,0.519,0.519],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0]},"t":0,"s":[0,0,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":8,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":42.219,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":65.218,"s":[100,-100,100]},{"t":89.21875,"s":[100,100,100]}],"ix":6}},"ao":0,"ip":0,"op":105,"st":0,"bm":0}],"markers":[]} \ No newline at end of file diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 64586e5..2d52dce 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -267,4 +267,14 @@ No preguntar de nuevo Sitio no disponible Fecha no disponible + ¿Cuántas veces te has lavado las manos? 💦👏 + Hoy te has lavado las manos %1$s 👏 + Esta semana te has lavado las manos %1$s 🤗 + Este mes te has lavado las manos %1$s 😱 + + %d vez + %d veces + + ¡Añade otra más! 🙌 + Bueno, quizás una menos 😅 diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index fd69e76..e7a6ec6 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -5,4 +5,5 @@ 80dp 16dp 5dp + 8dp \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index f4a3fad..d986c7f 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -295,4 +295,14 @@ Don\'t ask again Website not available Date not available + How many times did you washed your hands? 💦👏 + Today you washed your hands %1$s 👏 + This week you have washed your hands %1$s 🤗 + This month you washed your hands %1$s 😱 + + %d time + %d times + + Add another! 🙌 + Well, maybe one less 😅 diff --git a/extras/25160-leaves.json b/extras/25160-leaves.json new file mode 100644 index 0000000..49b5ab4 --- /dev/null +++ b/extras/25160-leaves.json @@ -0,0 +1 @@ +{"v":"5.6.10","fr":30,"ip":0,"op":105,"w":512,"h":512,"nm":"tree-1781554","ddd":0,"assets":[{"id":"image_0","w":952,"h":954,"u":"","p":"data:image/png;base64,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","e":1},{"id":"image_1","w":58,"h":106,"u":"","p":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADoAAABqCAMAAADa67LtAAAAJHpUWHRDcmVhdG9yAAAImXNMyU9KVXBMK0ktUnBNS0tNLikGAEF6Bs5qehXFAAAACXBIWXMAAAABAAAAAQBPJcTWAAAA+VBMVEVHcEzI6jvW6joe6tNO7o0U7+RH9VnJ6ztE8os7pP9J9Fw0sPov+I1Y80OV7jwa9c0Z5uk39ncf+7MY+sY2qv1+7zwg+q8m+aPa6jrF6zsj+qk79m9L9E4c+7sQ+9gz94GE7zwkz/Fh8kUoyPMox/Oe7Twc4eoN+uM59nFO80FH9FAS8+UN/OIQ9uQU7ucb4esX6OkM/eIe2+0Q/NkS/NIW+8kj0fAZ+8Ed+7gg+rAox/Mk+ad/7z0n+Z957z1y8D4r+Jcu+I6G7j1l8T5r8T4tvfYx94aM7jw1935e8j5Y8j+T7Tw/9WVC9Vwzsfqd7Tyr7Dy56zs5pf5yJmjnAAAAJ3RSTlMAQP4vIGNa/hBANIF5nV1zordVgsLc1ECArqeX17zQ26raelC84ECyq+TNAAADUElEQVRYw63YZ1fbMBQGYKVRjDCxTwaJCQlZBcpqAyU0UOiktIVC1///MfWUh14N+1Qf0XlA1hXSvZcQPuoNUnU0Fq2qtLU4rWpni9NXFe1rnx53q8j9kL6ssleNiL6oYFsxrWB3E3pS2r7h9KxTTtYzdFkvRZ9n6PlFKbuRpctVGbuboxd9VuIY5umqhH1boJc7xhss0Ktt0w0W6XXTcIMBfWd2NFqIfrRN6Cakn0zsHaYTVpl+bWvluox+H2pjI6UHli42cnpgV6ePVB0bFR2Z03GBPjkq+jNDz1pFemQZ0pNxq0iP5Fu1nqNndYGOmCGl4yI9lH7uRp52GgI9tMxol6wEek+N6Jh0RSqJ7maeLgkV6MP93ISeU7Ij0lvbhHZIB9AeitDfAvXTgr5IvzgG1L++u4B+qOnpmBCKaE9Pl/5XbQP6XtjldYF6hHiI3lAtDdKnPqJTLQ2euSain2s66u8TYZD2dPQiiP42ot9cHfX3iXiQ7ulomGZOEP3hamiYDjQh3dPQy+DnDNJfrpquwti3Id3T0DAZ6ED6u6amUTo+gXSgplHa1IT0D1PScJ8IxdRV0yjBbEM6UNMoabIgXeOUIRrneZjW5LeET/vR1BDSLSW9iqZsSAfSezikXjQ3QXRNTeMazUL0mey5yuXDDFKqpNfx5BDRmuRVj2n8my0lpZAmmfRIRQmkSfI/BHRLna3Fh4LYSopzRMZXrKD1O1XZMFd9K5khmlQrTElhscILLEdFKUxqk1lLRYuVZES9ZMXyMxw0CBBtpitWUDIDtJ2uWPb/mu1L5BJ4PvsguyV4D0ZamDlKug8o/1hbdq3F1aRI07pshC/TJAQLsVjhkw6+wnl5JlKPrzhLt0ABLNC06L7NUJArsrG8CHUyFDYKjouFGX8PLfi+ZiNUpGmlAV/13KGSlr4Op7J6qZGnEz5hJXQgb7Lli1B+zzOU/BRtjqbhmYKUS7BZmoZnHtGpujGYren4ptgRpZpG0GVK0/D0AjrVNpF4nfOYOVABrWmbHCytOPjPXDELx4Pn/nzF1KeuWeerH9H0QPVujP5osOhhlJmmkb1xienwJgHlK3Z7xHywpk/5HlOXlBm0/XTESMVhjeaV2/rEcmzyP8c/16Z1ezJhv0cAAAAASUVORK5CYII=","e":1},{"id":"image_2","w":58,"h":106,"u":"","p":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADoAAABqCAMAAADa67LtAAAAJHpUWHRDcmVhdG9yAAAImXNMyU9KVXBMK0ktUnBNS0tNLikGAEF6Bs5qehXFAAAACXBIWXMAAAABAAAAAQBPJcTWAAAAwFBMVEVHcEy80SG40zbp2w/P1hzv3QzX2Bqr0TrE1BvX2BoTrkvt3A3D1CPs3A231DojsUXu3A3g2hMQrExwwiw0tT+30zXv3Q52wyoPrUuWySD33gXI1RtZvTMGq03U2CPL1yrC1TL93wHg2hm81Dfp3BLv3Q210zzl2xDz3QYmskPY2SDP1yfG1i7b2R0Eq01PujdavTNmvy/43gZDuDpywix9xCg3tj6VySGJxiWgyx2szhq30Bbb1wvD0xPP1Q8TrknxkElrAAAAHnRSTlMAIH62MHxQQBBgPMqhm7xZQPCC197V2Wiom+jYoNDbFk4TAAADgUlEQVRYw63Ya3uaMBgG4CAIQaWIWs9d1WLt2q7d3Hnr4f//qxEO4ZAnB7iWj+O695QkhvcNIXxYPuk6ertRVzraPXa1l7vH6452l9DrXhfpMfrw4HeaJZb68MHvMksZ7WAvCnrf2t5w+sltJ50KjZxW1K3QQzvbq9J29qJGT0PaYhvWadzC3tToIY6H5hPcSI2PgfEEN1OPx77pBAupxzuzbTUCqXfnsdnagNTz2mR5b2Hqfk5b0zz1vNcvkYdTz/u9dolcWepeO809aerdadyOVlJPkaVeG0Xqy0b9u6nSYT315U15sN9W6Iegkfr26hvSeztopL7+dRTLWntXR6CX1JBawyb9s5KvTY26vkB/L8xon0QC/ekYUZsEIp3KdkSNRsQS6a+FCT1YxBbpd8+EusQFdErhjqjT5Cyci/Tb1oDahPQB/TLQ03lSgCE61dND8lY2oJ8nYB82aHIyjBF9trSUfTPmiG61NJkn0kf060BHk3kiFNKZjp7Y6geI/gh1lNU/Y0hnOpp+W9eIfgw1lM0T6UM6U9M4Yv9OIa3FgtQ4XXsb0islPcRpnehC+j5Qp2Y1yBrSpTo1nSfSh/SJKlPjrPfBNFSmxtnhaUO6VKdmnygf0idOKUrNi4g1pAP5KXHi5WUA6URO2fc1rzshXVY+6qCWyMuPIaJX0iM8Tc0LUx/RJ9nnKku18zmEdCClLHWdPwyU1EOpx/zgdJXUQqm8xNsAGuIKpkgtKssRoBN1tTYvWgolxTVi8dvaqKgDa8Sio+wppok1VyC16FaokvZQKq/eRypqwXq4eOqraKOTbPQ5VL6H2X+MUnlrtlJRcglS+cu60h9dGou6K/5UekpkCw9qf15TrpTUAx0Hf1lPdqzlkyz2dDZ/KDlMiyXYiX0Of7iq0iuxPRP7HN5aOVU6AQ2wkFo23dMKBbUi3TRTyyZ0VdJ3eFFw3ezp+PfQg9/X6go1e7ryFqikIW4D/EZq2fquOJX1S349dc4fLAq6lHZpfr1/5WUsRcVP09Y6ycrygJJLtJXUci8uMjpRdsK1no5PipNRS3MRFJW07HunjC61l0hDTsu/eMvoQHvJQXnHEZUvK1bhePDan28oK6Gh2c3XPKPlBcH02SiU/dFBSiM+x9vnkJiO8ZpVa3yOFzNiPmi/eqVhhaTNsOy3V0o6DnfTI52Hv/LI/xz/AG9w2PZ68exoAAAAAElFTkSuQmCC","e":1},{"id":"image_3","w":109,"h":53,"u":"","p":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAG0AAAA1CAMAAAB8+5X+AAAAJHpUWHRDcmVhdG9yAAAImXNMyU9KVXBMK0ktUnBNS0tNLikGAEF6Bs5qehXFAAAACXBIWXMAAAABAAAAAQBPJcTWAAAArlBMVEVHcExHgXdyUiYNwecZtNBRd2ZlYkI9kZQunKcvm6UcsMlEhX4PvuFDhoAopLQSvN5YcFotnakJxe0IxvBbblhyUyghqr9uVy5SdWNba1FeaU08jo00lpwrn6wnpLRLfXAiqb1PeGc9jIpDhX9ba1FHgngWttMersVjY0JUc2AbssxXcFlvVSs3kpYwmqI6kJBAiYUSu9xnXzt3TRxgZklqWzUHxvANweYunKcPvuGOHF3zAAAAHHRSTlMAxM1CI302EIdAzESkn2F0XcjKhiCQoHLurt3wCSH7ygAAAxJJREFUWMPtmFl7ojAYhQUEZJNFkWIXd3Sm2BZxqf//j03CZiJLEqS9mnPJzfucnHxL6PUeUV8ZDgoNlX7vx6QNuM/9fv/y8vHx/Qr0BjS3J0O5c5JsGc/LzxQGad85DWjbMVAzvr6WS0D7RGmvOW27/asPO0KNjdVqVdBeSt62kAZkdhCixm02KA0/yXlmDtKCQO8/mNbTbNNEQ7wFQNEDPFkELJy2r8stpQVRWx5gLWZARG83WgR0jUZyK9aCTJu/FbT0KKPr9eIIzKw/ELZIYBvCnZzfcktol4vTZ2MBEb2Vc8tpl4tJC7Oe3t8xGHVuBe18PjtU6UnT9XtOS3j1d7KcW+EN4M7k9Mb+GsIQbxT1Nq/yBjQiBOYe1+t15q19blfHGY1MQWg2Zx0TGHKSM5o7eaOpumkKdNdRmu6Ox4SH0ejqTZ3QYrJD3O12BSzPbUG+k+pkwDzBrd2hoDXlht1JMLaVNhOMPxxKtMaTbAmC8g6nnLYm52YbrUHwdvCn0wn1Vp/bszFQmBp8qeK8MERoiLe73DhDG7MaMUf3xhLYqTq3vJcYosa6dgoj3Tk7+Ec3hKr2lubGGRaTJVlIOKn698YAK6zLjRM1mdqLYJq6qqJdGW/LbhyHcY23J1+USARFgVv6xFbVfAZEWFc2MWMIDPM29a0K0ljTtPQNYAN9oH2yYgZc8AngxVDlk4QoSRJTGVwitN6yh0DFDIDegttJ6kiWfMJKaGFYeyfL05RyL8FgVpwJ8k509UbcFNDZje4JLsKqyI1tBlTuJQiMj+MSjrZP3u/KyF4S3HLDNiDJLXBV3ppnN8Veot9VqezxqbW43hvrXlLkplfNNAikyG3GsJcktLpNa+zyrLk1vAPS3K5Nb1TJ5ZvqjTm3gLSiACB9bvvG3CKVpotLHt9JvVE/Ncaez7IFVeWmMi16suVPjy125Sw3s8VK5Pnt6m3S9nkviX5DvVXmpj7260ITOXKfzHObKL3HJYFpSvamDjr8j6cBZD2tU1RxWzULbg0YzbYHwx/8O5lglUy9//pt/QOW6sIbig4V/AAAAABJRU5ErkJggg==","e":1},{"id":"image_4","w":79,"h":106,"u":"","p":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAE8AAABqCAMAAADKi/wxAAAAJHpUWHRDcmVhdG9yAAAImXNMyU9KVXBMK0ktUnBNS0tNLikGAEF6Bs5qehXFAAAACXBIWXMAAAABAAAAAQBPJcTWAAAAsVBMVEVHcEy30zfp2xLz3Qfg2hbs3A7u3A2rzie/0yLZ2R+ozy3Y2Bi61DO60iwHq04Vr0oOrUtrwS4UrkqYyyVLuTglsUTN1yjC1TH93wHy3QrT2CTs3A7X2SDg2hnk2xa20zvI1i333gV6xCnm2w9Quza91Datzhk3tj7H0xHd2Rza2R4Vr0huwS1mvzCHxiVdvTKQyCIAqk+YyiCgyx1EuDq60RUqs0LT1g7b2AshsUQJrEzu01uEAAAAFnRSTlMAfsnbWnukECQ9VJa32Yc4wtBinojg8k4vYQAABBlJREFUaN6tmml/ojAQxiNHAggeta0HFaX2sNfudrf39/9gi4jAhJkkKLyV379PZiZh5kkZwx7XY10+wWLhdsnrLxZXVncSxeUiAy6crni9jHd19fjARTe8qOAtLb8LnJcvd8dbPnSx5v7lQd8ySTpY87TO25yc5+AS8OLEOXm5gBfHJ9W2mAJekvHm1glBdNYNffO5fXzhhOumvuw5NohijelL09Q9drmovnQ2O64SQ0rfbGaLY7KL6st5s7h9VpwprW+WblpnJVTpS+O2QDFV6os3X/12e3eq0ff12QrY1+r73IYt0nyr17fdRsZAf2qgb/tuDOxNTfS935kCIzN9d3f/jA5t71bmPVC8vybAXoNnLSneHwNg2OS5JO+XHnjb4CViSfKudUAf47k0byx04WvyfI/m6YARwnMYp3nPSqBYITyXeQrey1h1tmA8zjKBNO/3uSJ8GM/K0qTi3dDAEOMl2Q+WindzRvEyHMLLisxX8t6GRPWtUH27T5qt5F0IInwob9cWOEre/YgIH8rLPxe2kneP5iTCeVY+Lql5H0gIvRXOs/NSj9U8JITBCs/vhhUCVbzvAZYOVN8m71k8De9piKYD4+07DK7hjZB04Pr2faSv4T3J22RF8fj+d0vD+xHy7sB5sVU0cjreQE4vEb+4eMPW8F6FnF5UX3JoSh0dD0RwQusrWkiRangjqVwofYdBgWt4r8NGuaD6+GFH6ngDqVwIffbhHa7hjerfNrL+4risKQ3vVSo/Ql81ddga3lAuZ1xfOSM4Gt6Zmb6yoxcbNW8AtgcZP6tq/015PZW+pDrE1byRAW/XT1aNHj+Zl+mbV2Ol04m+2mhudxC/Oa8ZjR3kt9pxWcmY8ZT1F8/rE6P5/iD11RLCfCOep9ZX90oik/OAqfXx+pfB5LzKz1NSXz0hTBidp6HifMn6ZzDDm5z3PWX86glhAcVrtmukPmBeRQbfN7J/yfWlHHyrDb6/+/aZ1GeDtRj0B2R/WvgHYDYN9f0L2Y8X8zTwmRyMNzSbPwp9wBUSTd7PheG8VeizwKsTfT+5i6BCXwq7uyZPmM3npV8CrcQGb2Dmb1T6oB07kXjfAp/QaX0wgIF2/igNIsLfncM3Ie9DkPYp6V/BAE4A78zcjz3EbwYDGNR5Fy387FIfDCCr84ZqC5Xwx+FrE908XSVljft/0CoOSt6bzlwUEepPcvhXS9652f1gQ58N3xkXvLGRMWsh/in05yYFb2hmlPeb/qlUMTp/qCFR1gcXLHLemJk/7qPk73pSADNeq8s5wSFPOmMyXtD2KpPXebYUwOdz1vrx+H5/5P4BWJ13PWbHPF4/PvhX8K5ifOzNpnDtPU/K8Am3kD7P/YhO7pcLNY6dxrzTfyPwXIs7gnXL1PD+A+5YMssNyq+nAAAAAElFTkSuQmCC","e":1},{"id":"image_5","w":58,"h":106,"u":"","p":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADoAAABqCAMAAADa67LtAAAAJHpUWHRDcmVhdG9yAAAImXNMyU9KVXBMK0ktUnBNS0tNLikGAEF6Bs5qehXFAAAACXBIWXMAAAABAAAAAQBPJcTWAAAA81BMVEVHcEwm87VL9FQz94Mi7cQh+bDW6jrI6zs4qf188EAq7a8Q/Nti71DW6jwa79eK8D8Z5uk69nEe+7czsfo193w2qv3S6zpS80InyfN08D7E6zsl+qQQ+9hy8D158D2S7jwu+I4e+rVa805A9WEkz/Eox/Or7DwN+uNb8j459nFO80FA9WFH9FAS8+UN/OIQ9uQU7ucb4esX6OkM/eIe2+0Q/NkS/NIW+8kj0fAZ+8Ed+7gg+rAox/Mk+ad/7z0n+Z957z1y8D4r+Jcu+I6G7j1r8T5l8T4tvfYx94aM7jw1936T7Twzsfqd7Tyr7Dy56zs5pf66o1QyAAAAJ3RSTlMAEJlHMH7+/kBeIGAQQ00porq3hWXCgudrQq6c0Nur3tjWe9javOC6cK4rAAADWElEQVRYw63YeXuaMBwH8AC6FkTR6ry1Hu3We7Nd7bRbd9/n+381A4EEyDcHeZY/i5+nIQnJ7xtCaPP6xLT1V01T2lxdm9qj1fVzQ/sipJdVE7m/o89qJqMU06cGtplQA3uc0qvS9iWlN41yspKh60op2sjQ27tStp2l600Ze5yjd3OrxDLM000J+6pAtwfaA8zR+5HuCubpa81PoQ3om47eCkb0vaNDH0P6QWd6v2A6s4zp15Z6gEX0u21OT1XD3BbTU8ecnnvyuZHRpT4dF+i5dKh+ZuhNs0gvXE16NW4WaS8Qz02O3lSK9GLp69JxkZ4JX7efp40aR89Er9vO0yrZcFT0ugU6JiOOnk0E05qna+Jx9ORkoUNvPXLA088DHdogDUAnaIb+Fmi4F855+snWoOH2XQX03UBNx2EBhuhETdfhbnYA6FtulPc5Gp7rDqIPgZJGZ8Yc0amSRsdcFdGPAxUNx4lYkB6q6F20648Q/eaqaFT/OJAequjubJ0h+sNV0F050IH0UEG30d8tSH+5crrZbfkjSJ8o6K5OdCD9XZfTuAaZQTqU07hsqkL6x5fSbZx9MHXlNK5AWpAO5TSuxTuQ7lFqIZrUeZjWxbtESOfxIxvSrpTeJ3UnpEPhPryjSfmxRHRPTpPCtIPoI9FxFdOklvYhDaT0dfLQRrQuONUTmtRLjpR6kKYl3lJGCaRp8W8D2pVXa7O0T1Lah4WeRXssoRVI05p0IXtXcoRomlZ8KYVhhQaslox6sKhNn7ri1cQnyXzO8XvCNRyNMaI0mtkySo4AbbEei77X7L1EroCnT3uiXYLewQiDmS2l+4DSkDMQbWtJmuRpK9NjuJmme+qKDyuE9Rhu4fRugqc0WgVZ2gUBmKMsdPcytM5TaywOoXaGwouCy2Iwo+ehC8/X7OsWKcuRjArSR00YfW1KRXmplqcsRropHQpTWi0fQml69VNaF2fDmiBwT0HJxdksZdOziGlXmoRzmY4OShDTQHERtGWU9XgS0anqtsFjOYf1eBrRgfKSw2KJg00PX4XjRmt/9vWE1NW7+UpKabagJg9a/zRaBElRS8d4+uAS3ebMIkp/705KXC9ai5DSMQ5cUqZ5rfML3/R+3VkujK/1SccekP/Z/gEsXGwqa2751AAAAABJRU5ErkJggg==","e":1},{"id":"image_6","w":81,"h":159,"u":"","p":"data:image/png;base64,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","e":1},{"id":"image_7","w":91,"h":124,"u":"","p":"data:image/png;base64,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","e":1},{"id":"image_8","w":73,"h":156,"u":"","p":"data:image/png;base64,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","e":1},{"id":"image_9","w":77,"h":122,"u":"","p":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAE0AAAB6CAMAAADNqC6XAAAAJHpUWHRDcmVhdG9yAAAImXNMyU9KVXBMK0ktUnBNS0tNLikGAEF6Bs5qehXFAAAACXBIWXMAAAABAAAAAQBPJcTWAAAAqFBMVEVHcEw3fKORd2VieoUxfqnZczOKeGp9eXWpdlV+eHLnciorfqztcSUXf7mCeG4Rf72hdlpBfJtren/Mczzlciubd13ocinTczc1faO1dUzDdELKcz1AfJyEeG2ndlYjfq96eHSddlxZeopveXzccjCNd2cZfrfvcSQMf8BJe5ZQe5G6dUhieoWvdU+sdVK+dEahdlmYd2CTd2Mvfajici0rfavociloeoFd5XxuAAAAF3RSTlMAXZ+sMlR6EzFagYLAq8zWvMzgzqDZ3a2z8rEAAAReSURBVGjevZrpduIwDIUdstghUCBAC4WWlraULjMDtGXe/80mIQteJMUJOeO/4Xzn6spxJBnG9CV81uLyF2PeHm28uB2LtmB8sbj98yFaC3Rxm+BaMm+S0eZBK7RFTpt3WoCJm4I271ye2m5J+/V7ejFuLNHuL8bdyLT73WU7xddoj+Iy2zTaXlxkm07bh81fqxuTdggvsM2kvYTNbQNoTXFjmNYMx59h2t1dkzPAR2kPDc6ALkprgusRtPq4Z4r25Na1jaI9PIU1baNp9XCTKtrfOrjrKlodnPiu1Pb30xrnf1dr+/z0bJNgRbPF9axo67Ud7tuSFnOrJOi0IUx7H3GbJBi0IUhbr0c2STAiFTDt/d21SIJB41OE9uZUvgmAthCjVeH40aQFDKW90fvEB2gdFmC0JZ3YLkAbMo7SlqO6tB1jHZS2jKiUArR7zgTm23I5IzJxPB5NWlIgDVFtsxmeiVdIW/JRDgkamgkfpKVfvSlO++rXog2TJwHqW4JDrOuCtF26rXFts68vONYJTEt/3CFoP3CsPZgWnsQRtBW4644w7VRpXeG+/ax+oG3yCtPSNLCQ0rYCYhUIbcpycTht4wAbBKY9np4GFG214ta0U/PBCd8ScZG53V6h93T3mBW8HUrbZuMBNFBbVlEKmtY3Ni9CG2bPr0iaLq6H0fbZ85DybbMZmDTQt32eMFrb1rPUlhdsAU3ra68CRsu7GE7TVHE4LU8DcynfNttIp8G+HYp3j9Y2sNNWpIHFJE0JlaAVjX1A0/p2tKKZ5KRvSqi4b/uyW3NJbXKohLZpeQbitBTnWNEO5fEVk9oiO1o5XwlQ31LawMq3Q9nTc1LbwE7buWl2Kd+2drQyDcyjtEm0a4L2cv6KxIRvW5vzTZn8BJQ2bnG+JbTzaIVTvnnVX5mUdnWOwSW0WdJezrTQyjef8k1KA4ttcipIbVIzH9jsN6ziymjS0IfbvAtYNZjRpDQwFznfFFqXot1JP3QwbX2lgKNoQgm18kRCepmCFiihwjSlKJxQ2uTZm4f45lX1pyVNTgOLYW2VnXg5U72Ti9EI3G/9qgmGNF2Rh1Ec1KaVq5yiKRPaEeSbV2MqpRjnQNpqTfO4EqrpW7/WFFSZ4rmmNqfWvFcxzjHOtw14/YTSFON4RaVK3i+cOkquhqrR4B56gtJCNdQlVeGXMYwxWqCGqtKwQYtYIDTFOOaSvYyCg+eWinEO2WcpF6gwTTGOkz2ggrO5Xxide+fVgB7FgbSYaaGW2irmhP4HNO8VWqgFbVA5+50DtEALtaBVTkSZGJs0dSwbFbRKaWkkQ3Oyre2RnOYwm9Wh72V4Thswu+XrNDXUUUazvW9IotUm26pxxIwLXgFxZ+SdaF4NWiJPpqmhprSo5v2bPAWNDeNqX8FzaTYYasY5rP4S5cTMVY0bsUYrLCY/ymd11vivBmFs3rV5rPkK44TmstZWmHSULf6Bh3mu67SIY9yJ2P9a/wDoln8oZFJ6SgAAAABJRU5ErkJggg==","e":1},{"id":"image_10","w":77,"h":122,"u":"","p":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAE0AAAB6CAMAAADNqC6XAAAAJHpUWHRDcmVhdG9yAAAImXNMyU9KVXBMK0ktUnBNS0tNLikGAEF6Bs5qehXFAAAACXBIWXMAAAABAAAAAQBPJcTWAAAAxlBMVEVHcEwOo1KJS30JpVG4NYCuSHC5L4YJpVGXVG/GSWLlVz8MpFIpkFvrYDKHTHy/M38xil7INnfqXjPWRVu4LIvpXTWITH1vXXQNo1LKNnZXbmzTQWQEqE/bS1IBq04sjF3QPmkflVm/KIzLOHLELoITnlToWzihOoXXRlrtYi3UQ2DgUkatMYrjVkBHeWdOdGk1hmBUcGtBfWQ7gWLHM3pabG1gZ29mY3FtX3NzWnV5Vnh/UnqFTXyLSX6SRYCYQILeT0y3Ko2R6U16AAAAHXRSTlMAxL6BXUOf/hEnWUkokXtzZLq6zdzi6aCg5M6H3YTrOv0AAARGSURBVGjevdlpe5swDABghxpqSAIhZ9M2vZs2PbZsbbeea/f//9RykdhGkgXhGV/J80aWDNiyEPbVaIkKr9Z0X1Wn1aYP+42qMHU8ffj5rSqutdC+V5S8vaX2oxrueLrUngYVYI3jVWxPvwbbl3Zno/3efqbUNO15a+5U115ew62wuqX1t+J2LO1tK65ma+8fsvxjdZrX/sjyaQO0T1k6bZB2JcumDdTKceoS0e6SMmlDtbugRNpw7W9xrkZoZ4W5S0orytVp7UwWTButXRTi9lxaIe7QqRXgGrtu7ZHN1Tnao88tAksbMl+fNZZ2fs7jdpnakPMlaxzZ2gDWzjsMrp7TvAGsnaSMIuS0Zx/RTtyPbC2vKQ/Rbpxvz0MgNolpN5HjLX6U12LRh7UZFzqKkNcGIkY1urA7gOYJhWrXnaLaixADVLs+IEsKaEqEuDYiKrELabMXhodr13gljiAtFsLHtS+0EnVQm6/L+6g2+mpimw5Q82Z3YlwbfUVoSQHtdT6tKe0LTt0erM0TExDabRubIJA2/wooSruNkAkCafH8nkdptyE8QSBtXgYhKe2+Db3GYa2/uNultPsInG6Q9ra4m5BaT7G1xcdYkdp9E5puoLYogwgobTwOi2khGdu4DUxeUPOW91MytrGfn7yg9r68L8nYJm2utsoJHdskZGqrQSS01sw9CrC2LIMIac0MDtdWZRABpVnB4Vp/9Qufjq3H0z6y56ZLxmYMldCyuZSQsRlDJbQ4W6jQWo+nZWUQAanpQyW0rAzCJ/M2iVjan/Xrq0vG1uRp60c6IWNr87R4vWAkY2Nq6zLM6kDENuFp6zLM6kDENtGX0Lj2ufmKDOH3m63VKG3zZk14sZFavFm4U3nz3V+ZubYpgwiI2BRP+9xokjXSOqVdaY/gkKM1SE3bzCec+YatuJaatuUL0dh67tXgUtPKIFLGk4Wte1d7QO2HkqM1SE1bGaghkremcy+TafrmNkBii6x1Da7pO28fiS107U/XWlf/5RCOzbkT33Qw9D8+AGNr23sjQtObUSEYm7WWVlSvxmhZdFzfeldXykhcAsVWqJunzKHmtGahLqjRxUvzWlSo32skTua0MdTmOkU1I3HKsVLNZgnefzO2P6mtwXvoPVQzEhdZWg/pJtVYXXdlfU+b7pM7UzMSNxuqERvaGGlMkb6lModK7bP0WYdo0hyqHhvVA25xzhc6Wmw9+ngX1MzEJVpsjj5hC+z3GqkOtZ24s/cLaYk1VLxLkOP285rZ5T3I+iHO0OZF2893ts05ksUWCc41oM9l1ErrCd7l25o1R5Ya97xBKM/qbJuJI3pc8BX38TMjf6EVOvoNPV0zhzoiWo3YJfUuaC5xhY/gVYCcZ80SF4niV+hlWmAmrlPuzNfvrror+sjUqPTpu+zmz9p8Uf6Ss83uRSoqu2R68RhWxwk/TWWFnAjlgfhf1z+KRQLZH3C47QAAAABJRU5ErkJggg==","e":1},{"id":"image_11","w":75,"h":124,"u":"","p":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEsAAAB8CAMAAAAW773NAAAAJHpUWHRDcmVhdG9yAAAImXNMyU9KVXBMK0ktUnBNS0tNLikGAEF6Bs5qehXFAAAACXBIWXMAAAABAAAAAQBPJcTWAAAAn1BMVEVHcEypZGiMJZGKIJSnYWvLszfTxSuraWaoaGukWW/XzyWLIpSudmO+lkm5iVGsbGSEEp27jU+tbWOACaLAmUiDDp/OujKdSnnGpz6hU3OKH5SYPYGTM4e3hVSRLovAmkeGFpqnYWqze1qOJ4+9k0ukWm+BC6G6jU+ra2TKsDnTxCzNtjWbRXyfT3aZQn7PvDGVOYTXziWvdF/CnkTDoUKCFThUAAAAF3RSTlMAn1uifEGzNw/FdDYgiLZVwmbYgNna3K2z7zEAAAQsSURBVGjerdrreqIwEAbg0AIJKiDWdq2t4qFSa4+6e//Xth6o5DAzJIB/d5/3mfkINsnImNtH+KyzT/Rx0xXl/fn46ow6Wv1OKP/zZN12QaWPZ+tp0J7ij6XVvjC++bW+REtKjK7WU8tlIaJNZf20XA1L2eq3o2SrVfrpUrF+npunz7e61Tj9wdawfhqvBtN6bpi+twWsZun7h+0ZU615k/TTwwGqa94gfX6Aredhg9xXSF1z528eb3WxzLzmrulnqxVa171jWDPCcktfzChr7dRkUllAXuu1wxKLZyfrgNa1tm8yeJ3RPdo3Kca11tS2yeRVtsC8ppZNZi+lReQ1tWuSv7y81vdo1+QYsiLDsmkyzjXrnJdnWBZNBnllVXlF0adu1TcpxnkO9OjrPa6ni9om47+gxc28apsM/sEWA6yFqOlQsa55eaBFNxlPgLq2y00KWmSTwWQC9yhAa0F1+IBYEWOPkNWnOtSs37x8xLrDtyFvkwmcF0cs/E9IiFjLEUMsdFUEb5jlodYNFrxplXmlx3+FLWRV9N7RuuSzgmIVcGCDd9TyUGtRgBuLELd8woICC74h65IXJywosPAbrWvECOseKgu3vPNmE7EKAZSFWyluFUBgwe4bz0uQdRmvZLhD6zpEjLAWxdAoC7e2PiPrKoyyCItfriawvLTVOtjtiLwYYZ3qUsPv7Yi6PNIywt/tjxRmpXV1KeH39lRdgzpLWfnhnshrVP6nDWrJD1LsibpWHm2dsL7WIlpXSlqnsmQrpOo6lK+uIOqSHuSeskbGHYyZV2UFpOVZWEOtRczirhZR1/WwvEHfR8naU1ZyPcMTdV0X2IC0fCcrIC3uZPVIq7pZtbXQ55i4W2hdWWeWFFcHdVUv2qatlXRo+bVWoXyxUs+RS1e1Nu8jtVaZnXVrYSWW1p3Fux1L13NUXtV39AOaF6+3znUJi+8vZmEtlL+PPcxK5KEJUZf0pwP9vo8tLfmshuWV2lnaFgCuS95XLfG87tStCWiNmYVVaFs5eD+hRM/RuvSzArzPyews7ZQM778CZRqAWcYFMPgclRtu1Oqbu3vTSqws82QF7aNjZVaIWcBxG9jfZxbWFLpSAM4dXFlesAXfvJvnIWZhwRfvxjlNiZ7DFnbLpJ8f41oLv/jVz7WZuiQAixhTqOdtZdVD1nxI3MkN1LrUUTRgkeOmnmwpXzhsZFr05Eo8SFaiLQndqhuCBZKlP0bNqr89DisrIy2Li2hRWfpjdJ+xBldLaCNW2bIcGIa/lv4YKyuynT2K0lKXhDwLiOxHOeVdbYLMaz88lwnT5Q5ZWRJpZTmOHceG5V0t7jpzHB8tZUlEpRW5D1a5vrzKeUejn61kec6V6E91RZw1+sS5surLu+2GnyRTB9xNi7pUJmGjbcsf+FR7QuG1/U1Ig89/KERy//wbfUMAAAAASUVORK5CYII=","e":1},{"id":"image_12","w":77,"h":120,"u":"","p":"data:image/png;base64,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","e":1},{"id":"image_13","w":107,"h":194,"u":"","p":"data:image/png;base64,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","e":1},{"id":"image_14","w":104,"h":194,"u":"","p":"data:image/png;base64,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","e":1},{"id":"image_15","w":110,"h":192,"u":"","p":"data:image/png;base64,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","e":1},{"id":"image_16","w":89,"h":189,"u":"","p":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFkAAAC9CAMAAADvACEJAAAAJHpUWHRDcmVhdG9yAAAImXNMyU9KVXBMK0ktUnBNS0tNLikGAEF6Bs5qehXFAAAACXBIWXMAAAABAAAAAQBPJcTWAAAAaVBMVEVHcEyyAFq1AFSyAFyrAGiVAJaaAJWoAGumAHqpAG2jAIeqAG2wAHCvAHe2AE+0AF+XAH+4AEm2AFezAGStAIOeAHSkAGmrAImyAGqQAIquAH2IAJiwAFSqAI+NAJCCAKGpAGGtAFqrAF63ZdLYAAAADHRSTlMAd9a8m4W+GDpY39R2ZPg1AAAHX0lEQVR42rWcW2OiMBCFAQNJRBFptVrtevn/P3LDRcllZpIA8rIv27On35yEJBM2SaxH8ORDT/H9Kenyt/yQ8vfvL/uIsFDKv/ITymmr/Cs+oJy3ytst/0Q0lPDvtuSfKGDrebt4Ffl3T2O7lYtHY1DebMTSBXx53myWRc1Gz5vVwtEYPW8WreK35nlzkotGQ1c+8SWj8VLebk6b03IDJrU8f7EFQzcqn1ppuVw0tGyclPJpoQHzY9P4+loIta2spHeLoBY/ejZ65a/dEqgl4Plrt1sAdQ4rZ/NRFz9ONlrlhi2prHveNbNR/2DKu5k8+A+YjV3TNNnc0CGelTabGTpYWXluKjEvdHA2Wu2qmYOaEZ6bajUrdDjnpqrSOaFDs6FoVDNQ/9Ce99mM0FGclXS6hLKbDaW8n8gj9XreT5z1copz73nPpoYOz0bveb+X85XdbHTK1RQeaz9n9UwZin++bPTS8Tz4X5Dn/TWah/gL4axIR/OQfyHZ2FfxU1P+58vzIH2N5ZEHclbKkTwKIBuly1n9eb3KGcq95xXs+fqM4rEGlNnJ4dx5fkTx+AOysWKw58dDTlfuKyhgzo/HnUcMFEiZZ4jnJ5uqPMwbIoU5P553Mc9zyjHP9zJ8oIDKSQlzft7v6UTlft5YJeluWHDYnu9HPsdzlvDBssP5flyFDkFw3kiSFer5KKcov94pIpGmsub5GMYD8SxbHJjnIwsbgqBy2uJAOKtHTFAe3imqShL3fFhN9qzCoXDAnDtpGfDmhml8tXsB3PPhwAMGN7zeUCQlzvl4yGOV357bX3dHePaaRpXTAQfMWWkX/sENrzfa6kvK80FEKr/OkbrNSYZzPtTlNOUuHC0O3HMto5Tf2ehOZMRrzQ9wPtRrPs2zHHBgnutDnXsmJFi5P/tirTLMWWnXPEb5vRbtpgZBeFZPPsVzX8IkQ/PcafMpyv1JT0pxrmsWrqztU7qRwHHOSvh85lM8py8cWJ5r0jSh3P9QinGuPab/0Gzs+vcGpz2fWbCytptIBhx7NM/KM2qaUuYDDjzPrTQLVN46Z7mcyPNZKf/j8Z6HpWFGez7n8crD75ninFvPmGkiG7vhZIoTY7BTzqM9D+HocQDz88vzv3hlYeCA89wqy9hsvA62OfwefNO4FNGeUwMH5Pnceb6ISOXmtS5M0Xmjp3HJI7PxCseAg/B84ZE03mfPGckZNr0mlYWOA/Rc956LqHeKUk5HHBRnJS1iZn69VZPtK5IzgINWfm8aUg/nyyVivdFtrd5bA5/nm4zyPJawW3dQnG95+Lqu8/x2kno8X27Ba9GO8zsc3bqD5HwTEZ6V8rjvyzycHRwkjZ3WEUtpzpdb4dsBbQ3PY2+J+zzfeODealAWBg6Ks507n3Kq4aCz4YCm3im7phrXKdzD2QHt8ax1ljIP55s7jVLKjdbR8XC2E13Q2dBKyD2cbdCMzHNTSR0HydlWzj00tKUm83Au7AYCNW8YzVLh4XwLORcdlSvt72Y051vI+fObs9GWZjRnOxxrOht6CQXN2Y0dWUGjP/OkOcvgTo3T72Y055zqLm0dzo3+d2nOeWivbTj60ufdJ8k5D+08DsfxOj1Gcs7dawV4NqwWmyQ5F+4lCyrPRgk5yblwr5xQ2TBGYbKiOBdAixefNxqzhJLiXFA3QyDPegl5DGf8NkvfAzIvLJQxnhNy3jBLmKQxnFXu8JGiPBslFFGeBc15b6x+yuB5o0sHMW8oZWMOY+Fj0L7H6Ho2SijC543EvS9q5tm6rRDlebw9C2XDLGGLA+EMbb8FMW9Ue7OEMvQ9+KohwdksIUc5g8c+kpg37Gsyq/5IxrfeeJumPJslTMfDL3K98TKNzhvteZ2ZpOMB5Ix1WNZ4NuyrTuUR5IydoEuUhpJOrdwdAtaiummUs1VCCXNGm0ICmjcGztZ1NZgzfn6+wj2bYyVhIfsUvebYvOHcRpKHgL2VMeWh2bBKyEP2g8YPrMF5o3IvZ5X+PaxVRJTzPnFAe/bddhExztZYkY7ny83TSt5inq27Fa5nX/dUwHl2bgUyh7O3mcwQz9ZYSR3P3gsBvITmDWesCMdzwMUZ2HNlhWpt5bnwKycS5AyAps/rQNSQZxu0NM5ywRNo4CkBzpV1+5Ibnm/rwKuNkGcHtOcsF6miy7lyQNf0+TNWRcCzDdrwHHEN2OVsg67pc348IO4dKgt0qXmOuobIzLlO/QsW6FzzHHf3NbM9A6DPeKeGlLY4XwHQeHfJK6336N1E957XSRIvPc4b1bUCEt0p58kkaa2T7oLuPU+5dc2zSruXa4EWg+ciSSZKj31Y62U4eJ745Q3Xe/TWqqPoPScTH8603rE9Vs54vztoNI59WGmDPlNXOAKmp7dnZo8V/F5B4KT68pzZY+V8nvklmaiGnqa1jq7PRTLzadPXKqd2Ced/V8dXnfLKKuE6WeBpexNP6z74eZmvLuXj8XiaUkt9Kcoz+2b1ch/Osuc9+dAj7uxT0pyV4nPaH/uPHujnP7ohcmmDfvayAAAAAElFTkSuQmCC","e":1},{"id":"image_17","w":80,"h":162,"u":"","p":"data:image/png;base64,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","e":1},{"id":"image_18","w":82,"h":193,"u":"","p":"data:image/png;base64,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","e":1},{"id":"image_19","w":104,"h":183,"u":"","p":"data:image/png;base64,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","e":1},{"id":"image_20","w":89,"h":188,"u":"","p":"data:image/png;base64,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","e":1},{"id":"image_21","w":176,"h":97,"u":"","p":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAALAAAABhCAMAAABBC9vmAAAAJHpUWHRDcmVhdG9yAAAImXNMyU9KVXBMK0ktUnBNS0tNLikGAEF6Bs5qehXFAAAACXBIWXMAAAABAAAAAQBPJcTWAAAAxlBMVEVHcEzOTFoilVndTVDlWD6+NH5KeWe0U2LIQ2e2Smq1MIbkVz/mWjrBNHx7VXlGembOO2y/LoVnY3KHTH0FqE/qXzMcmVeLSX7TQmDELoHYR1jBK4e/KIzKNXXMOXDbS1OlN4fPPGubPoORRYDVRF7iVUJoYXJGembHMXy7J460LIytMYnRPmdQcmnTQWPfUUmETnt9U3nmWDxxXHXdTk5gZ287gWLpXTZZbW3IM3kxiF8UnFWKSn0EqE93V3btYi0glFkoj1yZ1GRIAAAAGXRSTlMAMF6jgGY5EEIgsl/Ch3ecw9zOp5Pbx9bdsUe3xgAABz1JREFUeNrtm2tb2koUhROSkBsJhEhRVAxU2ioiFqmotFL//586k8mFTDK3ZMbUD2ef53zr43nPcmXN3numSuckq05WXVjKJy3zD1r7uP6Cen9/f339Aqrf7/Q7n+j/oFvB3f/dQ14A/PT0/Qn8G9cK1HA47Pv+v4bvFHhRhQExQIbEKwi8en7+9evXN1CPgN33ra79L4hPKgonwK+vKXCm8PNzBvz4+Pj29vbz58+Hh4cwBOTtgn8pKRxbIvMEdEVmiQQ4J4bAv3///gpqPnY8oy1u80i8/4M6Ilc49sRzwRI58AMEnsOKoshxvBawbbyHX48eLiqMA4bIgDdaLpe3t7ex2q1ERTklCgqXPYxROOaNIPDNzc3FReB8IHWn8Nn9TRV+TT2MfHUVD+cmTokzYEB8cXF+PvAs8+OI9/tyqhEUxno4iorAEDeuszMgtflh4ZYjlz38ne3heQn4IuE9+wEq8KwPCLf9H6rCq3oezoFj5JeXl0vHsGWHG0KMenjF9HCEU/jsPAEGxJegBjKZIfGelsN0D5MV/pEBTy+nU4nM3UzgfbMcJnm4oPB0Op3NZgNDLjFb4UdSSiyXFIVfIHBMfDfz5Mjc2WN6CV4PZ8g0D08z4vu7gSWNuHAyo7wrtoeXVA9nCs/u7u7u76Ugn6AK18lhnpRAgK+vZSCf5OdGk14iont4elkEhsiqDOKjJ479cN1egpgSiYcz4Mm1JnxsfyErPOwzegmOHC4ofB3X5NoVPkBIJx1A9rv9Wjlc8PBL2cMJ8WQyGdgSiAk5/Dzsmv6w6UmHAZ4A4quJoMj2Oy2Hh7Zi+8P6vcQLzsNQYYAs6OTue3lqRk46H/z0biicw6mHY15QliAx/aTz4z/kh1UPR008nBDrwsRlhYsHxxBuf2x/XKOXuMTkMPQw5N0K2qKT5zChlwiTH6+GdVNiSlB4u+3ZYsSsbs1P/6QRIhI39PA2LqFzr0NQOM/ht3G2FQTWoAHTPDwpEq+3rjAxvpf4lu7WwvyXqIbNchhReL1eixI/sfYSYf6lmIYj6GFx4j7P1OwXThwvqNtLFFIC8q7XQvHWf6puLyu9xLiY+ZbTNIdThdcLTZiYOTWHxTwyjUDAw+vFYqFLISb3w3EK+0joqx63h9GUWCfIYj6GnmBMHF/n49Lobjh1czj28BYqLEr8nTHTpWezU2pfwBfIkcOIhxOB1zKIV/RFyhwedWH5aLWcGr3ENlE4kXijyiAmezg7mr1y/wK+wBo5nAu82CwEiRmbn7yXCKpbKMs5q+PhVOFNzxQkpnp4nk0c0TKoduJAZs5eIpN4ExML9cd9+gYe6dYcTJeoOlw5nKUE4N1sNBnENA8f+2EP8+s0jQGXh3NPbHauKDFl8/MVveO4xS5UVY2jl0hxocaqIDE9h9GJI8AOlUBmvhyGvLuR2CzdZ3v42A/fXDj4gUfVplQP57gAeCf24Skdci+B3Us4eIFM45SSEovcwzGx2Cit+KwcRiYOcDB7hN+ppVUVLuUwwAXAI8HtZofbw2m3FpCuM4DMNA8nH90OEAtuCn1yL0HYSwTElY6l4XqJYw7vYAmaQrHJHx1u8xM3P+TlpO3RcjhhPoiuvLtUD1fumgHw+ZlDjif3lJjDicLCplC6Q0ovgZ2aQe/jkZFVDQJjcjhBPoiaQjGH9F4COzUHlMtEUz8l5HDMK24KQMzoJapTM+jVAtpC1e3hczgmPvQU4QpJKUHd/FBvuVQNl8NQYXFTgHjjzOHiPR34R6PtJ019W81hSLyT8EDEZ590mKn5cupR/9vuCKvwQRMHVrrsXgJ/T0dHtnqLsodjYlUCsTpm5DD+JnQ2m9JfH9gactJBXhnfHfDcuOrhiOuu+ZSObOqph4+eOLhSiEOOXgK/l2Bci5v6CHHE4TBSpJTPm8PHqXmWznSsm3x3hHhCRrTBb4T3pMPsJTTGHYzbK/AeDpLevtljakrQ98MsZKtXIJYkMTByjRyu7CU4kA8HuRIDI9fycGkvca+bLOQMWJbE4GceX+NGdW9CQXEjy3vBaY7nIu8leJHlSQxs0fQ2f5LM+XzIMh/JWvmuinUTWt2tXYGRg4nsjuRKrJhO7btmZC9xdcVGHilSy2ji4eLmh4msS/77ALYTLeu/qire5m/1j3mAThFZ6K4ZzBttI9tOg1dV6G5trbf7926MZfO75myk01pFNp3GL1Lgbg3OoJraJrLl3AjcNWfXXm6rvgi4e4lJebeWT/mjdpFvGr+XKMz5bX5/JrwbZ/cS1yXe5GIxH0E1q1Vj8PUSGA/ngz4YP91WPz/+Fyn5e4lM4V22DNzFzlD1drS24+t87ndrqIezsTlepBxazGbVCzh7iWR7uUY28HApobst9xiqMcC/gSd6OFe41zps7mdvwNNLLJIgjqun6Zbyb0s1vAGPwgDVVZVPU5bleYPBoDRxbLe9Xk/XXctSPnGZVlKm8n/9X43qPxAp8u6KzK1KAAAAAElFTkSuQmCC","e":1},{"id":"image_22","w":144,"h":143,"u":"","p":"data:image/png;base64,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","e":1},{"id":"image_23","w":142,"h":88,"u":"","p":"data:image/png;base64,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","e":1},{"id":"image_24","w":112,"h":83,"u":"","p":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAHAAAABTCAMAAABwBnPhAAAAJHpUWHRDcmVhdG9yAAAImXNMyU9KVXBMK0ktUnBNS0tNLikGAEF6Bs5qehXFAAAACXBIWXMAAAABAAAAAQBPJcTWAAAA3lBMVEVHcEwkZIIwqlZavVDX3Fstj2l/xlIpSIs1l2XT2lrk311CuU3r4l2/1Ffr4l4khG8jaoG+01YprlHm3lsijWxdv08oVYYhgHcimmEpSYuJyFJkwU8jpFfN1ligzVXi21nl3VtkwE9Xvk4rtUsilWXv5V8ijWt8xlGFx1Jvw1CSylOoz1Xq4V1Hu00ihnE/uU02t0yx0FUksU3C1FckrFHP11cjpVcjoFvc2Vgjm2AhenwjbIEhgHb362N1xFGhzVSdzFROvE6LyFOYy1S30Va70lYjqFTI1VcmW4bV2FjNYsnuAAAAH3RSTlMAYWE9EiWD/hA9c5DCWJKE36HQ59W4O7DptODnp8rIiTVi+gAAA9ZJREFUaN7tmWl3mkAUQGVRGQQRFHcDmpDESPZFo6WtbdKS//+HChqpy5thZsBvma+e4+W+ZZh5FAr8S5HEUmWzRFGSCkdcsmj3hsPhaDRaXl/f3t7dnZ4+PDxYfeM4VN2++D4ej3eBEfHp6enlxRKLeeO6lxcxcAgA/7xMp9PXtiHmift1uQVc7gMj4uvr2VnbyMdTPrm62gGOMMCz8/OGoWTX+/mbHnh+c9PKpqmYPxiBN99aAj8PnUwOgcsU4Ld5jReJJpMJu2FE5ETq9/dcwPl8vlhwINW3LMBFWGuy8cx3DHBJB+wwKpqP7xkMawJrQ5qPGYCtJnP+zL/8QJ4drvpMAJJzyLW56c/PfIYNvo7XP/iADc43FJpxAdu8L0RUTwGCOeTGFeT6jN2wTSiVtBbRHGbglFSZzRRg1WEG9ol9J5C7UnXSgbs5tMh/WOwQf0eOw2ZokQ+lilEukwuGEZhSmkK5TAZqDhOwQt7FhFrM6xALxnXoc5iSPKEWhjGQsNnpjkNteG2JKbhFuAY2SQl0qYHkaEa4xQZISKBLabhc2qRoKkJtfaZZAfEpVCMenWGP1ApFY3VMTIAGtgNdl9KwRMA1W1unthWwiU1gDKQwJERTERs7x8QVENsRCY4IJESz2G/vnUtjIC6FuuvSGJYUvNzqSHMAFNIDigfaEl4OOnmH5bCs4DqCwrCEk7MwR/2I2KEIKAZow88q9vF3iwgo4ANKNuyC0ZQqxMtMiIuo5qcZQsVSFK14TkMEwhHVfZdseCIBibPv1pMhIlCAA+qTDQ+LRbSp7odhCLe87/sEwxP5IHG0F9IQ3EdRzPNxhhN9n9ajvwGHRUxA8Ybmjp5U6g3HDFdusGRUPwEeGL7pO7Qu45U7hF4UyPP+C+4ZbulFNPahQg1sQc9PFHcNByh5qFKXa4oB9YTq+RjDjZ5u8k4xIEHZ90DDjzr6pEVtyDfFmIOCVQ82rEZ6sm5mGZuAglHFAIazSE9Ws0wxVsAmWDH7hvGqInXAcQPeA7bAioEMtbozm33wTjESYBHaYzwPyuEsDyCmYsAcOjkAoYDKQQAbst6AISC0a2uBFxzLEAooWgtyGaYO2cFNNDiaIchTg09g/jlsgCe1ehBsYpqzIcyrBlkMSUCYJwfBkQxh3lrwGDlswbxPwfwNcddrdZuXXw4b2LsxqmpJSHMzbBspY1+1qiWK2XPYppvlyzHW9X3fzWZoiUyfDiKsqmku9dhkD8j9hVtGeuSrMQGtipj9m28BRcbqYDAgAW27UpHy/na/skZIQqWtJUVLKXytr/W1Mq5/KJN593N99JIAAAAASUVORK5CYII=","e":1},{"id":"image_25","w":214,"h":159,"u":"","p":"data:image/png;base64,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","e":1},{"id":"image_26","w":114,"h":259,"u":"","p":"data:image/png;base64,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","e":1},{"id":"image_27","w":134,"h":247,"u":"","p":"data:image/png;base64,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","e":1},{"id":"image_28","w":112,"h":259,"u":"","p":"data:image/png;base64,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","e":1},{"id":"image_29","w":56,"h":89,"u":"","p":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADgAAABZCAMAAABc8BfTAAAAJHpUWHRDcmVhdG9yAAAImXNMyU9KVXBMK0ktUnBNS0tNLikGAEF6Bs5qehXFAAAACXBIWXMAAAABAAAAAQBPJcTWAAAAolBMVEVHcEz+2CL7vSX/xRz6uir3oS/1mTT/7DL2nyjuazr5sCfqSjrvkTf4qSj6uRv+1h74qyb2njLsWzvvbTz9yRL/4yrtYzzyiDv/7zXrUTv9yA3yiz7wfD3vdT30lTrtaTz5siD9yxH2njL3oy3+0Rb7vRXxgT7xhT76uBrzkD/+3CLubz31mTfrTTvsXzz3pyn8xA/8wRH+1Rv4rCXrWDv+4yolL4/gAAAAGnRSTlMAeE0QILeG/jBCZYAP8NChmeC7aenomtLA41K69JoAAALoSURBVFjDtdh3d9sgEABwJIGCsLU8KsfxkjOatOlI637/r1ZN5oHGe/G/vJ+Bu5M4hNDY3zJEs360OEaz3Mvh+GWGW78cDsdihvtawyOe6rKHFq6nuxauJrEleXvoZvw+yRUnASckJChOElyMdrh4l+HoTdL4qsDzBKfCaKR7vGozLkY6A65GuV8mPI9xfyFIRzgQrgfdBYYDz2SQXCzwfsC92uDPpdvZoasEyLMDOkogfVbhmwLtJeB9aPBEZGiNDvutwzyU4c2WwL0BmTLjDY4O3miweqyWhQLXcCL2xowhUoJzA2sn3ZsQUxVCYfX2JiTIVyEQVloCMEKhCn+YG9wAMK8KSYNGWNMSgAyhfAD6TwCMq4GTBhf6QiFYHd+RDrV8pE8QrE5EpkM1H/4fCJJqJNShmo8tCOs4EB0q+fC+QTCvh04uGGxByOqyNyGVJ4RgXL+YfBNG8oQQzFATGwf0/oGw6U6ICcUTuQUhacZyE/LS8e9AyJre4d0BUxDGzVjkgncgbLvM0AEpDNvGjTigB8I2NCh3wBSEfttXXR1wB8GkHYsmw64BD11wC8GupyUQjHg2TNiFBuVTIet6zisEqR3GfTGCENmDk6E+NgC8d8B+MQSCK2QtgKQfiiHIa9wsOa+v4kcIUvE46jDo+wEIitcq1iHhFw4ISieADv1+IIeg9HLcqXDDrxxQZyWfHJ4CP1K++aFeDqszUrFFE6pH1U6CYqUoAaB6HCsv5Iy/34HuUTtUsQxFfg14LvQ2dyegWGlmzmjcAZiAmdiiAYE2TpyP/E8Do0OG2j9+IouV+joEuz/cw1RsUYOWG0faQV6nKFeh7YMF7qDoCNVbgP1q1DRIZSICrUDHbQM3LZknkiHBwvnJwashTwaWrg8D32OqdrUUyWAc5oP3YlaWIhlJD8Pl8CU1KT2x0hYSikb8cOmLlTa9HBt575diWsF4zCr1+8DrJQ6D6R+MgixhMxjyPR993u8/91BMybEguJYAAAAASUVORK5CYII=","e":1},{"id":"image_30","w":58,"h":106,"u":"","p":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADoAAABqCAMAAADa67LtAAAAJHpUWHRDcmVhdG9yAAAImXNMyU9KVXBMK0ktUnBNS0tNLikGAEF6Bs5qehXFAAAACXBIWXMAAAABAAAAAQBPJcTWAAABC1BMVEVHcExf5SasPki4JU2XZEOzMEt74yWTeD4p5imzLktmrjO/FFAi8ifDC1KoSkW3KE2iUkMp5yh8hDt56iS3JU2fVEMs5ClukDmqR0WExCx+eT6B0CmWkDa7Gk5muDBGvjCgdDxdzysg9SZ4dT2FYEFjkzh56iOLtC+OqzF64ybHBVOhcj2XjjeUlzWbhDmeezukaD6RoTOOqjGKtC+oX0CHvS2rVUKuTESxQkaExyu1OUi4L0paoDa7JkyB0Cm+HE5hljhUqjTCE1BojDpvgjxNtDJ2eD59bj/BClJGvTF92ie6FFCDZEGKWkOzHk6RUEWtKE2mMks/xy+fPEmYRkd55iQ40S0x2ysk7yfCQ+mDAAAAKnRSTlMAQTC2IEa9D/5mQJzF6MWFh6iUgOiiaXfjZzWA0NCjwNTMgODW2+bQ4OAWB1D2AAADZklEQVRYw53Ya1fbIBgHcJoQMalt05q00VnrmXY6d3Pq1GnddPf75tzt+3+S5QoBngAhbzm/EwIE/g8ItXnIGrJ8yO6Rrdw4PAos5dPDozU7+Silm1byYUZ3LeTW45w+s5BPStp6nOIHFW07TvEBpXdaymNGl1rJ8KRGn7eR/ilHcQt5xtOOsfTOBWo8Tk4kUtOliKMLkRoOMZleSvSFGZ2+BCgxkd1XEDUZ4sE+SA1Wsb8PU/3sOL0Gqh1i0vtgS6efm+gn3eB+s6X+DwVVzo5zY0vJrUi36nRVQfsijQ8MafhLpD5Hm2fH+y7SviHFXyXqhxy910R3JJqQPkd/N8jJF4mG6NiErr+XKcYCBSeWbMu0jzwTOnonUw+FBjR4I9MkXSICBdYEmQN0gFCip6PXAMWInGhpcAVQN11eIpWX0xyinnhIQnSyAGiU/Ug6iq8h6qctiY5ugjQ9JciphgZvIdrNfkKJCr/ODKRe9udLlF//wUeI9rImV0NnIM0GCZ2qaecnSLOjFJ+p6Qyk3XyrUtPOH5B6+SmrpkOQ9opNWUnJX5AO8sZISccwzSOdc66kKyB1izMPoGw14WWQekUWAShbw2OQFoOEXCUdgrRbNF4o6TJIi9zrKSn5B9FpmQwgSre1Dkj98lOVdAzRqAyREUTp7n8XouUg4UslHULUK094kLK1BNCkSl4Qva+kg7LNheiSklbFDBi5VlW0X4UZTVoDqF9FRZASFa0aXU0elmlcNfU0AV6eV68KqGAyrdWD8mqiqRii9bNKorGyzqnXKtKf49BPhShS0B26Z0EpnK9yBBqyGgmgfEW3wlPa3y5AhaqM39Zof1FPpntCiuA30xH7VJmKdyH8Fr6uKAdHUtaqHxzbrMCS6J5c9Q5rdFT7VJECVzf1k47215GqqwmUvRll/fVFOgITPzvVWbsr0A24vB9TSvuLhHJwq+FigFDKaiyBrjfVRcOSsv4OeOo01nFV5GKDGHHUUdSdJaUfhOs1XaKSaJzTOZuaGu2rr05wTll/3Vp1pbt02cwoXWmEq3M0T57C5RuCyDe4rJldL9gFllvSqWNyQxRcL+inkjIPd43ultLXLthfXiRTz/QGLliw24U8IxJk/ND+4jT8RB6yefzbm4EVRDiJsZ0MY7u+tnj+AweapLiIlbMnAAAAAElFTkSuQmCC","e":1},{"id":"image_31","w":109,"h":53,"u":"","p":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAG0AAAA1CAMAAAB8+5X+AAAAJHpUWHRDcmVhdG9yAAAImXNMyU9KVXBMK0ktUnBNS0tNLikGAEF6Bs5qehXFAAAACXBIWXMAAAABAAAAAQBPJcTWAAAA5FBMVEVHcEyZYE9oNXKjyDuDVl5sNnBiMnd1TGeVdVKdvkCHkVOSKFiVKFZzaWZoL3OhxD1zamW6JD1vY2hgNHeWr0fCIzeLmFGjxzyFjVaixT2JllG2JEDLIjGNnU+LKVxxZmaoJkhXMH1oVG9dP3hfLnlnLXOGjlVvLG5/KmSHKV+Qo0xhRnSPKVlvYmhsXGtkTHJ1bmOlJkp6d19+gVuUqkmBhlicJ1GKl1F1K2qWKFWuJUSYskVzamVaOXtZNnx4c2FxZ2Z8fF2buEOlzDp5K2eevkCNnU+1JUDCIzfNIjC7JD2hxD3ddNRjAAAAIXRSTlMAIH3HMFrAQBA8WaNwgN96kVe3l8KmoO7El3vQ6tjM4OABCBPCAAACjUlEQVRYw+3Y6XaaQBQHcFmHYdEgiOCSNGLcs5AYI6Rp2mjikvd/n46ctDkgBGYYzumH3hf4+Z8L945UKhnFA1k7+ShNAFKlrGJky767+3V7+3xz8/3n5eXVVa/Xu66zfAlUyx4M7u+PtevptGoINCmp1X14GKRqU9d1DVoJeWvy9vaV5rpPTz/OqyyFJkJzPsnUXKShKhoQWQjLq/X7olDEenycz+cYWv+iSugxzssLvnZB5tWar2TacIjtQe79lVhbLrE8yVm9F9CGy/UovwebCCukIW50CvJ1bIWquDYanzLZp8itaGnjsZI1XyQIYa3mcBwNbTxT8z+YiDXNItpsNsvZvs+xLLesLqnm+75CMK6RaZNk8/3FQiXcdECrn+FqiGuTb4eQxMq2WHhKoe0Xkrk1z/O+6QmDv4a1JgRWzKt5QeM4HgexHx/BELNP0gu8IDiKxzRXnFODEu65smJmtiDYNOKzDIaDa9U8mHg5edaopmcLtc1GTxvK4eQyTaslw9xZGcGoJmcLQm2/j3fPSZ7Ktm23NE0GIPPdQceacpJI23di8bjsHXBWR6WxGisAVAnJgaqkaNttNN5h6RBsHDEsgw1LRT8CgfG+hdq2A2IcyX47/3svWaf3DWG73a4RvSvQ2abJJ3nwIpMTceVpu7D0CPdHm1DQFkfZUEXScXSzRfu268TGisSV1rdO43ghSGY5WkNPnkwOLe2zb201fQjKdPvWVr++z8IutZPMosLmWVQ0RWfyLRG5qOYrOsZylqwC2qkCcK8e0CbTFIEhutDJXUxtrahFvmfIdv5/wqJa/NMQb+XQRIPw9JICWulaVWQF6t/zePmkHtUOtwMBVEosCXxU5X/9A/UbTDkfoLCHclMAAAAASUVORK5CYII=","e":1},{"id":"image_32","w":52,"h":110,"u":"","p":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADQAAABuCAMAAABfs8BIAAAAJHpUWHRDcmVhdG9yAAAImXNMyU9KVXBMK0ktUnBNS0tNLikGAEF6Bs5qehXFAAAACXBIWXMAAAABAAAAAQBPJcTWAAAAn1BMVEVHcExbNcI6YNcRpfQ+Zdc0ct01a9sMrPYUo/MOq/Yblu1HS80Vn/F5CK11DbA7X9YQp/UMrPcre+JDVNFzErJ6BawjiOcdkuwXm+8Ro/MkguUuduAMrfYGt/sxbN0Pp/UUn/FjKLwZlu0ejeorf+I3bdpvFrQBvv48ZNZCW9I5X9coe+NIUc57BKxOSMotct9UP8ZaNsI1Zdo9VtQKsPjF03fHAAAAFnRSTlMAP7lcECVZgj6/eX2kjF2b1ujc3LjcmWYWHgAAAtJJREFUWMO12Hl32jAMAHAncRLnKIRQoAeMdivrsbGr/f6fbaQmh2TJR9+Wf3m/JyNfkoWgviQVwZ9cFOGo3G/LYNTstwsZaJLr/fatDg3UoZ0KMuruHTVBqNZolweY9OqMdgG5yHv06J8LuRnQo3cuks8j8l4XzQTtEs98b6bIc13UAG290p5+gcgrVInQtU/aW4yu3NsxWxvInfbaRBvXDMsbArlClRRaO0K1JGrtk3RDoofMhuYMsoZ6YNCtJVT2jUOWUDGLDmwoecujJTs6gBqAnrhQMUAFREwo+ROgHKJnOlSGUAERHSqGKKkh+h5Ro/sEkcoRiqnRYaQQ+kFs4RijFKM5MTqMBEa/pDk6AzUYVcauMFGB0W+MnjA6XTkGmqE9+8dEiYEuIaoIpAx0D7MeE0iY6AKgFwo1BroHCSdRYaJpKuYkyk00TcXSQIvu9jXR18mqeDFQd4ArAo3ji2gkCDSOr2LQzkTj+GIGFQSajXmgUU2gfnzylUE5gY59HjikKHQeXxWEzutvziFBodU5eQTSNceCQEed9GcC6ZqooJA+NV9ZlFPo/U+lgehSZ5xFikKrj6CjniYWCRJFDrT4CCooNHOgnEIX/wMlHMosSHHIMk9CcIlIQ1FkX7CnnHNoGYhSbhPmQwdGrz3yjLAgfYilFqSYTXja70Fo1l9PLJLcGXG6CFkkuCOsGx+LGuawpK7PARXMsXzKH49q5gKg6ogBlQhNSonIG0WgRmSQgmg1LT8iTzSzlaNDT5eydUT3I4MEQBe4tvRBuIyVBxo1E1TZu5oRFRMk7U0XiSqqc2hhFdY32T1aki8GCtZ75yUxoMjZUZsodjf8JmLfGGQ7dgAIVZZOfI1RplFs7d8xUhqlPo9NJUSZ64lKN5IAzd0PWwZqPR6C8q45nqCD1wNrCZHn41uyfRtRJjy/ZEBlwDNp/1CchLxcDpMt/tX3F+UidwW7EEGyAAAAAElFTkSuQmCC","e":1},{"id":"image_33","w":58,"h":106,"u":"","p":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADoAAABqCAMAAADa67LtAAAAJHpUWHRDcmVhdG9yAAAImXNMyU9KVXBMK0ktUnBNS0tNLikGAEF6Bs5qehXFAAAACXBIWXMAAAABAAAAAQBPJcTWAAAA81BMVEVHcEzOwR7QxR88R8RGSKyJWBfTyh+Jaj+FViCDXS98SBqSYhE7SMh+RxVZR3h/UCVrRUVTRohmRVNVRoCriRZ9SBhBSLbIuR08SMVtRUCUZBGUZhG9pxqhehVpRUrEsxx1RSk4SM7UzR/Wzh9QR4/TyR/WzyB9RBN3RCNgRmJuRTtlRVKlgBW0mBmwkhisjBeohhahehS4nxm8pRrAqxudcxQ5SM7DsRyZbROVZxLHtx2RYRGNWxCKVQ9LR52GTw6CSA5NR5bLvR5IR6VQR45TR4ZFR61WRn5ZRnZCR7VzRS/PxB9cRm4/SL1qRUfUzB88SMU2grNyAAAAJ3RSTlMAQIDAP4jADzggqMCl3ItQbSmr3tJoYmd702Lm16PqnPDYq+ao4FBlIB95AAADZklEQVRYw53YZ3ubMBAAYCWGCAEGYyeOR+w6adoMd6UZTZrYaevu4fb//5oCBs1DEuijed7H0h1Id0KozsD7qOHAB1eN5eXVVjO5+/byar+ZfJnSJ43kq4weNJGvc/qugXxT0Npxmrwoad04TS4orRmnyTWjO7Wke8vR93Xk6E6gpIa8F6l9iL0HiW7bS5nahpgsFjK1DDFuPyrUMsTtDyr9hG2kfwJRmxC7JyC1eIv3TmFqzo43q6BHxuDOTiuoMTvDn1X0l0F2z5vSzrmGarOTrJtSPJXpLk912RnOJTq5sKTRXKYjgVZnJz6TaWBJyZlCR65An1ZmVKFjHAj0b9VCfyvURdc21PmuUkIkCiYW91QaoNiGRt9UGiNXos+h6X5R6RihQKLb0HQB6iI0NtPoM0AJwrdG6nwEqJ9uNTJVt5geRGP5kEyp8iYOlgBtpw8CE01WEN1Ln4xNNARpekrgOwN1vkLUzzZkhUqfTgukXnZ+KFR8/50fEM2ChAIDbYE0CxIa66nzB6TZUUru9bQFUj/fqvTU+QfSOD9l9TQEaR4kFGgpvgGpmz980NIBTAlYcon0EKT+ps4DKHubkhuQxptaBKDsHe6DdLZ5GGhpCNJNkNBCSw9Buql7PYgesdRAdFgUTxCl25oD0k5R62npQKXr9bQoItsQ3dLQ+bpbHLaPWhpC1CtOeJCyDw6g07I4hegzLS2ChAKI7mhp2cyAJde2jg7LwtZQrQG0nO8eSLGOlg99Qz2s0m75yFT7q3mNy6WClPDbi0xp4wBR/qxSaFfb5/C9ivLlJLS5gijS0B4tNKEqXCxBJBqxpQJU7OikDSZhS1Wp1JWJ21qP9ZEqle9RxM2UzpcAHYdcWIpbeKJpB9Vmmz842Hx9hR6oXW/I0YgtVaFAHdznKJ2vp3RXUH/Dna9svh2ZTsCKn53qEbdUke7C7X2fUof+JrWDFRJhSlm/LVGvqi8KC3rMrgdEWilRv6B9+stUoJ6m7ywoXRDhe7qxTqJBTrnUcDTQX50kOWWpGXLdlenSJcwoTQ0W+hzDyKtw9YZgMbK4rGmtlsfctcSGtj2bGyJntaSpwUU97FrdLaV/u6RL7eR0FtvewDlLNt+8RsTIetD5kqxu8lCT0ZlPO7iRJNMuaQRR1G021xrjP5N8dkjccU8kAAAAAElFTkSuQmCC","e":1},{"id":"image_34","w":81,"h":159,"u":"","p":"data:image/png;base64,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","e":1},{"id":"image_35","w":91,"h":124,"u":"","p":"data:image/png;base64,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","e":1},{"id":"image_36","w":77,"h":122,"u":"","p":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAE0AAAB6CAMAAADNqC6XAAAAJHpUWHRDcmVhdG9yAAAImXNMyU9KVXBMK0ktUnBNS0tNLikGAEF6Bs5qehXFAAAACXBIWXMAAAABAAAAAQBPJcTWAAAAxlBMVEVHcEyiUXPehjWfTnePSoSBOpOVRYGGW4ZxWpVoRZ8gcL3ojioQeMMYc7/pkCkrabiFOZGeTXjXfz05X7JjR6FLVarVfj4TdcHpkChuQZ3GcU7HcU3iiTC+aFbHcU20YGFCWq7Nd0anVG+BN5XiijCdTHjagjg5X7IxZLWQQIaKOo4paLgQd8JUUKchbbt3PJlgSaJvQJy4ZFzCbVJnRZ+uXGYZcb6rWWnSfEHVfj3rkieiUHSYSH6URYJKVapOU6kFfcZaTaTrzM/OAAAAHXRSTlMAwjqON5xbECBfOX+4Wr1/2OxZr6jQrt7g0HXX3Ak7JjIAAAQrSURBVGjerdp9X6owFAfwIQiDICQ1M29mXc0se7zdbk+3uu//TV1EkY3tnB0Q/o3Pt21nDPabjNW+jj3W2OWNxseNYc5oPD5sCmud/BqPzxrq6sFJph00gh0/ZlozXbVP19rZqIFiWqe5dtbaHftdaLvOEW79EbQdB85JzkXtZVdM1nYZODd5L2l7O2CzWVmrP3Duh6q91sb+6rSaD5e70Gr1Bq690Gv7tbArQLuohYFa9RnX/glre9UxRNuvjiFaxYFr3+FapYHz7wxalTXOfzBph1Uwo/aDjj2ZtalHxigasQz8nqTR5q9zT9NI89f7R9SOKFgf0KyyNuVmrX+j12xVM5ehdwNojtJTcxniL0CzPFUzlcH/hDRX0zbD0xB8QlrCbFWb4uWcgJqt1dAy9CcTQEs8ZqnaG/ZS7U5AzWbsXNM2pKjRHNY444p2MX2DixrMYc1KH15d247gBwrRnPQLWqe9gYN2CWsJgzSgqNElornpDZZW0xeVX2Laask+12r6oobXiGavVtAKWnyNaatlzNVruinCrzGtnW2K9JpuioS3mOawrAhaTTNFoltEW3SyexJAU96p3i2mXbnZPe+Apky47jOqZf/doWr8GdXa6w9zSCtPkRDX1m85m6gFS1Rb14BZRC3ENXd91wzS9ktNQ7VN0xyiFuJaO98EkjS+xLXN5LRBTXq0YlzbNI1ZoCY9WgNccza3JSQt+ka1Tj4eM5IW4poP78R12gDX8gXCJmn8G9XyGqRFoGgRruU1SItA0WJUG2678EHSQlTz0QRD1Qaotl2kbZr2jWnbGjCrAW1bA/aXpHmYNizmEaYVa0iAab4h+ammFS9Ke3etV4yuhWl7JK2oAVvQNKSmQ2G3imotiuYbEzP1OwTWuBiZIdoRIzynQg1YB9P2KZrQ0bQIiCa+6sEVSYwMFljbWsy8WoodtTFNepsGgOYQcstsDyh/0yy1Wl+85Qprm/wtqH8DijVwMK20E4+1mhhzuJhW2hcFOq1PS2gv1JBgqdHEjrIOoilbtlCjiVsAXinNi1StJ4XklVJQT9V8Yhatiy3VL1VPHjZIe9XljJGsfX51pabD+Zt+1/xcapvUURfUgHg2ljW5o1Dq/gId3XG5p1JHWQdqGxjOyvssqaMcyC3HcBoVSZr0T31Aw6KtgaB1KecL6HFRJGix9Bd9Qms4exoUmpS5OlrNFKQWKUHfePbx6BhT2UGuyR0dqtqIEBhHuRZIj5WabFukQ4DBRisdpZQ14tlJtNbk+dEraw4jXlnGJQ+bV0q2LfoPCIKsbQF8ZpS4Vc6buqu2lY6zBM3iVTDmpZq8UIq5ZaWGrYPLeQycZ9k1fnLRlYetl2uWw2pcfC53dJP81LJWdVXP2jp+Mz8rGaZaUxbjTw/DpizPH/Yc1hQW+w3+4qjG9R92vwL6rN3fRAAAAABJRU5ErkJggg==","e":1},{"id":"image_37","w":75,"h":124,"u":"","p":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEsAAAB8CAMAAAAW773NAAAAJHpUWHRDcmVhdG9yAAAImXNMyU9KVXBMK0ktUnBNS0tNLikGAEF6Bs5qehXFAAAACXBIWXMAAAABAAAAAQBPJcTWAAAA4VBMVEVHcEzr416oz1U+sFS51ViVylSKsWeIul8ph2yWy1Pp4FySylPj31woqVUusVDw5V8ie3osPI+hzVT77mT162IrqlWJyFLf21kpTYqHyFJNvE5bvk8ijGwqRY1vw1ArQo3k3Vri21kjqlPo31wjmmAikWfr4l0+uU3u5V8hgXXS11jL1VfE1Fe90la10VbZ2FiOyVP872Vbv0+u0FUheXx5xVGnzlUjoFsksE4jpFgms0slYIQttUwztkygzVQpSosii20ih3CZy1RKu05RvE7x52BmwVCDx1L26mJtwlAjboFtkvF7AAAAIHRSTlMAV8B6MIUgDz1E7WmaW7+wun/dxXmWqtaww+6z7V3Y6E4TtsEAAAQFSURBVGjevdlpW9pAEADghYSEBBIknIIgoNarh0fFtlaw1hbb//+DShDILtk5cjzdr+Z53Z2dhN0ZIaDRtEVeo3DRyc3qXFxWc6KcTxeX73PCaqH1oZIHZY1X1sc8sOraunUyU7a7saZm5oQYb61Z1jTrSFaQDTPHkvXczhh52boqZYn8WLWuMmRGc8daLMwskVfntbiz0kZ+FLeGKTezqrHu+ilzXmf9MVLl/Ehr/U7zZnYAq5d8M60RYN0kj38VtF76KZYIWDcvCePvjBDrZznxEmGrlyhkLmqd+kmS6wi3ThOErEZZj+yQ2Uek9Wizl0ha3332EmnrGy9k9juO9YUVskLMCnSWzwlZLWYVddakxVli3Ap01qTMWGLcqmite5teYtwytdZXMjHONJYItNa9RyxRa7X11jG+yqrWcgAL30tXYwVCANYxtpfmmca6FaINWAfIKpuAVQGsE+S9bACWBVlP4CrtX4AlAsA62QMzArRKkPUEJVlVby0PXg5oQeF39VZ4IgGtH/rwW6+w1Qatc+15sYBYFdjShr+GWBZsnevyooFYYghbmonZr4C1uie0YetzWRMuzHIQKz6xJmrZiBWfWAO1xD5ixSb2ilsVxPq7MzHrGrKKb982zOruhJ6wBGY9qMnfpKw2ZtXVrKesCmY9qNsIWtN1QFFrIFvXlCWGmCVH36StEmbNLXkbSctDrbq8jbC1/pc2anV51ubaOMSsuS2lBG0ZqDWQUoK2HNSqs6xtnQO1ulJ6wda2ANPHrHkyy0AtK5FlolaZY0UVqx5mDThWcWv1MauezDIyW0H0BchszaJXrZej1edYWN5LNVEjsxXVl0yOVeVZosfI1SbT8hkW9o2eSdVVg/FuC6blgNYh6/dRtgTj+4WcAaazopADRn5Xlz+QPKsFWfK5yYWtQHrMA6xD1hlzOnuWj8j072N47WBZoqe3Bqy7wtKSi7QtvaXesUyeZZDnnFWKgZbcCTDp81cYMdBSWhRay46XJziWr7G6mlo7YO3LT7XIczRYg4lZXtzS3iEbeutKfqZM3js2q6QtQd+HoHrhylI6Cj55T9vsJW21yPsjWkNWksIj77XbkHUoq0zet6OXxI1bSlII1UIbWiG2YwXKA4+yRVRazXivSfm7L1kHVG20sGupbT5Dshg1W3dnXkpSeJG1x+n0ueq81I3cWgesTqLpKvNSOrb21vKYPciObKlJsbaO9wRz2B25F6BuJF0Tjb2bkuVorEQ9rEJkKY1kY2Ul7K2ZwaaPrGxkK7R8kXDYxfW81M90aKXoBVfW/Q4lwZZWWaQY1lsvQA6+NZl4It0ohZYSfE4fBtqC/cWdnPleempVShvmRS03tB3tmyf+x/gHuf5GxB+6iq8AAAAASUVORK5CYII=","e":1},{"id":"image_38","w":77,"h":122,"u":"","p":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAE0AAAB6CAMAAADNqC6XAAAAJHpUWHRDcmVhdG9yAAAImXNMyU9KVXBMK0ktUnBNS0tNLikGAEF6Bs5qehXFAAAACXBIWXMAAAABAAAAAQBPJcTWAAAAjVBMVEVHcEzXiIjipH/ERZHfnILlrHXmrHzVf4jfm4LfmoDyz2D01VnESpH431DptXfajofVgIr321T75kjQdI3fnILbkobrunbdl4TiooDptXjtwm3WhIrUf4vTeozYiYnajojObo/kqH7mrHzsvXPvxmn33FLnsXrz0V3DQZHMaI/KX5D97EPIV5Dxy2PGT5C7ysPAAAAAE3RSTlMAwILMoiDSOVwQYD2CuqbZdN6OeZvAhQAABG1JREFUaN6tmu16qjoQRiElIYDfB0W7t7tVqz2tp/v+L+8ggmSSmSEg/q3P6spMDMkbgmDwZ5EEo32ScL8YDRbP9/twNNjv3/v9PyMNVf+qaPEosMWvOy0aAxb9qWnzMZr5p6E9P9Rk/tbSFk/PjLcHbf/+5ByJ1+s3w+39KZhcQ9pThdNrmzZ5AlYUFu2JwpUwmza8cDrHaPFQGEobVrgox2nhUBhKG1K4aEvS4gEwmjYZACNpX+EQ2JZy++oJO2wZt/evXoUThwPv1meN04cD79anDfp46HJ76QE7drp9J/6wkrY9sG7fnm2Qx2O329e3X+Hi4+VB49y8Cicvl4uX238+z01VwVy30KHJbloJQ90il9bdhtlu19BgT2N3pJ1tEBXMdStC6dK62pBuStrugvRUx71pcrch3OaBcGl/+XbeYJDWuC2CCKGxbVCbTY2z3dZJECI0zbVzcwJuRk/LrSkye/9OuA5sSDdZ7pIQGt2G2IDZdYtuG7g+tKQcJuFW5GW1BUb7lyza6US63bbgEUojmpq+nkwcrNutdfMeNAlhsKfFbcleozS8qdNXxk24u2iWJj5uNKpu8r7zxWgv6Dg/GLfqUCX8adMPzq2qdIjTkCmS/nzQbtv7CW1O0JxnavLzg7tVPa2mR5CsCZozRVafnFue1AcjP1r2+fnD1E3cj7kUzZ4i00/W7f6Uizxp2Zl1q/ejc0/a9My61atrQdFeHDXGTdVrnydtembdRHNu86LJ85muWznf6skp/GirM+vW5B4hSQM/rSXndtg2c7PwoqVXzq3pQSD9aKsr66bJkzhKW/JuzQIhvGjyyro9sqfQi5ZeWbfH+lB40VasmwraJvjQpqybZhMMl7Zk3R6LtPCjXTm3Nv8LB9GgW7tGF7kPTXJuqv1azri1v/qMc9MdyU8nzXRrexCIZ93MHgQhR5t4uWUtLfejMT1Vxn6YpcU+8010JmbuPoR2kyDlomnmY4H8nc7MA85AWutmnnjygqFNAnJFeuCOAWgCQ4sDcrVsYDtzoIKjgadphrpdLrFHbomc2m40dx+izK9wCYZ1Pp2idRNW2UiatSPHn6cSpr00zTrsZpib8ktokeTnjLiB/6gYmnMOx/ZI5hFAMvmbe45JXbcZCOJ6paCJ65aC41y/vHfqzDdw1lG0G5YzprYbGGhC52/4qdl207BsFI24u1tBtx08BeM0+j5Lwp7OYBREuZHhLDhnwY5KKrek06gUuCVI2RwaF20tDbeZcyWAJLSazQMNNwH+skXdNJ8ILls3kLnGaN7bFaSmDzdlzw83i+5OZZeNmzM/bLfQIzBOG7cM/KzcnDz0ugRY1m7W7Yft5nl3kt7dYNlmtpv3tU6VcW1SOFDoFvq/QJBVtNi65jHdet2Cz17tskWmm08vzXGVTVDWQFs33ffOLz2dBHGfJQa8cqFOGezo3S3vOchmMTuBgdZZjRp6MZ+5d21KB6N8VEkbi3W7HxuNlWgVZaO9IyT0iG8cDfj8D45ZCDoCHh+OAAAAAElFTkSuQmCC","e":1},{"id":"image_39","w":104,"h":194,"u":"","p":"data:image/png;base64,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","e":1},{"id":"image_40","w":110,"h":192,"u":"","p":"data:image/png;base64,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","e":1},{"id":"image_41","w":89,"h":189,"u":"","p":"data:image/png;base64,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","e":1},{"id":"image_42","w":84,"h":194,"u":"","p":"data:image/png;base64,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","e":1},{"id":"image_43","w":89,"h":189,"u":"","p":"data:image/png;base64,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","e":1},{"id":"image_44","w":89,"h":188,"u":"","p":"data:image/png;base64,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","e":1},{"id":"image_45","w":176,"h":97,"u":"","p":"data:image/png;base64,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","e":1},{"id":"image_46","w":144,"h":143,"u":"","p":"data:image/png;base64,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","e":1},{"id":"image_47","w":142,"h":88,"u":"","p":"data:image/png;base64,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","e":1},{"id":"image_48","w":112,"h":83,"u":"","p":"data:image/png;base64,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","e":1},{"id":"image_49","w":214,"h":159,"u":"","p":"data:image/png;base64,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","e":1},{"id":"image_50","w":134,"h":247,"u":"","p":"data:image/png;base64,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","e":1},{"id":"image_51","w":112,"h":259,"u":"","p":"data:image/png;base64,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","e":1}],"layers":[{"ddd":0,"ind":1,"ty":3,"nm":"NULL CONTROL ","sr":1,"ks":{"o":{"a":0,"k":0,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[260.898,561.149,0],"ix":2},"a":{"a":0,"k":[50,50,0],"ix":1},"s":{"a":0,"k":[32,32,100],"ix":6}},"ao":0,"ip":0,"op":105,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":2,"nm":"Layer 2","parent":1,"refId":"image_0","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[19.171,289.451,0],"ix":2},"a":{"a":0,"k":[476,954,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.721,0.721,0.721],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,19.333]},"t":0,"s":[0,0,100]},{"i":{"x":[0.728,0.728,0.728],"y":[1,1,1]},"o":{"x":[0.212,0.212,0.212],"y":[0,0,0]},"t":11,"s":[116,116,100]},{"t":62,"s":[0,0,100]}],"ix":6}},"ao":0,"ip":0,"op":105,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":2,"nm":"Layer 3","parent":1,"refId":"image_1","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.831],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":87.1025390625,"s":[-408.307]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.167},"t":0,"s":[43.284,15.222,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.215},"t":13.188,"s":[-293.512,-1166.629,0],"to":[0,0,0],"ti":[320,-2754,0]},{"t":80.7158203125,"s":[33.872,456.375,0]}],"ix":2},"a":{"a":0,"k":[29,106,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.519,0.519,0.519],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0]},"t":0,"s":[0,0,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":8,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":60.355,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":83.355,"s":[100,-100,100]},{"t":107.35546875,"s":[100,100,100]}],"ix":6}},"ao":0,"ip":0,"op":105,"st":0,"bm":0},{"ddd":0,"ind":4,"ty":2,"nm":"Layer 4","parent":1,"refId":"image_2","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.831],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":80.771484375,"s":[333.811]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.167},"t":0,"s":[43.284,15.222,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.237},"t":9.304,"s":[-453.503,-1143.468,0],"to":[0,0,0],"ti":[320,-2754,0]},{"t":91.4677734375,"s":[-334.128,424.375,0]}],"ix":2},"a":{"a":0,"k":[29,106,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.519,0.519,0.519],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0]},"t":0,"s":[0,0,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":8,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":58.119,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":81.119,"s":[100,-100,100]},{"t":105.119140625,"s":[100,100,100]}],"ix":6}},"ao":0,"ip":0,"op":105,"st":0,"bm":0},{"ddd":0,"ind":5,"ty":2,"nm":"Layer 5","parent":1,"refId":"image_3","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.831],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":112.806640625,"s":[277.128]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.167},"t":0,"s":[43.284,-11.278,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.229},"t":9.865,"s":[-641.354,-661.064,0],"to":[0,0,0],"ti":[-256,-2258,0]},{"t":86.390625,"s":[-590.128,520.375,0]}],"ix":2},"a":{"a":0,"k":[54.5,53,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.519,0.519,0.519],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0]},"t":0,"s":[0,0,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":8,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":42.827,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":65.827,"s":[100,-100,100]},{"t":89.8271484375,"s":[100,100,100]}],"ix":6}},"ao":0,"ip":0,"op":105,"st":0,"bm":0},{"ddd":0,"ind":6,"ty":2,"nm":"Layer 6","parent":1,"refId":"image_4","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.831],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":108.1025390625,"s":[66.474]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.167},"t":0,"s":[43.284,15.222,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.624,"y":0.756},"o":{"x":0.167,"y":0.172},"t":16.247,"s":[-124.158,-1222.782,0],"to":[0,0,0],"ti":[300.62,-2401.303,0]},{"i":{"x":0.722,"y":1},"o":{"x":0.374,"y":0.813},"t":77,"s":[-804.78,411.671,0],"to":[-10.967,87.605,0],"ti":[11.263,-96.936,0]},{"t":97.466796875,"s":[-646.128,432.375,0]}],"ix":2},"a":{"a":0,"k":[39.5,106,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.519,0.519,0.519],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0]},"t":0,"s":[0,0,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":8,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":51.709,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":74.709,"s":[100,-100,100]},{"t":98.708984375,"s":[100,100,100]}],"ix":6}},"ao":0,"ip":0,"op":105,"st":0,"bm":0},{"ddd":0,"ind":7,"ty":2,"nm":"Layer 7","parent":1,"refId":"image_5","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.831],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":76.435546875,"s":[-322.516]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.167},"t":0,"s":[43.284,15.222,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.206},"t":13.507,"s":[-262.006,-845.219,0],"to":[0,0,0],"ti":[240,-2386,0]},{"t":84.5537109375,"s":[-782.128,616.375,0]}],"ix":2},"a":{"a":0,"k":[29,106,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.519,0.519,0.519],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0]},"t":0,"s":[0,0,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":8,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":65.008,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":88.008,"s":[100,-100,100]},{"t":112.0078125,"s":[100,100,100]}],"ix":6}},"ao":0,"ip":0,"op":105,"st":0,"bm":0},{"ddd":0,"ind":8,"ty":2,"nm":"Layer 8","parent":1,"refId":"image_6","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.831],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":83.5185546875,"s":[-412.496]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.167},"t":0,"s":[43.284,41.722,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.183},"t":15.715,"s":[-512.143,-859.842,0],"to":[0,0,0],"ti":[320,-2754,0]},{"t":103,"s":[-350.128,536.375,0]}],"ix":2},"a":{"a":0,"k":[40.5,159,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.519,0.519,0.519],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0]},"t":0,"s":[0,0,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":8,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":58.388,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":81.388,"s":[100,-100,100]},{"t":105.3876953125,"s":[100,100,100]}],"ix":6}},"ao":0,"ip":0,"op":105,"st":0,"bm":0},{"ddd":0,"ind":9,"ty":2,"nm":"Layer 9","parent":1,"refId":"image_7","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.831],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":101.78515625,"s":[343.807]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.167},"t":0,"s":[43.284,24.222,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.249},"t":12.53,"s":[-527.013,-1046.968,0],"to":[0,0,0],"ti":[-407.77,-1996.524,0]},{"t":100.2724609375,"s":[-502.128,488.375,0]}],"ix":2},"a":{"a":0,"k":[45.5,124,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.519,0.519,0.519],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0]},"t":0,"s":[0,0,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":8,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":59.106,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":82.106,"s":[100,-100,100]},{"t":106.1064453125,"s":[100,100,100]}],"ix":6}},"ao":0,"ip":0,"op":105,"st":0,"bm":0},{"ddd":0,"ind":10,"ty":2,"nm":"Layer 10","parent":1,"refId":"image_8","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.831],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":101.15625,"s":[214.936]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.167},"t":0,"s":[43.284,40.222,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.227},"t":10.553,"s":[49.872,-1251.625,0],"to":[0,0,0],"ti":[320,-2754,0]},{"t":90.2548828125,"s":[529.872,440.375,0]}],"ix":2},"a":{"a":0,"k":[36.5,156,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.519,0.519,0.519],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0]},"t":0,"s":[0,0,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":8,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":52.324,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":75.324,"s":[100,-100,100]},{"t":99.32421875,"s":[100,100,100]}],"ix":6}},"ao":0,"ip":0,"op":105,"st":0,"bm":0},{"ddd":0,"ind":11,"ty":2,"nm":"Layer 11","parent":1,"refId":"image_9","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.831],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":82.3828125,"s":[-452.243]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.167},"t":0,"s":[43.284,23.222,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.25},"t":9.722,"s":[17.572,-733.066,0],"to":[0,0,0],"ti":[0,-1.333,0]},{"t":103.9873046875,"s":[57.872,424.375,0]}],"ix":2},"a":{"a":0,"k":[38.5,122,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.519,0.519,0.519],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0]},"t":0,"s":[0,0,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":8,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":52.705,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":75.705,"s":[100,-100,100]},{"t":99.705078125,"s":[100,100,100]}],"ix":6}},"ao":0,"ip":0,"op":105,"st":0,"bm":0},{"ddd":0,"ind":12,"ty":2,"nm":"Layer 12","parent":1,"refId":"image_10","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.831],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":115.109375,"s":[316.435]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.167},"t":0,"s":[43.284,23.222,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.211},"t":11.565,"s":[-183.479,-864.334,0],"to":[0,0,0],"ti":[320,-2754,0]},{"t":100.826171875,"s":[-542.128,480.375,0]}],"ix":2},"a":{"a":0,"k":[38.5,122,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.519,0.519,0.519],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0]},"t":0,"s":[0,0,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":8,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":33,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":55.999,"s":[100,-100,100]},{"t":80,"s":[100,100,100]}],"ix":6}},"ao":0,"ip":0,"op":105,"st":0,"bm":0},{"ddd":0,"ind":13,"ty":2,"nm":"Layer 13","parent":1,"refId":"image_11","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.831],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":84.4169921875,"s":[88.571]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.167},"t":0,"s":[43.284,24.222,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.173},"t":15.813,"s":[-618.369,-911.114,0],"to":[0,0,0],"ti":[320,-2754,0]},{"t":95,"s":[-486.128,536.375,0]}],"ix":2},"a":{"a":0,"k":[37.5,124,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.519,0.519,0.519],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0]},"t":0,"s":[0,0,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":8,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":43.172,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":66.172,"s":[100,-100,100]},{"t":90.171875,"s":[100,100,100]}],"ix":6}},"ao":0,"ip":0,"op":105,"st":0,"bm":0},{"ddd":0,"ind":14,"ty":2,"nm":"Layer 14","parent":1,"refId":"image_12","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.831],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":83.9404296875,"s":[-251.629]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.167},"t":0,"s":[43.284,22.222,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.191},"t":15.252,"s":[-420.029,-756.057,0],"to":[0,0,0],"ti":[304,-2314,0]},{"t":73.033203125,"s":[65.872,464.375,0]}],"ix":2},"a":{"a":0,"k":[38.5,120,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.519,0.519,0.519],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0]},"t":0,"s":[0,0,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":8,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":53.378,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":76.378,"s":[100,-100,100]},{"t":100.3779296875,"s":[100,100,100]}],"ix":6}},"ao":0,"ip":0,"op":105,"st":0,"bm":0},{"ddd":0,"ind":15,"ty":2,"nm":"Layer 15","parent":1,"refId":"image_13","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.831],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":93.32421875,"s":[-221.347]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.167},"t":0,"s":[43.284,59.222,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.243},"t":10.837,"s":[173.826,-1008.003,0],"to":[0,0,0],"ti":[96,-2098,0]},{"t":105.357421875,"s":[-158.128,448.375,0]}],"ix":2},"a":{"a":0,"k":[53.5,194,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.519,0.519,0.519],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0]},"t":0,"s":[0,0,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":8,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":38.935,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":61.935,"s":[100,-100,100]},{"t":85.9345703125,"s":[100,100,100]}],"ix":6}},"ao":0,"ip":0,"op":105,"st":0,"bm":0},{"ddd":0,"ind":16,"ty":2,"nm":"Layer 16","parent":1,"refId":"image_14","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.831],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":110.423828125,"s":[-449.185]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.167},"t":0,"s":[43.284,59.222,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.142},"t":16.032,"s":[-270.448,-966.067,0],"to":[0,0,0],"ti":[184,-2394,0]},{"t":84,"s":[-638.128,552.375,0]}],"ix":2},"a":{"a":0,"k":[52,194,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.519,0.519,0.519],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0]},"t":0,"s":[0,0,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":8,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":38.72,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":61.72,"s":[100,-100,100]},{"t":85.7197265625,"s":[100,100,100]}],"ix":6}},"ao":0,"ip":0,"op":105,"st":0,"bm":0},{"ddd":0,"ind":17,"ty":2,"nm":"Layer 17","parent":1,"refId":"image_15","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.831],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":96.00390625,"s":[183.632]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.167},"t":0,"s":[43.284,58.222,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.218},"t":8.6,"s":[-186.287,-660.152,0],"to":[0,0,0],"ti":[128,-2218,0]},{"t":103.9892578125,"s":[-614.128,488.375,0]}],"ix":2},"a":{"a":0,"k":[55,192,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.519,0.519,0.519],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0]},"t":0,"s":[0,0,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":8,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":55.063,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":78.063,"s":[100,-100,100]},{"t":102.0634765625,"s":[100,100,100]}],"ix":6}},"ao":0,"ip":0,"op":105,"st":0,"bm":0},{"ddd":0,"ind":18,"ty":2,"nm":"Layer 18","parent":1,"refId":"image_16","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.831],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":90.724609375,"s":[-93.048]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.167},"t":0,"s":[43.284,56.722,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.226},"t":9.443,"s":[-308.83,-718.718,0],"to":[0,0,0],"ti":[136,-2018,0]},{"t":93.0234375,"s":[-742.128,544.375,0]}],"ix":2},"a":{"a":0,"k":[44.5,189,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.519,0.519,0.519],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0]},"t":0,"s":[0,0,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":8,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":43.755,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":66.755,"s":[100,-100,100]},{"t":90.7548828125,"s":[100,100,100]}],"ix":6}},"ao":0,"ip":0,"op":105,"st":0,"bm":0},{"ddd":0,"ind":19,"ty":2,"nm":"Layer 19","parent":1,"refId":"image_17","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.831],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":85.91796875,"s":[323.925]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.167},"t":0,"s":[43.284,43.222,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.177},"t":7.259,"s":[131.777,-1201.391,0],"to":[0,0,0],"ti":[320,-2754,0]},{"t":80,"s":[137.872,480.375,0]}],"ix":2},"a":{"a":0,"k":[40,162,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.519,0.519,0.519],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0]},"t":0,"s":[0,0,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":8,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":50.032,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":73.032,"s":[100,-100,100]},{"t":97.0322265625,"s":[100,100,100]}],"ix":6}},"ao":0,"ip":0,"op":105,"st":0,"bm":0},{"ddd":0,"ind":20,"ty":2,"nm":"Layer 20","parent":1,"refId":"image_18","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.831],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":81.072265625,"s":[-63.263]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.167},"t":0,"s":[43.284,58.722,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.211},"t":9.802,"s":[-139.641,-990.282,0],"to":[0,0,0],"ti":[320,-2754,0]},{"t":67.1083984375,"s":[-694.128,544.375,0]}],"ix":2},"a":{"a":0,"k":[41,193,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.519,0.519,0.519],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0]},"t":0,"s":[0,0,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":8,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":37.565,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":60.565,"s":[100,-100,100]},{"t":84.5654296875,"s":[100,100,100]}],"ix":6}},"ao":0,"ip":0,"op":105,"st":0,"bm":0},{"ddd":0,"ind":21,"ty":2,"nm":"Layer 21","parent":1,"refId":"image_19","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.831],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":96.97265625,"s":[-315.838]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.167},"t":0,"s":[43.284,53.722,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.209},"t":8.128,"s":[-206.256,-1145.789,0],"to":[0,0,0],"ti":[320,-2754,0]},{"t":102,"s":[-806.128,480.375,0]}],"ix":2},"a":{"a":0,"k":[52,183,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.519,0.519,0.519],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0]},"t":0,"s":[0,0,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":8,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":65.619,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":88.619,"s":[100,-100,100]},{"t":112.619140625,"s":[100,100,100]}],"ix":6}},"ao":0,"ip":0,"op":105,"st":0,"bm":0},{"ddd":0,"ind":22,"ty":2,"nm":"Layer 22","parent":1,"refId":"image_20","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.831],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":114.46484375,"s":[-259.512]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.167},"t":0,"s":[43.284,56.222,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.238},"t":8.52,"s":[-366.619,-1069.492,0],"to":[0,0,0],"ti":[320,-2754,0]},{"t":96.6259765625,"s":[-686.128,488.375,0]}],"ix":2},"a":{"a":0,"k":[44.5,188,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.519,0.519,0.519],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0]},"t":0,"s":[0,0,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":8,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":40.262,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":63.262,"s":[100,-100,100]},{"t":87.26171875,"s":[100,100,100]}],"ix":6}},"ao":0,"ip":0,"op":105,"st":0,"bm":0},{"ddd":0,"ind":23,"ty":2,"nm":"Layer 23","parent":1,"refId":"image_21","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.831],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":78.58203125,"s":[-236.661]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.167},"t":0,"s":[43.284,10.722,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.173},"t":16.523,"s":[-505.73,-563.466,0],"to":[0,0,0],"ti":[320,-2754,0]},{"t":73.544921875,"s":[81.872,488.375,0]}],"ix":2},"a":{"a":0,"k":[88,97,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.519,0.519,0.519],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0]},"t":0,"s":[0,0,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":8,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":27.63,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":50.63,"s":[100,-100,100]},{"t":74.6298828125,"s":[100,100,100]}],"ix":6}},"ao":0,"ip":0,"op":105,"st":0,"bm":0},{"ddd":0,"ind":24,"ty":2,"nm":"Layer 24","parent":1,"refId":"image_22","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.831],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":79.9482421875,"s":[-65.959]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.167},"t":0,"s":[43.284,33.722,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.182},"t":14.115,"s":[-361.573,-377.534,0],"to":[0,0,0],"ti":[24,-2354,0]},{"t":84.2890625,"s":[-78.128,520.375,0]}],"ix":2},"a":{"a":0,"k":[72,143,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.519,0.519,0.519],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0]},"t":0,"s":[0,0,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":8,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":48.496,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":71.496,"s":[100,-100,100]},{"t":95.49609375,"s":[100,100,100]}],"ix":6}},"ao":0,"ip":0,"op":105,"st":0,"bm":0},{"ddd":0,"ind":25,"ty":2,"nm":"Layer 25","parent":1,"refId":"image_23","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.831],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":104.2109375,"s":[-60.331]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.167},"t":0,"s":[43.284,6.222,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.197},"t":14.122,"s":[-468.314,-460.573,0],"to":[0,0,0],"ti":[296,-2114,0]},{"t":90.72265625,"s":[-198.128,600.375,0]}],"ix":2},"a":{"a":0,"k":[71,88,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.519,0.519,0.519],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0]},"t":0,"s":[0,0,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":8,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":26.418,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":49.418,"s":[100,-100,100]},{"t":73.41796875,"s":[100,100,100]}],"ix":6}},"ao":0,"ip":0,"op":105,"st":0,"bm":0},{"ddd":0,"ind":26,"ty":2,"nm":"Layer 26","parent":1,"refId":"image_24","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.831],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":77.802734375,"s":[328.004]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.167},"t":0,"s":[43.284,3.722,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.133},"t":7.976,"s":[-259.378,-370.437,0],"to":[0,0,0],"ti":[272,-2330,0]},{"t":88,"s":[-70.128,528.375,0]}],"ix":2},"a":{"a":0,"k":[56,83,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.519,0.519,0.519],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0]},"t":0,"s":[0,0,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":8,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":62.791,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":85.791,"s":[100,-100,100]},{"t":109.791015625,"s":[100,100,100]}],"ix":6}},"ao":0,"ip":0,"op":105,"st":0,"bm":0},{"ddd":0,"ind":27,"ty":2,"nm":"Layer 27","parent":1,"refId":"image_25","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.831],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":83.486328125,"s":[-312.749]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.167},"t":0,"s":[43.284,41.722,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.235},"t":13.124,"s":[-585.135,-722.821,0],"to":[0,0,0],"ti":[208,-2018,0]},{"t":105.2685546875,"s":[-422.128,512.375,0]}],"ix":2},"a":{"a":0,"k":[107,159,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.519,0.519,0.519],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0]},"t":0,"s":[0,0,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":8,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":28.163,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":51.163,"s":[100,-100,100]},{"t":75.1630859375,"s":[100,100,100]}],"ix":6}},"ao":0,"ip":0,"op":105,"st":0,"bm":0},{"ddd":0,"ind":28,"ty":2,"nm":"Layer 28","parent":1,"refId":"image_26","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.831],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":110.236328125,"s":[463.981]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.167},"t":0,"s":[43.284,91.722,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.212},"t":7.548,"s":[-29.778,-1062.058,0],"to":[0,0,0],"ti":[320,-2754,0]},{"t":86.9501953125,"s":[457.872,488.375,0]}],"ix":2},"a":{"a":0,"k":[57,259,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.519,0.519,0.519],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0]},"t":0,"s":[0,0,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":8,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":53.864,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":76.863,"s":[100,-100,100]},{"t":100.8642578125,"s":[100,100,100]}],"ix":6}},"ao":0,"ip":0,"op":105,"st":0,"bm":0},{"ddd":0,"ind":29,"ty":2,"nm":"Layer 29","parent":1,"refId":"image_27","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.831],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":89.0361328125,"s":[-484.46]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.167},"t":0,"s":[43.284,85.722,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.241},"t":9.685,"s":[-411.043,-866.994,0],"to":[0,0,0],"ti":[-80,-2138,0]},{"t":99.0771484375,"s":[-678.128,568.375,0]}],"ix":2},"a":{"a":0,"k":[67,247,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.519,0.519,0.519],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0]},"t":0,"s":[0,0,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":8,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":35.429,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":58.429,"s":[100,-100,100]},{"t":82.4287109375,"s":[100,100,100]}],"ix":6}},"ao":0,"ip":0,"op":105,"st":0,"bm":0},{"ddd":0,"ind":30,"ty":2,"nm":"Layer 30","parent":1,"refId":"image_28","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.831],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":104.14453125,"s":[-431.019]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.167},"t":0,"s":[43.284,91.722,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.152},"t":17.008,"s":[-79.428,-780.951,0],"to":[0,0,0],"ti":[320,-2754,0]},{"t":97,"s":[-758.128,584.375,0]}],"ix":2},"a":{"a":0,"k":[56,259,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.519,0.519,0.519],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0]},"t":0,"s":[0,0,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":8,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":62.371,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":85.371,"s":[100,-100,100]},{"t":109.37109375,"s":[100,100,100]}],"ix":6}},"ao":0,"ip":0,"op":105,"st":0,"bm":0},{"ddd":0,"ind":31,"ty":2,"nm":"Layer 31","parent":1,"refId":"image_29","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.831],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":78.328125,"s":[446.848]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.167},"t":0,"s":[43.284,6.722,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.231},"t":8.974,"s":[318.579,-1167.501,0],"to":[0,0,0],"ti":[320,-2754,0]},{"t":80.373046875,"s":[401.872,440.375,0]}],"ix":2},"a":{"a":0,"k":[28,89,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.519,0.519,0.519],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0]},"t":0,"s":[0,0,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":8,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":43.723,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":66.723,"s":[100,-100,100]},{"t":90.72265625,"s":[100,100,100]}],"ix":6}},"ao":0,"ip":0,"op":105,"st":0,"bm":0},{"ddd":0,"ind":32,"ty":2,"nm":"Layer 32","parent":1,"refId":"image_30","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.831],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":95.12890625,"s":[-50.488]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.167},"t":0,"s":[43.284,15.222,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.238},"t":10.871,"s":[492.056,-1143.467,0],"to":[0,0,0],"ti":[296,-2618,0]},{"t":103.08984375,"s":[641.872,448.375,0]}],"ix":2},"a":{"a":0,"k":[29,106,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.519,0.519,0.519],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0]},"t":0,"s":[0,0,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":8,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":41.872,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":64.872,"s":[100,-100,100]},{"t":88.8720703125,"s":[100,100,100]}],"ix":6}},"ao":0,"ip":0,"op":105,"st":0,"bm":0},{"ddd":0,"ind":33,"ty":2,"nm":"Layer 33","parent":1,"refId":"image_31","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.831],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":93.06640625,"s":[-141.337]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.167},"t":0,"s":[43.284,-11.278,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.266},"t":7.979,"s":[679.848,-661.065,0],"to":[0,0,0],"ti":[384,-1434,0]},{"t":83.6396484375,"s":[409.872,408.375,0]}],"ix":2},"a":{"a":0,"k":[54.5,53,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.519,0.519,0.519],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0]},"t":0,"s":[0,0,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":8,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":54.619,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":77.619,"s":[100,-100,100]},{"t":101.619140625,"s":[100,100,100]}],"ix":6}},"ao":0,"ip":0,"op":105,"st":0,"bm":0},{"ddd":0,"ind":34,"ty":2,"nm":"Layer 34","parent":1,"refId":"image_32","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.831],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":95.068359375,"s":[-111.289]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.167},"t":0,"s":[43.284,17.222,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.214},"t":14.7,"s":[80.419,-1124.493,0],"to":[0,0,0],"ti":[320,-2754,0]},{"t":97.6572265625,"s":[345.872,400.375,0]}],"ix":2},"a":{"a":0,"k":[26,110,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.519,0.519,0.519],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0]},"t":0,"s":[0,0,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":8,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":33.359,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":56.359,"s":[100,-100,100]},{"t":80.359375,"s":[100,100,100]}],"ix":6}},"ao":0,"ip":0,"op":105,"st":0,"bm":0},{"ddd":0,"ind":35,"ty":2,"nm":"Layer 35","parent":1,"refId":"image_33","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.831],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":99.09375,"s":[461.811]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.167},"t":0,"s":[43.284,15.222,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.17},"t":16.078,"s":[300.585,-845.216,0],"to":[0,0,0],"ti":[320,-2754,0]},{"t":104,"s":[305.872,472.375,0]}],"ix":2},"a":{"a":0,"k":[29,106,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.519,0.519,0.519],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0]},"t":0,"s":[0,0,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":8,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":30.469,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":53.469,"s":[100,-100,100]},{"t":77.46875,"s":[100,100,100]}],"ix":6}},"ao":0,"ip":0,"op":105,"st":0,"bm":0},{"ddd":0,"ind":36,"ty":2,"nm":"Layer 36","parent":1,"refId":"image_34","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.831],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":120,"s":[-363.787]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.167},"t":0,"s":[43.284,41.722,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.186},"t":15.279,"s":[550.606,-859.847,0],"to":[0,0,0],"ti":[456,-2490,0]},{"t":102,"s":[81.872,424.375,0]}],"ix":2},"a":{"a":0,"k":[40.5,159,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.519,0.519,0.519],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0]},"t":0,"s":[0,0,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":8,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":26.315,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":49.315,"s":[100,-100,100]},{"t":73.3154296875,"s":[100,100,100]}],"ix":6}},"ao":0,"ip":0,"op":105,"st":0,"bm":0},{"ddd":0,"ind":37,"ty":2,"nm":"Layer 37","parent":1,"refId":"image_35","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.831],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":113.2197265625,"s":[-378.462]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.167},"t":0,"s":[43.284,24.222,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.192},"t":8.781,"s":[565.396,-1046.958,0],"to":[0,0,0],"ti":[320,-2754,0]},{"t":103,"s":[665.872,568.375,0]}],"ix":2},"a":{"a":0,"k":[45.5,124,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.519,0.519,0.519],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0]},"t":0,"s":[0,0,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":8,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":55.754,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":78.753,"s":[100,-100,100]},{"t":102.75390625,"s":[100,100,100]}],"ix":6}},"ao":0,"ip":0,"op":105,"st":0,"bm":0},{"ddd":0,"ind":38,"ty":2,"nm":"Layer 38","parent":1,"refId":"image_36","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.831],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":80.3076171875,"s":[54.174]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.167},"t":0,"s":[43.284,23.222,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.189},"t":16.634,"s":[221.723,-864.331,0],"to":[0,0,0],"ti":[320,-2754,0]},{"t":81.63671875,"s":[305.872,448.375,0]}],"ix":2},"a":{"a":0,"k":[38.5,122,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.519,0.519,0.519],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0]},"t":0,"s":[0,0,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":8,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":41.028,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":64.028,"s":[100,-100,100]},{"t":88.0283203125,"s":[100,100,100]}],"ix":6}},"ao":0,"ip":0,"op":105,"st":0,"bm":0},{"ddd":0,"ind":39,"ty":2,"nm":"Layer 39","parent":1,"refId":"image_37","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.831],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":95.115234375,"s":[299.774]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.167},"t":0,"s":[43.284,24.222,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.259},"t":7.976,"s":[657.604,-911.115,0],"to":[0,0,0],"ti":[400,-1810,0]},{"t":74.6279296875,"s":[529.872,440.375,0]}],"ix":2},"a":{"a":0,"k":[37.5,124,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.519,0.519,0.519],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0]},"t":0,"s":[0,0,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":8,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":30.896,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":53.896,"s":[100,-100,100]},{"t":77.896484375,"s":[100,100,100]}],"ix":6}},"ao":0,"ip":0,"op":105,"st":0,"bm":0},{"ddd":0,"ind":40,"ty":2,"nm":"Layer 40","parent":1,"refId":"image_38","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.831],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":94.71484375,"s":[408.356]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.167},"t":0,"s":[43.284,23.222,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.213},"t":14.321,"s":[458.294,-754.048,0],"to":[0,0,0],"ti":[336,-2346,0]},{"t":104.5693359375,"s":[529.872,440.375,0]}],"ix":2},"a":{"a":0,"k":[38.5,122,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.519,0.519,0.519],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0]},"t":0,"s":[0,0,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":8,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":64.818,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":87.818,"s":[100,-100,100]},{"t":111.818359375,"s":[100,100,100]}],"ix":6}},"ao":0,"ip":0,"op":105,"st":0,"bm":0},{"ddd":0,"ind":41,"ty":2,"nm":"Layer 41","parent":1,"refId":"image_39","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.831],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":87.1083984375,"s":[276.287]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.167},"t":0,"s":[43.284,59.222,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.206},"t":13.954,"s":[309.209,-966.075,0],"to":[0,0,0],"ti":[320,-2754,0]},{"t":83.669921875,"s":[529.872,440.375,0]}],"ix":2},"a":{"a":0,"k":[52,194,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.519,0.519,0.519],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0]},"t":0,"s":[0,0,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":8,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":50.654,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":73.654,"s":[100,-100,100]},{"t":97.654296875,"s":[100,100,100]}],"ix":6}},"ao":0,"ip":0,"op":105,"st":0,"bm":0},{"ddd":0,"ind":42,"ty":2,"nm":"Layer 42","parent":1,"refId":"image_40","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.831],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":106.5673828125,"s":[-1.984]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.167},"t":0,"s":[43.284,58.222,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.186},"t":14.708,"s":[225.01,-660.153,0],"to":[0,0,0],"ti":[320,-2754,0]},{"t":92.6083984375,"s":[649.872,544.375,0]}],"ix":2},"a":{"a":0,"k":[55,192,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.519,0.519,0.519],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0]},"t":0,"s":[0,0,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":8,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":35.781,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":58.781,"s":[100,-100,100]},{"t":82.78125,"s":[100,100,100]}],"ix":6}},"ao":0,"ip":0,"op":105,"st":0,"bm":0},{"ddd":0,"ind":43,"ty":2,"nm":"Layer 43","parent":1,"refId":"image_41","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.831],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":87.626953125,"s":[-494.691]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.167},"t":0,"s":[43.284,56.722,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.206},"t":11.514,"s":[32.736,-923.559,0],"to":[0,0,0],"ti":[320,-2754,0]},{"t":88.0556640625,"s":[529.872,440.375,0]}],"ix":2},"a":{"a":0,"k":[44.5,189,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.519,0.519,0.519],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0]},"t":0,"s":[0,0,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":8,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":56.146,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":79.146,"s":[100,-100,100]},{"t":103.146484375,"s":[100,100,100]}],"ix":6}},"ao":0,"ip":0,"op":105,"st":0,"bm":0},{"ddd":0,"ind":44,"ty":2,"nm":"Layer 44","parent":1,"refId":"image_42","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.831],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":78.0693359375,"s":[-385.194]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.167},"t":0,"s":[43.284,59.222,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.216},"t":14.825,"s":[244.176,-1136.626,0],"to":[0,0,0],"ti":[320,-2754,0]},{"t":91.99609375,"s":[529.872,440.375,0]}],"ix":2},"a":{"a":0,"k":[42,194,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.519,0.519,0.519],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0]},"t":0,"s":[0,0,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":8,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":36.859,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":59.859,"s":[100,-100,100]},{"t":83.859375,"s":[100,100,100]}],"ix":6}},"ao":0,"ip":0,"op":105,"st":0,"bm":0},{"ddd":0,"ind":45,"ty":2,"nm":"Layer 45","parent":1,"refId":"image_43","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.735],"y":[0.9]},"o":{"x":[0.159],"y":[0.09]},"t":0,"s":[0]},{"t":87,"s":[445.112]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.167},"t":0,"s":[43.284,56.722,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.178},"t":15.908,"s":[347.991,-718.714,0],"to":[0,0,0],"ti":[144,-2674,0]},{"t":121,"s":[529.872,440.375,0]}],"ix":2},"a":{"a":0,"k":[44.5,189,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.519,0.519,0.519],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0]},"t":0,"s":[0,0,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":8,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":42.171,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":65.171,"s":[100,-100,100]},{"t":89.1708984375,"s":[100,100,100]}],"ix":6}},"ao":0,"ip":0,"op":105,"st":0,"bm":0},{"ddd":0,"ind":46,"ty":2,"nm":"Layer 46","parent":1,"refId":"image_44","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.831],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":91.416015625,"s":[333.236]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.167},"t":0,"s":[43.284,56.222,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.238},"t":8,"s":[400.078,-1076.292,0],"to":[0,0,0],"ti":[320,-2754,0]},{"t":99.583984375,"s":[529.872,440.375,0]}],"ix":2},"a":{"a":0,"k":[44.5,188,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.519,0.519,0.519],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0]},"t":0,"s":[0,0,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":8,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":65.277,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":88.277,"s":[100,-100,100]},{"t":112.27734375,"s":[100,100,100]}],"ix":6}},"ao":0,"ip":0,"op":105,"st":0,"bm":0},{"ddd":0,"ind":47,"ty":2,"nm":"Layer 47","parent":1,"refId":"image_45","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.831],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":113.0849609375,"s":[-485.494]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.167},"t":0,"s":[43.284,10.722,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.208},"t":15.515,"s":[544.431,-563.471,0],"to":[0,0,0],"ti":[384,-1994,0]},{"t":94.806640625,"s":[529.872,440.375,0]}],"ix":2},"a":{"a":0,"k":[88,97,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.519,0.519,0.519],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0]},"t":0,"s":[0,0,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":8,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":54.68,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":77.68,"s":[100,-100,100]},{"t":101.6796875,"s":[100,100,100]}],"ix":6}},"ao":0,"ip":0,"op":105,"st":0,"bm":0},{"ddd":0,"ind":48,"ty":2,"nm":"Layer 48","parent":1,"refId":"image_46","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.831],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":97.6953125,"s":[-409.417]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.167},"t":0,"s":[43.284,33.722,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.215},"t":7.144,"s":[400.007,-377.545,0],"to":[0,0,0],"ti":[240,-1674,0]},{"t":92.912109375,"s":[593.872,448.375,0]}],"ix":2},"a":{"a":0,"k":[72,143,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.519,0.519,0.519],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0]},"t":0,"s":[0,0,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":8,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":42.787,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":65.787,"s":[100,-100,100]},{"t":89.787109375,"s":[100,100,100]}],"ix":6}},"ao":0,"ip":0,"op":105,"st":0,"bm":0},{"ddd":0,"ind":49,"ty":2,"nm":"Layer 49","parent":1,"refId":"image_47","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.831],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":115.154296875,"s":[-336.791]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.167},"t":0,"s":[43.284,6.222,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.173},"t":8.119,"s":[506.829,-460.573,0],"to":[0,0,0],"ti":[320,-2754,0]},{"t":103,"s":[633.872,456.375,0]}],"ix":2},"a":{"a":0,"k":[71,88,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.519,0.519,0.519],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0]},"t":0,"s":[0,0,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":8,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":32.079,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":55.079,"s":[100,-100,100]},{"t":79.0791015625,"s":[100,100,100]}],"ix":6}},"ao":0,"ip":0,"op":105,"st":0,"bm":0},{"ddd":0,"ind":50,"ty":2,"nm":"Layer 50","parent":1,"refId":"image_48","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.831],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":88.5029296875,"s":[-210.512]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.167},"t":0,"s":[43.284,3.722,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.194},"t":7.479,"s":[297.883,-370.437,0],"to":[0,0,0],"ti":[344,-2146,0]},{"t":102.8251953125,"s":[609.872,504.375,0]}],"ix":2},"a":{"a":0,"k":[56,83,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.519,0.519,0.519],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0]},"t":0,"s":[0,0,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":8,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":35.676,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":58.676,"s":[100,-100,100]},{"t":82.67578125,"s":[100,100,100]}],"ix":6}},"ao":0,"ip":0,"op":105,"st":0,"bm":0},{"ddd":0,"ind":51,"ty":2,"nm":"Layer 51","parent":1,"refId":"image_49","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.831],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":92.5166015625,"s":[317.237]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.167},"t":0,"s":[43.284,41.722,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.245},"t":7.901,"s":[623.397,-722.821,0],"to":[0,0,0],"ti":[96,-1842,0]},{"t":71.728515625,"s":[745.872,504.375,0]}],"ix":2},"a":{"a":0,"k":[107,159,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.519,0.519,0.519],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0]},"t":0,"s":[0,0,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":8,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":33.498,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":56.497,"s":[100,-100,100]},{"t":80.498046875,"s":[100,100,100]}],"ix":6}},"ao":0,"ip":0,"op":105,"st":0,"bm":0},{"ddd":0,"ind":52,"ty":2,"nm":"Layer 52","parent":1,"refId":"image_50","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.831],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":84.5986328125,"s":[159.427]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.167},"t":0,"s":[43.284,85.722,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.169},"t":10.031,"s":[449.823,-866.997,0],"to":[0,0,0],"ti":[320,-2754,0]},{"t":95,"s":[601.872,560.375,0]}],"ix":2},"a":{"a":0,"k":[67,247,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.519,0.519,0.519],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0]},"t":0,"s":[0,0,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":8,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":65.121,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":88.121,"s":[100,-100,100]},{"t":112.12109375,"s":[100,100,100]}],"ix":6}},"ao":0,"ip":0,"op":105,"st":0,"bm":0},{"ddd":0,"ind":53,"ty":2,"nm":"Layer 53","parent":1,"refId":"image_51","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.831],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":107.0546875,"s":[146.171]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.167},"t":0,"s":[43.284,91.722,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.721,"y":1},"o":{"x":0.167,"y":0.194},"t":11.752,"s":[117.819,-780.948,0],"to":[0,0,0],"ti":[72,-3306,0]},{"t":95.6337890625,"s":[697.872,536.375,0]}],"ix":2},"a":{"a":0,"k":[56,259,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.519,0.519,0.519],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0]},"t":0,"s":[0,0,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":8,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":42.219,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":65.218,"s":[100,-100,100]},{"t":89.21875,"s":[100,100,100]}],"ix":6}},"ao":0,"ip":0,"op":105,"st":0,"bm":0}],"markers":[]} \ No newline at end of file diff --git a/extras/calendar_today-black-24dp.svg b/extras/calendar_today-black-24dp.svg new file mode 100644 index 0000000..bdeabae --- /dev/null +++ b/extras/calendar_today-black-24dp.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/extras/date_range-black-24dp.svg b/extras/date_range-black-24dp.svg new file mode 100644 index 0000000..0317c3b --- /dev/null +++ b/extras/date_range-black-24dp.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/extras/today-black-24dp.svg b/extras/today-black-24dp.svg new file mode 100644 index 0000000..8447d8a --- /dev/null +++ b/extras/today-black-24dp.svg @@ -0,0 +1 @@ + \ No newline at end of file From 9a1034c29f08ba4003b5b73f6747e1d7ea73fe8b Mon Sep 17 00:00:00 2001 From: Javinator9889 Date: Fri, 26 Jun 2020 16:08:37 +0200 Subject: [PATCH 55/95] Updated ProGuard rules for avoiding errors on release versions --- app/proguard-rules.pro | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index 1e9bc77..f7b5d2f 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -100,4 +100,4 @@ -keep class kotlin.Metadata { *; } #data models -#-keep class com.javinator9889.handwashingreminder.collections.** { *;} +-keep class com.javinator9889.handwashingreminder.collections.** { *;} From fd8889a6c6dc78989f4b91e45708645275dba41d Mon Sep 17 00:00:00 2001 From: Javinator9889 Date: Fri, 26 Jun 2020 16:11:01 +0200 Subject: [PATCH 56/95] Removed Conscrypt support --- app/build.gradle | 4 ++-- .../handwashingreminder/activities/LauncherActivity.kt | 6 ++---- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 5d8f177..c9988ff 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -40,7 +40,7 @@ android { applicationId "com.javinator9889.handwashingreminder" minSdkVersion 17 targetSdkVersion 29 - versionCode 130 + versionCode 131 versionName "1.2.0-${gitCommitHash}" multiDexEnabled true resConfigs "en", "es" @@ -194,7 +194,7 @@ dependencies { // https://square.github.io/okio/#releases implementation 'com.squareup.okio:okio:2.5.0' // https://github.com/google/conscrypt/ - implementation 'org.conscrypt:conscrypt-android:2.4.0' +// implementation 'org.conscrypt:conscrypt-android:2.4.0' // https://github.com/deano2390/MaterialShowcaseView implementation 'com.github.deano2390:MaterialShowcaseView:1.3.4' // https://github.com/PhilJay/MPAndroidChart diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/activities/LauncherActivity.kt b/app/src/main/java/com/javinator9889/handwashingreminder/activities/LauncherActivity.kt index 4cd1bfb..8698296 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/activities/LauncherActivity.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/activities/LauncherActivity.kt @@ -54,9 +54,7 @@ import com.mikepenz.iconics.Iconics import javinator9889.localemanager.utils.languagesupport.LanguagesSupport.Language import kotlinx.android.synthetic.main.splash_screen.* import kotlinx.coroutines.* -import org.conscrypt.Conscrypt import timber.log.Timber -import java.security.Security import com.javinator9889.handwashingreminder.utils.Firebase as FirebaseConf internal const val FAST_START_KEY = "intent:fast_start" @@ -248,8 +246,8 @@ class LauncherActivity : AppCompatActivity() { Timber.d("Firebase initialized correctly") Timber.d("Initializing Iconics") Iconics.init(this) - Timber.d("Setting-up security providers") - Security.insertProviderAt(Conscrypt.newProvider(), 1) +// Timber.d("Setting-up security providers") +// Security.insertProviderAt(Conscrypt.newProvider(), 1) Timber.d("Setting-up activity recognition") val activityHandler = ActivityHandler.getInstance(this) if (sharedPreferences.getBoolean( From 841513402c3ddbda71b96a94abcec97f1aca8d26 Mon Sep 17 00:00:00 2001 From: Javinator9889 Date: Sat, 27 Jun 2020 12:00:10 +0200 Subject: [PATCH 57/95] Updated PendingIntent logic for using only the same PendingIntent, and update if necessary --- .../handwashingreminder/gms/activity/ActivityHandler.kt | 9 ++++++++- .../handwashingreminder/gms/activity/ActivityReceiver.kt | 2 +- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/gms/activity/ActivityHandler.kt b/app/src/main/java/com/javinator9889/handwashingreminder/gms/activity/ActivityHandler.kt index 03c399b..1249f41 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/gms/activity/ActivityHandler.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/gms/activity/ActivityHandler.kt @@ -19,6 +19,7 @@ package com.javinator9889.handwashingreminder.gms.activity import android.app.PendingIntent +import android.app.PendingIntent.FLAG_UPDATE_CURRENT import android.content.Context import android.content.Intent import android.content.IntentFilter @@ -88,6 +89,7 @@ class ActivityHandler private constructor(private val context: Context) { fun reload() = with(createSetOfTransitions()) { transitions.clear() addTransitions(this, transitions) + Timber.d("Reloading activity recognition - transitions: $transitions") disableActivityTracker()?.let { it.addOnCompleteListener { pendingIntent = createPendingIntent() @@ -124,7 +126,12 @@ class ActivityHandler private constructor(private val context: Context) { private fun createPendingIntent(): PendingIntent = with(Intent(TRANSITIONS_RECEIVER_ACTION)) { - PendingIntent.getBroadcast(context, ACTIVITY_REQUEST_CODE, this, 0) + PendingIntent.getBroadcast( + context, + ACTIVITY_REQUEST_CODE, + this, + FLAG_UPDATE_CURRENT + ) } private fun registerActivityReceiver() = diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/gms/activity/ActivityReceiver.kt b/app/src/main/java/com/javinator9889/handwashingreminder/gms/activity/ActivityReceiver.kt index b9212e6..31450aa 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/gms/activity/ActivityReceiver.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/gms/activity/ActivityReceiver.kt @@ -102,7 +102,7 @@ class ActivityReceiver : BroadcastReceiver() { R.string.activity_notification_vehicle_content ) else -> throw IllegalArgumentException( - "Activity not recognized" + "Activity not recognized - $detectedActivity" ) } var title = context.getText(notificationContent.title) From e30df0c13539d954830adfbc9207d0d09d927a1c Mon Sep 17 00:00:00 2001 From: Javinator9889 Date: Sat, 27 Jun 2020 12:32:56 +0200 Subject: [PATCH 58/95] Updated ActivityHandler.kt for managing correctly activities (issue #12) - this may be working now --- app/build.gradle | 2 - app/src/main/AndroidManifest.xml | 2 - .../fragments/settings/ActivityCheckbox.kt | 3 + .../settings/ActivityMultiSelectList.kt | 89 ------------------ .../gms/activity/ActivityHandler.kt | 90 ++++++------------- .../gms/activity/ActivityReceiver.kt | 3 +- .../jobs/BootCompletedJob.kt | 7 +- app/src/main/res/values-es/strings.xml | 2 +- app/src/main/res/values/strings.xml | 4 +- app/src/main/res/xml/preferences.xml | 8 -- 10 files changed, 40 insertions(+), 170 deletions(-) delete mode 100644 app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/settings/ActivityMultiSelectList.kt diff --git a/app/build.gradle b/app/build.gradle index c9988ff..e584e45 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -193,8 +193,6 @@ dependencies { implementation 'com.squareup.okhttp3:okhttp:3.12.1' // https://square.github.io/okio/#releases implementation 'com.squareup.okio:okio:2.5.0' - // https://github.com/google/conscrypt/ -// implementation 'org.conscrypt:conscrypt-android:2.4.0' // https://github.com/deano2390/MaterialShowcaseView implementation 'com.github.deano2390:MaterialShowcaseView:1.3.4' // https://github.com/PhilJay/MPAndroidChart diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 50c40d1..6c647d4 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -39,8 +39,6 @@ android:name=".activities.PrivacyTermsActivity" /> - diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/settings/ActivityCheckbox.kt b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/settings/ActivityCheckbox.kt index b2c87ee..ec267bb 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/settings/ActivityCheckbox.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/settings/ActivityCheckbox.kt @@ -33,6 +33,7 @@ import com.javinator9889.handwashingreminder.utils.isAtLeast import com.mikepenz.iconics.IconicsDrawable import com.mikepenz.iconics.typeface.library.ionicons.Ionicons import com.mikepenz.iconics.utils.sizeDp +import timber.log.Timber class ActivityCheckbox : CheckBoxPreference { constructor(context: Context) : super(context) @@ -91,8 +92,10 @@ class ActivityCheckbox : CheckBoxPreference { } with(HandwashingApplication.instance) { if (checked) { + Timber.d("Activity is checked so starting tracking") activityHandler.startTrackingActivity() } else { + Timber.d("Activity is not checked so disable tracking") activityHandler.disableActivityTracker() } } diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/settings/ActivityMultiSelectList.kt b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/settings/ActivityMultiSelectList.kt deleted file mode 100644 index d0c67c5..0000000 --- a/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/settings/ActivityMultiSelectList.kt +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright © 2020 - present | Handwashing reminder by Javinator9889 - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see https://www.gnu.org/licenses/. - * - * Created by Javinator9889 on 16/04/20 - Handwashing reminder. - */ -package com.javinator9889.handwashingreminder.activities.views.fragments.settings - -import android.content.Context -import android.util.AttributeSet -import androidx.preference.MultiSelectListPreference -import com.javinator9889.handwashingreminder.R -import com.javinator9889.handwashingreminder.gms.activity.ActivityHandler -import com.mikepenz.iconics.IconicsDrawable -import com.mikepenz.iconics.typeface.library.ionicons.Ionicons -import com.mikepenz.iconics.utils.sizeDp -import java.util.* - -class ActivityMultiSelectList : MultiSelectListPreference { - private var isFirstCall = true - constructor(context: Context) : super(context) - constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) - constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : - super(context, attrs, defStyleAttr) - - constructor( - context: Context, attrs: AttributeSet?, defStyleAttr: Int, - defStyleRes: Int - ) : super(context, attrs, defStyleAttr, defStyleRes) - - init { - icon = IconicsDrawable(context, Ionicons.Icon.ion_android_alert).apply { - sizeDp = 20 - } - } - - override fun notifyChanged() { - super.notifyChanged() - if (!isFirstCall) { - loadSummary() - reloadActivityHandler() - } - } - - override fun onSetInitialValue(defaultValue: Any?) { - super.onSetInitialValue(defaultValue) - isFirstCall = false - loadSummary() - } - - private fun loadSummary() { - val builder = StringBuilder() - var isFirstItem = true - var allDisabled = true - selectedItems.forEachIndexed { i, enabled -> - if (enabled) { - allDisabled = false - if (!isFirstItem) - builder.append(", ") - else - isFirstItem = false - builder.append(entries[i].toString().toLowerCase(Locale.ROOT)) - } - } - builder.append('.') - summary = if (allDisabled) - context.getText(R.string.activities_disabled) - else - "${context.getString(R.string.activities_info)} $builder" - } - - private fun reloadActivityHandler() { - with(ActivityHandler.getInstance(context)) { - reload() - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/gms/activity/ActivityHandler.kt b/app/src/main/java/com/javinator9889/handwashingreminder/gms/activity/ActivityHandler.kt index 1249f41..5420415 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/gms/activity/ActivityHandler.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/gms/activity/ActivityHandler.kt @@ -22,49 +22,57 @@ import android.app.PendingIntent import android.app.PendingIntent.FLAG_UPDATE_CURRENT import android.content.Context import android.content.Intent -import android.content.IntentFilter -import androidx.localbroadcastmanager.content.LocalBroadcastManager -import androidx.preference.PreferenceManager import com.google.android.gms.location.ActivityRecognition import com.google.android.gms.location.ActivityTransition +import com.google.android.gms.location.ActivityTransition.ACTIVITY_TRANSITION_EXIT import com.google.android.gms.location.ActivityTransitionRequest +import com.google.android.gms.location.DetectedActivity import com.google.android.gms.tasks.Task import com.javinator9889.handwashingreminder.BuildConfig -import com.javinator9889.handwashingreminder.utils.Preferences import timber.log.Timber internal const val ACTIVITY_REQUEST_CODE = 64 internal const val TRANSITIONS_RECEIVER_ACTION = "${BuildConfig.APPLICATION_ID}/TRANSITIONS_RECEIVER_ACTION" +internal val TRANSITIONS = listOf( + ActivityTransition.Builder() + .setActivityType(DetectedActivity.IN_VEHICLE) + .setActivityTransition(ACTIVITY_TRANSITION_EXIT) + .build(), + ActivityTransition.Builder() + .setActivityType(DetectedActivity.ON_BICYCLE) + .setActivityTransition(ACTIVITY_TRANSITION_EXIT) + .build(), + ActivityTransition.Builder() + .setActivityType(DetectedActivity.RUNNING) + .setActivityTransition(ACTIVITY_TRANSITION_EXIT) + .build(), + ActivityTransition.Builder() + .setActivityType(DetectedActivity.WALKING) + .setActivityTransition(ACTIVITY_TRANSITION_EXIT) + .build() +) class ActivityHandler private constructor(private val context: Context) { - private val transitions: MutableList = mutableListOf() - private var pendingIntent: PendingIntent + private var pendingIntent: PendingIntent = createPendingIntent() private var activityRegistered = false - private val transitionsReceiver = ActivityReceiver() - - init { - val activitiesSet = createSetOfTransitions() - addTransitions(activitiesSet, transitions) - registerActivityReceiver() - pendingIntent = createPendingIntent() - } companion object { private var instance: ActivityHandler? = null fun getInstance(context: Context): ActivityHandler { instance?.let { return it } - instance = ActivityHandler(context) - return instance!! + synchronized(this) { + val instance = ActivityHandler(context.applicationContext) + this.instance = instance + return instance + } } } fun startTrackingActivity() { - if (transitions.size == 0) - return Timber.d("Starting activity recognition") - with(ActivityTransitionRequest(transitions)) { + with(ActivityTransitionRequest(TRANSITIONS)) { ActivityRecognition.getClient(context) .requestActivityTransitionUpdates(this, pendingIntent).apply { addOnSuccessListener { activityRegistered = true } @@ -86,46 +94,9 @@ class ActivityHandler private constructor(private val context: Context) { } } - fun reload() = with(createSetOfTransitions()) { - transitions.clear() - addTransitions(this, transitions) - Timber.d("Reloading activity recognition - transitions: $transitions") - disableActivityTracker()?.let { - it.addOnCompleteListener { - pendingIntent = createPendingIntent() - startTrackingActivity() - } - } - } - - private fun createSetOfTransitions(): Set { - val preferences = PreferenceManager.getDefaultSharedPreferences(context) - with(mutableSetOf()) { - preferences.getStringSet( - Preferences.ACTIVITIES_ENABLED, - Preferences.DEFAULT_ACTIVITY_SET - )!!.run { - forEach { this@with += Integer.parseInt(it) } - } - return this - } - } - - private fun addTransitions( - activitiesSet: Set, - transitions: MutableList, - activityTransition: Int = ActivityTransition.ACTIVITY_TRANSITION_EXIT - ) { - for (activity in activitiesSet) { - transitions += ActivityTransition.Builder() - .setActivityType(activity) - .setActivityTransition(activityTransition) - .build() - } - } - private fun createPendingIntent(): PendingIntent = with(Intent(TRANSITIONS_RECEIVER_ACTION)) { + setClass(context, ActivityReceiver::class.java) PendingIntent.getBroadcast( context, ACTIVITY_REQUEST_CODE, @@ -133,9 +104,4 @@ class ActivityHandler private constructor(private val context: Context) { FLAG_UPDATE_CURRENT ) } - - private fun registerActivityReceiver() = - LocalBroadcastManager.getInstance(context).registerReceiver( - transitionsReceiver, IntentFilter(TRANSITIONS_RECEIVER_ACTION) - ) } \ No newline at end of file diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/gms/activity/ActivityReceiver.kt b/app/src/main/java/com/javinator9889/handwashingreminder/gms/activity/ActivityReceiver.kt index 31450aa..7ff9776 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/gms/activity/ActivityReceiver.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/gms/activity/ActivityReceiver.kt @@ -111,8 +111,7 @@ class ActivityReceiver : BroadcastReceiver() { val emojiCompat = emojiLoader.await() title = emojiCompat.process(title) content = emojiCompat.process(content) - } catch (_: IllegalStateException) { - } + } catch (_: IllegalStateException) { } withContext(Dispatchers.Main) { notificationsHandler.createNotification( iconDrawable = R.drawable.ic_stat_handwashing, diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/jobs/BootCompletedJob.kt b/app/src/main/java/com/javinator9889/handwashingreminder/jobs/BootCompletedJob.kt index 326a904..b0ecbe3 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/jobs/BootCompletedJob.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/jobs/BootCompletedJob.kt @@ -36,10 +36,13 @@ class BootCompletedJob : BroadcastReceiver() { Preferences.ACTIVITY_TRACKING_ENABLED, false ) - ) + ) { + Timber.d("Device rebooted so starting activity as it's enabled") activityHandler.startTrackingActivity() - else + } else { + Timber.d("Device rebooted but not starting activity as it isn't enabled") activityHandler.disableActivityTracker() + } Timber.d("Enqueuing notifications as the device has rebooted") with(AlarmHandler(context)) { scheduleAllAlarms() diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 2d52dce..71cb93a 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -154,7 +154,7 @@ No recibirás notificaciones de ninguna actividad Recibirás notificaciones de las - actividades seleccionadas debajo + actividades después de pasear, ir en bici, bajarte de un vehículo o correr
Habilitar reconocimiento de la actividad ¿Cuándo recibirás notificaciones? diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index d986c7f..e702c2e 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -156,8 +156,8 @@ the access to your activity. Enable it from settings
You will not receive any activity notifications - You will receive notifications for - the activities selected below + You will receive notifications after having + a walk, going cycling, leaving a vehicle or running Enable activity recognition When you will receive notifications? diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml index 7d72fd0..6d6758a 100644 --- a/app/src/main/res/xml/preferences.xml +++ b/app/src/main/res/xml/preferences.xml @@ -57,14 +57,6 @@ android:key="activity:gms:tracking" android:title="@string/activity_recognition" app:summary="For enabling the activity recognition, you need both Google Play Services and permissions" /> - - From c1322637b2c8fda1e0cf690b5da2d470b8e58e36 Mon Sep 17 00:00:00 2001 From: Javinator9889 Date: Sat, 27 Jun 2020 13:04:21 +0200 Subject: [PATCH 59/95] Created IntentFilter for ActivityReceiver.kt Broadcast (issue #12) - activity recognition notifications should be working --- app/src/main/AndroidManifest.xml | 6 +- .../gms/activity/ActivityHandler.kt | 6 +- .../gms/activity/ActivityReceiver.kt | 56 ++++++++++--------- 3 files changed, 37 insertions(+), 31 deletions(-) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 6c647d4..19a171c 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -49,7 +49,11 @@ + android:exported="true"> + + + + ( ActivityTransition.Builder() .setActivityType(DetectedActivity.IN_VEHICLE) @@ -95,8 +95,8 @@ class ActivityHandler private constructor(private val context: Context) { } private fun createPendingIntent(): PendingIntent = - with(Intent(TRANSITIONS_RECEIVER_ACTION)) { - setClass(context, ActivityReceiver::class.java) + with(Intent(context, ActivityReceiver::class.java)) { + action = TRANSITIONS_RECEIVER_ACTION PendingIntent.getBroadcast( context, ACTIVITY_REQUEST_CODE, diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/gms/activity/ActivityReceiver.kt b/app/src/main/java/com/javinator9889/handwashingreminder/gms/activity/ActivityReceiver.kt index 7ff9776..745a03e 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/gms/activity/ActivityReceiver.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/gms/activity/ActivityReceiver.kt @@ -21,7 +21,6 @@ package com.javinator9889.handwashingreminder.gms.activity import android.content.BroadcastReceiver import android.content.Context import android.content.Intent -import android.text.TextUtils import androidx.annotation.StringRes import androidx.emoji.text.EmojiCompat import com.google.android.gms.location.ActivityTransition @@ -35,42 +34,45 @@ import com.javinator9889.handwashingreminder.utils.goAsync import kotlinx.coroutines.Deferred import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext +import timber.log.Timber class ActivityReceiver : BroadcastReceiver() { /** * {@inheritDoc} */ override fun onReceive(context: Context, intent: Intent) { - if (ActivityTransitionResult.hasResult(intent) - && TextUtils.equals(TRANSITIONS_RECEIVER_ACTION, intent.action)) { + Timber.d("Detected user activity - ${intent.action}") + if (ActivityTransitionResult.hasResult(intent)) { val emojiLoader = EmojiLoader.loadAsync(context) - val result = ActivityTransitionResult.extractResult(intent)!! - for (event in result.transitionEvents) { - if (event.transitionType != - ActivityTransition.ACTIVITY_TRANSITION_EXIT || - event.activityType == DetectedActivity.UNKNOWN - ) - continue - val notificationHandler = NotificationsHandler( - context, - ACTIVITY_CHANNEL_ID, - context.getString( - R.string.activity_notification_channel_name - ), - context.getString( - R.string.activity_notification_channel_desc + val result = ActivityTransitionResult.extractResult(intent) + result?.let { + for (event in result.transitionEvents) { + if (event.transitionType != + ActivityTransition.ACTIVITY_TRANSITION_EXIT || + event.activityType == DetectedActivity.UNKNOWN ) - ) - goAsync { - putNotification( - notificationHandler, - emojiLoader, - event.activityType, - context + continue + val notificationHandler = NotificationsHandler( + context, + ACTIVITY_CHANNEL_ID, + context.getString( + R.string.activity_notification_channel_name + ), + context.getString( + R.string.activity_notification_channel_desc + ) ) + goAsync { + putNotification( + notificationHandler, + emojiLoader, + event.activityType, + context + ) + } + break } - break - } + } ?: Timber.w("Received unmatched activity - $intent") } } From de1ca2beec9321518b69d55b6bcb40ff6ea0f6c2 Mon Sep 17 00:00:00 2001 From: Javinator9889 Date: Sat, 27 Jun 2020 13:41:34 +0200 Subject: [PATCH 60/95] Updated main view model for better readability (issue #13) --- .../activities/LauncherActivity.kt | 15 ++++------ .../activities/MainActivity.kt | 2 ++ .../fragments/diseases/DiseasesFragment.kt | 6 ---- .../data/SettingsLoader.kt | 15 +++++++--- .../main/res/drawable/shadowed_divider.xml | 2 +- app/src/main/res/layout/main_disease_view.xml | 29 +++++++++++++------ app/src/main/res/layout/splash_screen.xml | 19 +++++++++++- app/src/main/res/values-es/strings.xml | 1 + app/src/main/res/values/strings.xml | 1 + 9 files changed, 60 insertions(+), 30 deletions(-) diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/activities/LauncherActivity.kt b/app/src/main/java/com/javinator9889/handwashingreminder/activities/LauncherActivity.kt index 8698296..ea77137 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/activities/LauncherActivity.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/activities/LauncherActivity.kt @@ -96,6 +96,7 @@ class LauncherActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.splash_screen) + progressBar.show() } private suspend fun displayWelcomeScreen() { @@ -179,11 +180,12 @@ class LauncherActivity : AppCompatActivity() { override fun finish() { Timber.d("Calling finish") + progressBar.hide() super.finish() } private fun installRequiredModules() { - val modules = ArrayList(MODULE_COUNT) + val modules = mutableListOf() val googleApi = GoogleApiAvailability.getInstance() if (sharedPreferences.getBoolean(ADS_ENABLED, true)) modules += Ads.MODULE_NAME @@ -201,7 +203,6 @@ class LauncherActivity : AppCompatActivity() { with(SplitInstallManagerFactory.create(this)) { deferredUninstall(listOf(BundledEmoji.MODULE_NAME)) } - modules.trimToSize() val intent = if (launchOnInstall) { createDynamicFeatureActivityIntent( modules.toTypedArray(), @@ -246,19 +247,15 @@ class LauncherActivity : AppCompatActivity() { Timber.d("Firebase initialized correctly") Timber.d("Initializing Iconics") Iconics.init(this) -// Timber.d("Setting-up security providers") -// Security.insertProviderAt(Conscrypt.newProvider(), 1) Timber.d("Setting-up activity recognition") val activityHandler = ActivityHandler.getInstance(this) if (sharedPreferences.getBoolean( - Preferences.ACTIVITY_TRACKING_ENABLED, false - ) && with(GoogleApiAvailability.getInstance()) { - isGooglePlayServicesAvailable(this@LauncherActivity) == - ConnectionResult.SUCCESS - } + Preferences.ACTIVITY_TRACKING_ENABLED, false) ) { + Timber.d("Tracking is enabled and Play Services are available so starting tracking") activityHandler.startTrackingActivity() } else { + Timber.d("Tracking is not enabled or Play Services are not available so starting tracking") activityHandler.disableActivityTracker() } with(AlarmHandler(this)) { diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/activities/MainActivity.kt b/app/src/main/java/com/javinator9889/handwashingreminder/activities/MainActivity.kt index 59c306d..7a5b0b6 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/activities/MainActivity.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/activities/MainActivity.kt @@ -117,6 +117,8 @@ class MainActivity : ActionBarBase(), override fun finish() { try { Auth.logout() + } catch (e: IllegalStateException) { + Timber.w(e, "Auth client was not initialized") } finally { super.finish() } diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/diseases/DiseasesFragment.kt b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/diseases/DiseasesFragment.kt index c6d69c0..dbc255a 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/diseases/DiseasesFragment.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/diseases/DiseasesFragment.kt @@ -24,7 +24,6 @@ import android.view.View import android.widget.LinearLayout import androidx.annotation.StringRes import androidx.core.app.ActivityCompat -import androidx.core.view.ViewCompat import androidx.emoji.text.EmojiCompat import androidx.fragment.app.activityViewModels import androidx.fragment.app.viewModels @@ -85,7 +84,6 @@ class DiseasesFragment : BaseFragmentView(), LayoutVisibilityChange { loading.visibility = View.VISIBLE countLoader.visibility = View.VISIBLE informationViewModel.parsedHTMLText.observe(viewLifecycleOwner) { - Timber.d("Parsed HTML text changed - $it | ${it.isEmpty()}") if (it.isEmpty()) return@observe parsedHTMLTexts = it @@ -158,10 +156,6 @@ class DiseasesFragment : BaseFragmentView(), LayoutVisibilityChange { view.countChart.axisRight.setDrawGridLines(false) view.countChart.xAxis.setDrawGridLines(false) view.countChart.invalidate() - ViewCompat.setElevation( - view.contentLayout, - resources.getDimension(R.dimen.menu_elevation) - ) view.countUpButton.setOnClickListener { lifecycleScope.launch { val createdItem = diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/data/SettingsLoader.kt b/app/src/main/java/com/javinator9889/handwashingreminder/data/SettingsLoader.kt index e24be12..9120a58 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/data/SettingsLoader.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/data/SettingsLoader.kt @@ -36,6 +36,7 @@ import com.javinator9889.handwashingreminder.activities.PrivacyTermsActivity import com.javinator9889.handwashingreminder.activities.views.fragments.settings.SettingsView import com.javinator9889.handwashingreminder.activities.views.fragments.settings.TimePickerPreference import com.javinator9889.handwashingreminder.emoji.EmojiLoader +import com.javinator9889.handwashingreminder.jobs.alarms.Alarms import com.javinator9889.handwashingreminder.utils.* import com.mikepenz.aboutlibraries.LibsBuilder import com.mikepenz.iconics.IconicsDrawable @@ -257,7 +258,8 @@ class SettingsLoader( onInitialized = ::setupTimePickerDialog, onInitializedArgs = setOf( "title" to getText(R.string.breakfast_pref_title), - "summary" to getText(R.string.breakfast_pref_summ) + "summary" to getText(R.string.breakfast_pref_summ), + "alarm" to Alarms.BREAKFAST_ALARM ), dispatcher = Dispatchers.Main ).also { deferreds.add(it) } @@ -267,7 +269,8 @@ class SettingsLoader( onInitialized = ::setupTimePickerDialog, onInitializedArgs = setOf( "title" to getText(R.string.lunch_pref_title), - "summary" to getText(R.string.lunch_pref_summ) + "summary" to getText(R.string.lunch_pref_summ), + "alarm" to Alarms.LUNCH_ALARM ), dispatcher = Dispatchers.Main ).also { deferreds.add(it) } @@ -277,7 +280,8 @@ class SettingsLoader( onInitialized = ::setupTimePickerDialog, onInitializedArgs = setOf( "title" to getText(R.string.dinner_pref_title), - "summary" to getText(R.string.dinner_pref_summ) + "summary" to getText(R.string.dinner_pref_summ), + "alarm" to Alarms.DINNER_ALARM ), dispatcher = Dispatchers.Main ).also { deferreds.add(it) } @@ -321,12 +325,14 @@ class SettingsLoader( return var title: CharSequence? = null var summary: CharSequence? = null + var alarm: Alarms? = null for (arg in args) when (arg.first) { "title" -> title = arg.second as CharSequence "summary" -> summary = arg.second as CharSequence + "alarm" -> alarm = arg.second as Alarms } - if (title == null || summary == null) + if (title == null || summary == null || alarm == null) return if (!::emojiCompat.isInitialized) emojiCompat = emojiLoader.await() @@ -338,6 +344,7 @@ class SettingsLoader( preference.title = title preference.summaryText = summary } finally { + preference.alarm = alarm preference.updateSummary() } } diff --git a/app/src/main/res/drawable/shadowed_divider.xml b/app/src/main/res/drawable/shadowed_divider.xml index e13f54f..c7c3a90 100644 --- a/app/src/main/res/drawable/shadowed_divider.xml +++ b/app/src/main/res/drawable/shadowed_divider.xml @@ -7,7 +7,7 @@ - + diff --git a/app/src/main/res/layout/main_disease_view.xml b/app/src/main/res/layout/main_disease_view.xml index e5e2822..cc32275 100644 --- a/app/src/main/res/layout/main_disease_view.xml +++ b/app/src/main/res/layout/main_disease_view.xml @@ -4,16 +4,16 @@ android:id="@+id/coordinatorLayout" android:layout_width="match_parent" android:layout_height="match_parent" - android:clipToPadding="false" - android:animateLayoutChanges="true"> + android:animateLayoutChanges="true" + android:clipToPadding="false"> + android:orientation="vertical"> @@ -23,22 +23,22 @@ android:id="@+id/contentLayout" android:layout_width="match_parent" android:layout_height="match_parent" - android:orientation="vertical" + android:layout_above="@+id/handwashingLayout" android:background="@android:color/white" android:clipToPadding="false" - android:layout_above="@+id/handwashingLayout" - app:behavior_peekHeight="32dp" + android:orientation="vertical" + app:behavior_peekHeight="65dp" app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior"> @@ -47,6 +47,17 @@ android:layout_height="4dp" android:background="@drawable/divider_shape" /> + + + + \ No newline at end of file diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 71cb93a..3f658f5 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -277,4 +277,5 @@ ¡Añade otra más! 🙌 Bueno, quizás una menos 😅 + Información de enfermedades diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index e702c2e..aedd584 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -305,4 +305,5 @@ Add another! 🙌 Well, maybe one less 😅 + Diseases information From 128f9972eb5b11079773ae9d351769ae1986cd69 Mon Sep 17 00:00:00 2001 From: Javinator9889 Date: Sat, 27 Jun 2020 14:05:14 +0200 Subject: [PATCH 61/95] Updated SettingsView.kt moving logic inside SettingsLoader.kt for avoiding uninitialized properties error --- .../views/fragments/settings/SettingsView.kt | 160 +----------------- .../data/SettingsLoader.kt | 156 ++++++++++++++++- 2 files changed, 156 insertions(+), 160 deletions(-) diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/settings/SettingsView.kt b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/settings/SettingsView.kt index 3023a61..e798545 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/settings/SettingsView.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/settings/SettingsView.kt @@ -20,30 +20,17 @@ package com.javinator9889.handwashingreminder.activities.views.fragments.setting import android.os.Bundle import android.view.View -import androidx.emoji.text.EmojiCompat import androidx.preference.ListPreference import androidx.preference.Preference import androidx.preference.PreferenceFragmentCompat -import androidx.preference.SwitchPreference -import com.afollestad.materialdialogs.MaterialDialog -import com.android.billingclient.api.BillingClient.BillingResponseCode -import com.google.firebase.analytics.FirebaseAnalytics -import com.google.firebase.perf.FirebasePerformance import com.javinator9889.handwashingreminder.R import com.javinator9889.handwashingreminder.activities.base.LayoutVisibilityChange -import com.javinator9889.handwashingreminder.application.HandwashingApplication import com.javinator9889.handwashingreminder.data.SettingsLoader -import com.javinator9889.handwashingreminder.gms.ads.AdsEnabler -import com.javinator9889.handwashingreminder.gms.splitservice.SplitInstallService import com.javinator9889.handwashingreminder.gms.vendor.BillingService -import com.javinator9889.handwashingreminder.listeners.OnPurchaseFinishedListener -import com.javinator9889.handwashingreminder.utils.Ads -import com.javinator9889.handwashingreminder.utils.isConnected -import timber.log.Timber import java.lang.ref.WeakReference class SettingsView : PreferenceFragmentCompat(), - Preference.OnPreferenceChangeListener, OnPurchaseFinishedListener, + Preference.OnPreferenceChangeListener, LayoutVisibilityChange { lateinit var firebaseAnalyticsPreference: WeakReference @@ -51,10 +38,8 @@ class SettingsView : PreferenceFragmentCompat(), WeakReference lateinit var adsPreference: WeakReference lateinit var donationsPreference: WeakReference - private lateinit var emojiCompat: EmojiCompat lateinit var billingService: BillingService private val loader = SettingsLoader(view = this, lifecycleOwner = this) - private val app = HandwashingApplication.instance override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -76,148 +61,7 @@ class SettingsView : PreferenceFragmentCompat(), override fun onPreferenceChange( preference: Preference?, newValue: Any? - ): Boolean { - return when { - ::firebaseAnalyticsPreference.isInitialized && - preference == firebaseAnalyticsPreference.get() -> { - val enabled = newValue as Boolean - with(FirebaseAnalytics.getInstance(requireContext())) { - setAnalyticsCollectionEnabled(enabled) - if (!enabled) - setCurrentScreen(requireActivity(), null, null) - } - true - } - ::firebasePerformancePreference.isInitialized && - preference == firebasePerformancePreference.get() -> { - val enabled = newValue as Boolean - with(FirebasePerformance.getInstance()) { - isPerformanceCollectionEnabled = enabled - } - true - } - ::adsPreference.isInitialized && - preference == adsPreference.get() -> { - val enabled = newValue as Boolean - var ret = false - val adEnabler = AdsEnabler(app) - if (enabled) { - adEnabler.enableAds() - with(SplitInstallService.getInstance(app)) { - deferredInstall(Ads.MODULE_NAME) - } - ret = true - } else { - MaterialDialog(requireContext()).show { - title(R.string.ads_explanation_title) - try { - message( - text = emojiCompat.process( - context.getText(R.string.ads_explanation_desc) - ) - ) - } catch (_: IllegalStateException) { - message(R.string.ads_explanation_desc) - } - positiveButton(android.R.string.cancel) { - ret = false - } - negativeButton(R.string.disable) { - ret = true - adEnabler.disableAds() - app.adLoader = null - with(SplitInstallService.getInstance(app)) { - deferredUninstall(Ads.MODULE_NAME) - } - (preference as SwitchPreference).isChecked = - false - } - cancelOnTouchOutside(false) - cancelable(false) - } - } - ret - } - ::donationsPreference.isInitialized && - preference == donationsPreference.get() -> { - Timber.d("Purchase clicked - $newValue") - val purchaseId = newValue as String - if (isConnected()) - billingService.doPurchase(purchaseId, requireActivity()) - else { - if (context == null) - return false - MaterialDialog(requireContext()).show { - title(R.string.no_internet_connection) - message(R.string.no_internet_connection_long) - positiveButton(android.R.string.ok) - cancelable(true) - cancelOnTouchOutside(true) - } - } - false - } - else -> true - } - } - - override fun onPurchaseFinished(token: String, resultCode: Int) { - if (context == null) - return - val context = requireContext() - when (resultCode) { - BillingResponseCode.OK -> { - try { - MaterialDialog(context) - .title(R.string.donation_thanks) - .message( - text = emojiCompat - .process(context.getText(R.string.donation_desc)) - ) - .positiveButton(android.R.string.ok) - } catch (_: IllegalStateException) { - MaterialDialog(context) - .title(R.string.donation_thanks) - .message(R.string.donation_desc) - .positiveButton(android.R.string.ok) - } - } - BillingResponseCode.USER_CANCELED -> { - try { - MaterialDialog(context) - .title(R.string.donation_cancelled) - .message( - text = emojiCompat.process( - context.getText(R.string.donation_cancelled_desc) - ) - ) - .positiveButton(android.R.string.ok) - } catch (_: IllegalStateException) { - MaterialDialog(context) - .title(R.string.donation_cancelled) - .message(R.string.donation_cancelled_desc) - .positiveButton(android.R.string.ok) - } - } - else -> { - try { - MaterialDialog(context) - .title(R.string.donation_error) - .message( - text = emojiCompat.process( - context.getText(R.string.donation_error_desc) - ) - ) - .positiveButton(android.R.string.ok) - } catch (_: IllegalStateException) { - MaterialDialog(context) - .title(R.string.donation_error) - .message(R.string.donation_error_desc) - .positiveButton(android.R.string.ok) - } - } - }.show() - } + ): Boolean = loader.onPreferenceChange(preference, newValue) override fun onVisibilityChanged(visibility: Int) { if (visibility == View.VISIBLE) diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/data/SettingsLoader.kt b/app/src/main/java/com/javinator9889/handwashingreminder/data/SettingsLoader.kt index 9120a58..d532594 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/data/SettingsLoader.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/data/SettingsLoader.kt @@ -29,14 +29,21 @@ import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.lifecycleScope import androidx.preference.ListPreference import androidx.preference.Preference +import androidx.preference.SwitchPreference import com.afollestad.materialdialogs.MaterialDialog +import com.android.billingclient.api.BillingClient import com.google.firebase.analytics.FirebaseAnalytics +import com.google.firebase.perf.FirebasePerformance import com.javinator9889.handwashingreminder.R import com.javinator9889.handwashingreminder.activities.PrivacyTermsActivity import com.javinator9889.handwashingreminder.activities.views.fragments.settings.SettingsView import com.javinator9889.handwashingreminder.activities.views.fragments.settings.TimePickerPreference +import com.javinator9889.handwashingreminder.application.HandwashingApplication import com.javinator9889.handwashingreminder.emoji.EmojiLoader +import com.javinator9889.handwashingreminder.gms.ads.AdsEnabler +import com.javinator9889.handwashingreminder.gms.splitservice.SplitInstallService import com.javinator9889.handwashingreminder.jobs.alarms.Alarms +import com.javinator9889.handwashingreminder.listeners.OnPurchaseFinishedListener import com.javinator9889.handwashingreminder.utils.* import com.mikepenz.aboutlibraries.LibsBuilder import com.mikepenz.iconics.IconicsDrawable @@ -44,13 +51,14 @@ import com.mikepenz.iconics.typeface.IIcon import com.mikepenz.iconics.typeface.library.ionicons.Ionicons import com.mikepenz.iconics.utils.sizeDp import kotlinx.coroutines.* +import timber.log.Timber import java.lang.ref.WeakReference import java.util.concurrent.atomic.AtomicBoolean class SettingsLoader( private val view: SettingsView, private val lifecycleOwner: LifecycleOwner -) { +) : OnPurchaseFinishedListener, Preference.OnPreferenceChangeListener { private lateinit var emojiLoader: Deferred private lateinit var emojiCompat: EmojiCompat private var arePreferencesInitialized = AtomicBoolean(false) @@ -170,7 +178,7 @@ class SettingsLoader( requireContext().resources.getTextArray(R.array.in_app_donations_debug) else requireContext().resources.getTextArray(R.array.in_app_donations) - billingService.addOnPurchaseFinishedListener(this@with) + billingService.addOnPurchaseFinishedListener(this@SettingsLoader) donationsPreference = WeakReference(it) } ).also { deferreds.add(it) } @@ -291,6 +299,66 @@ class SettingsLoader( } } + override fun onPurchaseFinished(token: String, resultCode: Int) { + if (view.context == null) + return + val context = view.requireContext() + if (!::emojiCompat.isInitialized) + runBlocking { emojiCompat = emojiLoader.await() } + when (resultCode) { + BillingClient.BillingResponseCode.OK -> { + try { + MaterialDialog(context) + .title(R.string.donation_thanks) + .message( + text = emojiCompat + .process(context.getText(R.string.donation_desc)) + ) + .positiveButton(android.R.string.ok) + } catch (_: IllegalStateException) { + MaterialDialog(context) + .title(R.string.donation_thanks) + .message(R.string.donation_desc) + .positiveButton(android.R.string.ok) + } + } + BillingClient.BillingResponseCode.USER_CANCELED -> { + try { + MaterialDialog(context) + .title(R.string.donation_cancelled) + .message( + text = emojiCompat.process( + context.getText(R.string.donation_cancelled_desc) + ) + ) + .positiveButton(android.R.string.ok) + } catch (_: IllegalStateException) { + MaterialDialog(context) + .title(R.string.donation_cancelled) + .message(R.string.donation_cancelled_desc) + .positiveButton(android.R.string.ok) + } + } + else -> { + try { + MaterialDialog(context) + .title(R.string.donation_error) + .message( + text = emojiCompat.process( + context.getText(R.string.donation_error_desc) + ) + ) + .positiveButton(android.R.string.ok) + } catch (_: IllegalStateException) { + MaterialDialog(context) + .title(R.string.donation_error) + .message(R.string.donation_error_desc) + .positiveButton(android.R.string.ok) + } + } + }.show() + } + private fun setupPreferenceAsync( name: String, icon: IIcon? = null, @@ -381,4 +449,88 @@ class SettingsLoader( } } } + + override fun onPreferenceChange( + preference: Preference?, + newValue: Any? + ): Boolean = + when (preference) { + view.firebaseAnalyticsPreference.get() -> { + val enabled = newValue as Boolean + with(FirebaseAnalytics.getInstance(view.requireContext())) { + setAnalyticsCollectionEnabled(enabled) + if (!enabled) + setCurrentScreen(view.requireActivity(), null, null) + } + true + } + view.firebasePerformancePreference.get() -> { + val enabled = newValue as Boolean + with(FirebasePerformance.getInstance()) { + isPerformanceCollectionEnabled = enabled + } + true + } + view.adsPreference.get() -> { + val enabled = newValue as Boolean + var ret = false + val adEnabler = AdsEnabler(HandwashingApplication.instance) + if (enabled) { + adEnabler.enableAds() + with(SplitInstallService.getInstance(view.requireContext())) { + deferredInstall(Ads.MODULE_NAME) + } + ret = true + } else { + MaterialDialog(view.requireContext()).show { + title(R.string.ads_explanation_title) + try { + message( + text = emojiCompat.process( + context.getText(R.string.ads_explanation_desc) + ) + ) + } catch (_: IllegalStateException) { + message(R.string.ads_explanation_desc) + } + positiveButton(android.R.string.cancel) { + ret = false + } + negativeButton(R.string.disable) { + ret = true + adEnabler.disableAds() + HandwashingApplication.instance.adLoader = null + with(SplitInstallService.getInstance(view.context)) { + deferredUninstall(Ads.MODULE_NAME) + } + (preference as SwitchPreference).isChecked = + false + } + cancelOnTouchOutside(false) + cancelable(false) + } + } + ret + } + view.donationsPreference.get() -> { + Timber.d("Purchase clicked - $newValue") + val purchaseId = newValue as String + if (isConnected()) + view.billingService.doPurchase( + purchaseId, + view.requireActivity() + ) + else { + MaterialDialog(view.requireContext()).show { + title(R.string.no_internet_connection) + message(R.string.no_internet_connection_long) + positiveButton(android.R.string.ok) + cancelable(true) + cancelOnTouchOutside(true) + } + } + false + } + else -> true + } } From 6f24e84d37aaac06d36c31abc2dfbfc336fb5dd7 Mon Sep 17 00:00:00 2001 From: Javinator9889 Date: Sat, 27 Jun 2020 14:10:23 +0200 Subject: [PATCH 62/95] Updated libraries and app version --- app/build.gradle | 10 +++++----- build.gradle | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index e584e45..3bed3f6 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -40,7 +40,7 @@ android { applicationId "com.javinator9889.handwashingreminder" minSdkVersion 17 targetSdkVersion 29 - versionCode 131 + versionCode 132 versionName "1.2.0-${gitCommitHash}" multiDexEnabled true resConfigs "en", "es" @@ -151,13 +151,13 @@ dependencies { api 'com.google.firebase:firebase-perf:19.0.7' implementation 'com.google.firebase:firebase-auth:19.3.1' // http://airbnb.io/lottie/#/android?id=getting-started - api "com.airbnb.android:lottie:3.4.1" + api 'com.airbnb.android:lottie:3.4.1' // https://firebase.google.com/docs/remote-config/use-config-android implementation 'com.google.firebase:firebase-config:19.1.4' implementation 'com.google.firebase:firebase-config-ktx:19.1.4' - // https://mvnrepository.com/artifact/androidx.emoji/emoji/1.0.0 - api 'androidx.emoji:emoji:1.0.0' - api 'androidx.emoji:emoji-appcompat:1.0.0' + // https://mvnrepository.com/artifact/androidx.emoji/emoji/ + api 'androidx.emoji:emoji:1.1.0' + api 'androidx.emoji:emoji-appcompat:1.1.0' // https://github.com/mikepenz/FastAdapter api "com.mikepenz:fastadapter:$latestFastAdapterRelease" implementation "com.mikepenz:fastadapter-extensions-scroll:$latestFastAdapterRelease" diff --git a/build.gradle b/build.gradle index 346ced4..68d64ad 100644 --- a/build.gradle +++ b/build.gradle @@ -16,7 +16,7 @@ buildscript { classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "com.mikepenz.aboutlibraries.plugin:aboutlibraries-plugin:${latestAboutLibsRelease}" classpath 'com.google.gms:google-services:4.3.3' - classpath 'com.google.firebase:firebase-crashlytics-gradle:2.1.1' + classpath 'com.google.firebase:firebase-crashlytics-gradle:2.2.0' classpath 'com.google.firebase:perf-plugin:1.3.1' // Performance Monitoring plugin // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files From a5bd9615ed83f2b842d7554aa6c4c9ac7d0c7980 Mon Sep 17 00:00:00 2001 From: Javinator9889 Date: Sat, 27 Jun 2020 14:11:54 +0200 Subject: [PATCH 63/95] Removed obsolete API --- appintro/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appintro/build.gradle b/appintro/build.gradle index fb794f2..9bfb7e5 100644 --- a/appintro/build.gradle +++ b/appintro/build.gradle @@ -3,7 +3,7 @@ apply plugin: 'kotlin-android' apply plugin: 'kotlin-android-extensions' android { - viewBinding.enabled = true + buildFeatures.viewBinding = true compileSdkVersion 29 android.defaultConfig.vectorDrawables.useSupportLibrary = true From 0bf38d526db11a080def7813bca66614394b10aa Mon Sep 17 00:00:00 2001 From: Javinator9889 Date: Sun, 28 Jun 2020 13:53:56 +0200 Subject: [PATCH 64/95] Deprecation of Node 8 environment - issue #10 can be closed --- functions/package-lock.json | 65 ++++++------------------------------- functions/package.json | 6 ++-- 2 files changed, 13 insertions(+), 58 deletions(-) diff --git a/functions/package-lock.json b/functions/package-lock.json index 1365c31..df547f1 100644 --- a/functions/package-lock.json +++ b/functions/package-lock.json @@ -314,20 +314,19 @@ } }, "@types/express": { - "version": "4.17.6", - "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.6.tgz", - "integrity": "sha512-n/mr9tZI83kd4azlPG5y997C/M4DNABK9yErhFM6hKdym4kkmd9j0vtsJyjFIwfRBxtrxZtAfGZCNRIBMFLK5w==", + "version": "4.17.3", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.3.tgz", + "integrity": "sha512-I8cGRJj3pyOLs/HndoP+25vOqhqWkAZsWMEmq1qXy/b/M3ppufecUwaK2/TVDVxcV61/iSdhykUjQQ2DLSrTdg==", "requires": { "@types/body-parser": "*", "@types/express-serve-static-core": "*", - "@types/qs": "*", "@types/serve-static": "*" } }, "@types/express-serve-static-core": { - "version": "4.17.7", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.7.tgz", - "integrity": "sha512-EMgTj/DF9qpgLXyc+Btimg+XoH7A2liE8uKul8qSmMTHCeNYzydDKFdsJskDvw42UsesCnhO63dO0Grbj8J4Dw==", + "version": "4.17.8", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.8.tgz", + "integrity": "sha512-1SJZ+R3Q/7mLkOD9ewCBDYD2k0WyZQtWYqF/2VvoNN2/uhI49J9CDN4OAm+wGMA0DbArA4ef27xl4+JwMtGggw==", "requires": { "@types/node": "*", "@types/qs": "*", @@ -1131,58 +1130,14 @@ } }, "firebase-functions": { - "version": "3.6.1", - "resolved": "https://registry.npmjs.org/firebase-functions/-/firebase-functions-3.6.1.tgz", - "integrity": "sha512-CBvlDEoFgsdm10PTHs7gRd5xBmhp+eqCqgsyqKbzmdbU3J8RYqtBWoHm2O31gjtZv6MyOWvS3oFITShzBulylQ==", + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/firebase-functions/-/firebase-functions-3.7.0.tgz", + "integrity": "sha512-+ROj2Gs2/KyM+T8jYo7AKaHynFsN49sXbgZMll3zuGa9/8oiDsXp9e1Iy2JMkFmSZg67jeYw5Ue2OSpz0XiqFQ==", "requires": { - "@types/express": "^4.17.3", + "@types/express": "4.17.3", "cors": "^2.8.5", "express": "^4.17.1", - "jsonwebtoken": "^8.5.1", "lodash": "^4.17.14" - }, - "dependencies": { - "jsonwebtoken": { - "version": "8.5.1", - "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz", - "integrity": "sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==", - "requires": { - "jws": "^3.2.2", - "lodash.includes": "^4.3.0", - "lodash.isboolean": "^3.0.3", - "lodash.isinteger": "^4.0.4", - "lodash.isnumber": "^3.0.3", - "lodash.isplainobject": "^4.0.6", - "lodash.isstring": "^4.0.1", - "lodash.once": "^4.0.0", - "ms": "^2.1.1", - "semver": "^5.6.0" - } - }, - "jwa": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", - "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", - "requires": { - "buffer-equal-constant-time": "1.0.1", - "ecdsa-sig-formatter": "1.0.11", - "safe-buffer": "^5.0.1" - } - }, - "jws": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", - "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", - "requires": { - "jwa": "^1.4.1", - "safe-buffer": "^5.0.1" - } - }, - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" - } } }, "firebase-functions-helper": { diff --git a/functions/package.json b/functions/package.json index df0ee08..128ac18 100644 --- a/functions/package.json +++ b/functions/package.json @@ -13,19 +13,19 @@ "daemon": "cross-env-shell RUN_DAEMON=true RUNNING_LOCAL=true \"npm run build && firebase emulators:start --only functions\"" }, "engines": { - "node": "8" + "node": "10" }, "main": "lib/bin/index.js", "dependencies": { "body-parser": "^1.19.0", + "cross-env": "^7.0.2", "express": "^4.17.1", "firebase-admin": "^8.10.0", - "firebase-functions": "^3.6.1", + "firebase-functions": "^3.7.0", "firebase-functions-helper": "^0.7.5", "http-errors": "^1.7.3", "morgan": "^1.10.0", "node-fetch": "^2.6.0", - "cross-env": "^7.0.2", "pug": "^3.0.0" }, "devDependencies": { From 3665d13c3eb3b9cac9dcc49eafcc4ac0a4a09841 Mon Sep 17 00:00:00 2001 From: Javinator9889 Date: Sun, 28 Jun 2020 14:12:45 +0200 Subject: [PATCH 65/95] Updated Firebase Functions version --- functions/src/bin/daemon.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/functions/src/bin/daemon.ts b/functions/src/bin/daemon.ts index fad031d..b96e737 100644 --- a/functions/src/bin/daemon.ts +++ b/functions/src/bin/daemon.ts @@ -16,4 +16,6 @@ process.on('SIGINT', () => { .catch(err => console.warn(`Error while finishing the schedules - ${err}`)); }); -exports.updater = functions.https.onRequest((req, resp) => resp.sendStatus(200)); +exports.updater = functions.https.onRequest(async (req, resp) => { + resp.sendStatus(200); +}); From 6488933d49c53476a34854f30e3608f81bc31eb1 Mon Sep 17 00:00:00 2001 From: Javinator9889 Date: Sun, 28 Jun 2020 16:09:37 +0200 Subject: [PATCH 66/95] First approach for LauncherActivity.kt optimization --- app/src/main/AndroidManifest.xml | 6 +- .../activities/LauncherActivity.kt | 148 ++++++++++-------- .../activities/MainActivity.kt | 19 +-- .../fragments/settings/ActivityCheckbox.kt | 13 +- .../application/HandwashingApplication.kt | 9 +- .../data/MainActivityDataHandler.kt | 4 +- .../gms/activity/ActivityHandler.kt | 7 +- .../utils/threading/Task.kt | 30 ++++ bundledemoji/build.gradle | 2 +- 9 files changed, 145 insertions(+), 93 deletions(-) create mode 100644 app/src/main/java/com/javinator9889/handwashingreminder/utils/threading/Task.kt diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 19a171c..432e7ab 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -50,9 +50,9 @@ android:name=".gms.activity.ActivityReceiver" android:enabled="true" android:exported="true"> - - - + + + >() private var launchOnInstall = false private var launchFromNotification = false - private var canFinishActivity = false - private lateinit var sharedPreferences: SharedPreferences - private lateinit var app: HandwashingApplication - private lateinit var initDeferred: Deferred + private val app = HandwashingApplication.instance + private val sharedPreferences = + PreferenceManager.getDefaultSharedPreferences(app) + private val dynamicFeatureDeferred = CompletableDeferred() + private val splitInstallManager = SplitInstallManagerFactory.create(app) init { lifecycleScope.launch { whenCreated { - app = HandwashingApplication.instance - sharedPreferences = - PreferenceManager.getDefaultSharedPreferences(this@LauncherActivity) - with(intent) { - notNull { - launchFromNotification = - it.getBooleanExtra(FAST_START_KEY, false) - } - } - initDeferred = async { initVariables() } + launchFromNotification = + intent.getBooleanExtra(FAST_START_KEY, false) + deferreds.add(async { initVariables() }) } whenStarted { - try { - withContext(Dispatchers.Main) { displayWelcomeScreen() } - withContext(Dispatchers.Main) { installRequiredModules() } - } finally { - initDeferred.await() + progressBar.show() + deferreds.add(showWelcomeScreenAsync()) + deferreds.add(installRequiredModulesAsync()) + deferreds.awaitAll() + if (!launchOnInstall) { + with(Intent(this@LauncherActivity, MainActivity::class.java)) { + if (launchFromNotification) + flags = Intent.FLAG_ACTIVITY_NEW_TASK or + Intent.FLAG_ACTIVITY_CLEAR_TASK + startActivity(this) + overridePendingTransition(0, android.R.anim.fade_out) + finish() + } } } } @@ -96,39 +98,36 @@ class LauncherActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.splash_screen) - progressBar.show() } - private suspend fun displayWelcomeScreen() { + private fun showWelcomeScreenAsync() = lifecycleScope.async { app.firebaseInitDeferred.await() val isThereAnySpecialEvent = with(Firebase.remoteConfig) { getBoolean(SPECIAL_EVENT) && !launchFromNotification } var sleepDuration = 0L - var animationLoaded = false - val fadeInAnimation = - AnimationUtils.loadAnimation(this, android.R.anim.fade_in) + val animationLoaded = CompletableDeferred() + val fadeInAnimation = AnimationUtils.loadAnimation( + this@LauncherActivity, + android.R.anim.fade_in + ) fadeInAnimation.duration = 300L fadeInAnimation.setAnimationListener(object : Animation.AnimationListener { override fun onAnimationStart(animation: Animation?) {} - override fun onAnimationRepeat(animation: Animation?) {} - override fun onAnimationEnd(animation: Animation?) { + animationLoaded.complete(true) logo.playAnimation() - animationLoaded = true } }) if (isThereAnySpecialEvent) { logo.setAnimation(AnimatedResources.STAY_SAFE_STAY_HOME.res) - logo.enableMergePathsForKitKatAndAbove(true) logo.addLottieOnCompositionLoadedListener { logo.startAnimation(fadeInAnimation) sleepDuration = logo.duration } - while (!animationLoaded) - delay(10L) + animationLoaded.await() delay(sleepDuration) } else { logo.setImageResource(R.drawable.handwashing_app_logo) @@ -142,7 +141,32 @@ class LauncherActivity : AppCompatActivity() { data: Intent? ) { super.onActivityResult(requestCode, resultCode, data) - if (requestCode == DYNAMIC_FEATURE_INSTALL_RESULT_CODE) { + if (requestCode != DYNAMIC_FEATURE_INSTALL_RESULT_CODE) { + Timber.i("Unknown request code $requestCode") + return + } + if (Ads.MODULE_NAME in splitInstallManager.installedModules) { + when (resultCode) { + Activity.RESULT_OK -> { + initAds() + data?.let { + val launchIntent = Intent(data) + createPackageContext(packageName, 0).also { + SplitCompat.install(it) + } + if (launchFromNotification) { + launchIntent.flags = + Intent.FLAG_ACTIVITY_NEW_TASK or + Intent.FLAG_ACTIVITY_CLEAR_TASK + } + startActivity(launchIntent) + } + } + Activity.RESULT_CANCELED -> app.adLoader = null + } + } + dynamicFeatureDeferred.complete(true) + /*if (requestCode == DYNAMIC_FEATURE_INSTALL_RESULT_CODE) { EmojiLoader.loadAsync(this) if (sharedPreferences.getBoolean(ADS_ENABLED, true)) { when (resultCode) { @@ -175,17 +199,37 @@ class LauncherActivity : AppCompatActivity() { finish() else canFinishActivity = true - } + }*/ } override fun finish() { Timber.d("Calling finish") progressBar.hide() + runBlocking(Dispatchers.Default) { deferreds.awaitAll() } super.finish() } - private fun installRequiredModules() { - val modules = mutableListOf() + private fun installRequiredModulesAsync() = lifecycleScope.async { + val modules = loadRequiredModules() + Timber.d("Required to install modules: $modules") + if (modules.isEmpty()) + return@async + val intent = if (launchOnInstall) { + createDynamicFeatureActivityIntent( + modules.toTypedArray(), + launchOnInstall, + AppIntro.MAIN_ACTIVITY_NAME, + AppIntro.PACKAGE_NAME + ) + } else { + createDynamicFeatureActivityIntent(modules.toTypedArray()) + } + startActivityForResult(intent, DYNAMIC_FEATURE_INSTALL_RESULT_CODE) + dynamicFeatureDeferred.await() + } + + private fun loadRequiredModules(): Set { + val modules = mutableSetOf() val googleApi = GoogleApiAvailability.getInstance() if (sharedPreferences.getBoolean(ADS_ENABLED, true)) modules += Ads.MODULE_NAME @@ -199,21 +243,7 @@ class LauncherActivity : AppCompatActivity() { ) != ConnectionResult.SUCCESS ) modules += BundledEmoji.MODULE_NAME - else - with(SplitInstallManagerFactory.create(this)) { - deferredUninstall(listOf(BundledEmoji.MODULE_NAME)) - } - val intent = if (launchOnInstall) { - createDynamicFeatureActivityIntent( - modules.toTypedArray(), - launchOnInstall, - AppIntro.MAIN_ACTIVITY_NAME, - AppIntro.PACKAGE_NAME - ) - } else { - createDynamicFeatureActivityIntent(modules.toTypedArray()) - } - startActivityForResult(intent, DYNAMIC_FEATURE_INSTALL_RESULT_CODE) + return modules - splitInstallManager.installedModules } private fun initAds() { @@ -250,7 +280,8 @@ class LauncherActivity : AppCompatActivity() { Timber.d("Setting-up activity recognition") val activityHandler = ActivityHandler.getInstance(this) if (sharedPreferences.getBoolean( - Preferences.ACTIVITY_TRACKING_ENABLED, false) + Preferences.ACTIVITY_TRACKING_ENABLED, false + ) ) { Timber.d("Tracking is enabled and Play Services are available so starting tracking") activityHandler.startTrackingActivity() @@ -263,11 +294,11 @@ class LauncherActivity : AppCompatActivity() { } Timber.d("Adding periodic notifications if not enqueued yet") Timber.d("Setting-up Firebase custom properties") - setupFirebaseProperties() + setupFirebasePropertiesAsync().join() } - private fun setupFirebaseProperties() { - val firebaseAnalytics = FirebaseAnalytics.getInstance(this) + private fun setupFirebasePropertiesAsync() = lifecycleScope.launch { + val firebaseAnalytics = FirebaseAnalytics.getInstance(this@LauncherActivity) val firebaseRemoteConfig = Firebase.remoteConfig val firebasePerformance = FirebasePerformance.getInstance() val config = with(FirebaseRemoteConfigSettings.Builder()) { @@ -296,12 +327,7 @@ class LauncherActivity : AppCompatActivity() { } } ) - fetchAndActivate().addOnSuccessListener { - if (canFinishActivity) - finish() - else - canFinishActivity = true - } + fetchAndActivate().await() } firebaseAnalytics.setAnalyticsCollectionEnabled( sharedPreferences.getBoolean( diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/activities/MainActivity.kt b/app/src/main/java/com/javinator9889/handwashingreminder/activities/MainActivity.kt index 7a5b0b6..d5f91ba 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/activities/MainActivity.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/activities/MainActivity.kt @@ -25,7 +25,7 @@ import androidx.fragment.app.FragmentTransaction import androidx.fragment.app.commit import androidx.lifecycle.lifecycleScope import androidx.lifecycle.whenCreated -import androidx.lifecycle.whenStarted +import androidx.lifecycle.whenResumed import com.google.android.material.bottomnavigation.BottomNavigationView import com.google.firebase.analytics.FirebaseAnalytics import com.google.firebase.ktx.Firebase @@ -63,16 +63,16 @@ class MainActivity : ActionBarBase(), launch { dataHandler.setMenuIcons(menu, this@MainActivity) } menu.setOnNavigationItemSelectedListener(this@MainActivity) menu.setOnNavigationItemReselectedListener(this@MainActivity) - deferredShowcase = dataHandler.asyncLoadShowcase( + deferredShowcase = dataHandler.loadShowcaseAsync( activity = this@MainActivity, lifecycleOwner = this@MainActivity ) - deferredRating = dataHandler.asyncSuggestRating( + deferredRating = dataHandler.suggestRatingAsync( activity = this@MainActivity, lifecycleOwner = this@MainActivity ) } - whenStarted { + whenResumed { with(FirebaseAnalytics.getInstance(this@MainActivity)) { setCurrentScreen(this@MainActivity, "Main view", null) } @@ -93,7 +93,9 @@ class MainActivity : ActionBarBase(), override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) if (savedInstanceState == null) - dataHandler.loadFragmentView(supportFragmentManager) + lifecycleScope.launch { + dataHandler.loadFragmentView(supportFragmentManager) + } } override fun onDestroy() { @@ -118,7 +120,7 @@ class MainActivity : ActionBarBase(), try { Auth.logout() } catch (e: IllegalStateException) { - Timber.w(e, "Auth client was not initialized") + Timber.w(e, "Auth client was not initialized") } finally { super.finish() } @@ -163,11 +165,10 @@ class MainActivity : ActionBarBase(), } override fun onNavigationItemReselected(item: MenuItem) { - when (item.itemId) { - R.id.news -> with(dataHandler.activeFragment as NewsFragment) { + if (item.itemId == R.id.news) + with(dataHandler.activeFragment as NewsFragment) { goTop() } - } } private fun onItemSelected(@IdRes id: Int): Boolean { diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/settings/ActivityCheckbox.kt b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/settings/ActivityCheckbox.kt index ec267bb..a6a3be1 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/settings/ActivityCheckbox.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/settings/ActivityCheckbox.kt @@ -27,13 +27,12 @@ import androidx.preference.CheckBoxPreference import com.google.android.gms.common.ConnectionResult import com.google.android.gms.common.GoogleApiAvailability import com.javinator9889.handwashingreminder.R -import com.javinator9889.handwashingreminder.application.HandwashingApplication +import com.javinator9889.handwashingreminder.gms.activity.ActivityHandler import com.javinator9889.handwashingreminder.utils.AndroidVersion import com.javinator9889.handwashingreminder.utils.isAtLeast import com.mikepenz.iconics.IconicsDrawable import com.mikepenz.iconics.typeface.library.ionicons.Ionicons import com.mikepenz.iconics.utils.sizeDp -import timber.log.Timber class ActivityCheckbox : CheckBoxPreference { constructor(context: Context) : super(context) @@ -90,14 +89,8 @@ class ActivityCheckbox : CheckBoxPreference { firstCheck = false return } - with(HandwashingApplication.instance) { - if (checked) { - Timber.d("Activity is checked so starting tracking") - activityHandler.startTrackingActivity() - } else { - Timber.d("Activity is not checked so disable tracking") - activityHandler.disableActivityTracker() - } + with(ActivityHandler.getInstance(context)) { + if (checked) startTrackingActivity() else disableActivityTracker() } } } \ No newline at end of file diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/application/HandwashingApplication.kt b/app/src/main/java/com/javinator9889/handwashingreminder/application/HandwashingApplication.kt index f8b2eda..ab2c123 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/application/HandwashingApplication.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/application/HandwashingApplication.kt @@ -25,7 +25,6 @@ import androidx.preference.PreferenceManager import com.google.android.play.core.splitcompat.SplitCompat import com.google.firebase.FirebaseApp import com.google.firebase.crashlytics.FirebaseCrashlytics -import com.javinator9889.handwashingreminder.gms.activity.ActivityHandler import com.javinator9889.handwashingreminder.gms.ads.AdLoader import com.javinator9889.handwashingreminder.utils.LogReportTree import com.javinator9889.handwashingreminder.utils.isDebuggable @@ -38,7 +37,12 @@ import timber.log.Timber class HandwashingApplication : BaseApplication() { private val scope = CoroutineScope(Dispatchers.Default) var adLoader: AdLoader? = null - lateinit var activityHandler: ActivityHandler + set(value) = synchronized(this) { + field = value + } + get() = synchronized(this) { + field + } lateinit var firebaseInitDeferred: Deferred companion object { @@ -59,7 +63,6 @@ class HandwashingApplication : BaseApplication() { override fun onCreate() { super.onCreate() instance = this - activityHandler = ActivityHandler.getInstance(this) firebaseInitDeferred = initFirebaseAppAsync() } diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/data/MainActivityDataHandler.kt b/app/src/main/java/com/javinator9889/handwashingreminder/data/MainActivityDataHandler.kt index b28c0d0..05bddf0 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/data/MainActivityDataHandler.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/data/MainActivityDataHandler.kt @@ -141,7 +141,7 @@ class MainActivityDataHandler(@IdRes var activeFragmentId: Int = R.id.diseases) fun onHide(@IdRes id: Int) = (this[id] as LayoutVisibilityChange).onVisibilityChanged(View.INVISIBLE) - fun asyncLoadShowcase( + fun loadShowcaseAsync( activity: Activity, lifecycleOwner: LifecycleOwner ): Deferred = @@ -194,7 +194,7 @@ class MainActivityDataHandler(@IdRes var activeFragmentId: Int = R.id.diseases) } } - fun asyncSuggestRating( + fun suggestRatingAsync( activity: Activity, lifecycleOwner: LifecycleOwner ): Deferred = lifecycleOwner.lifecycleScope.async { diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/gms/activity/ActivityHandler.kt b/app/src/main/java/com/javinator9889/handwashingreminder/gms/activity/ActivityHandler.kt index 33acac1..fad1ac7 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/gms/activity/ActivityHandler.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/gms/activity/ActivityHandler.kt @@ -28,12 +28,11 @@ import com.google.android.gms.location.ActivityTransition.ACTIVITY_TRANSITION_EX import com.google.android.gms.location.ActivityTransitionRequest import com.google.android.gms.location.DetectedActivity import com.google.android.gms.tasks.Task -import com.javinator9889.handwashingreminder.BuildConfig import timber.log.Timber internal const val ACTIVITY_REQUEST_CODE = 64 -internal const val TRANSITIONS_RECEIVER_ACTION = - "${BuildConfig.APPLICATION_ID}.TRANSITIONS_RECEIVER_ACTION" +//internal const val TRANSITIONS_RECEIVER_ACTION = +// "${BuildConfig.APPLICATION_ID}.TRANSITIONS_RECEIVER_ACTION" internal val TRANSITIONS = listOf( ActivityTransition.Builder() .setActivityType(DetectedActivity.IN_VEHICLE) @@ -96,7 +95,7 @@ class ActivityHandler private constructor(private val context: Context) { private fun createPendingIntent(): PendingIntent = with(Intent(context, ActivityReceiver::class.java)) { - action = TRANSITIONS_RECEIVER_ACTION +// action = TRANSITIONS_RECEIVER_ACTION PendingIntent.getBroadcast( context, ACTIVITY_REQUEST_CODE, diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/utils/threading/Task.kt b/app/src/main/java/com/javinator9889/handwashingreminder/utils/threading/Task.kt new file mode 100644 index 0000000..5a9cff7 --- /dev/null +++ b/app/src/main/java/com/javinator9889/handwashingreminder/utils/threading/Task.kt @@ -0,0 +1,30 @@ +/* + * Copyright © 2020 - present | Handwashing reminder by Javinator9889 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see https://www.gnu.org/licenses/. + * + * Created by Javinator9889 on 28/06/20 - Handwashing reminder. + */ +package com.javinator9889.handwashingreminder.utils.threading + +import com.google.android.gms.tasks.Task +import kotlinx.coroutines.CompletableDeferred + +suspend inline fun Task.await(): T { + val taskDeferred = CompletableDeferred() + addOnSuccessListener { taskDeferred.complete(it) } + addOnCanceledListener { taskDeferred.cancel() } + addOnFailureListener { taskDeferred.completeExceptionally(it) } + return taskDeferred.await() +} \ No newline at end of file diff --git a/bundledemoji/build.gradle b/bundledemoji/build.gradle index e86abc2..6898fb8 100644 --- a/bundledemoji/build.gradle +++ b/bundledemoji/build.gradle @@ -22,7 +22,7 @@ dependencies { implementation project(':app') // https://mvnrepository.com/artifact/androidx.emoji/emoji/1.0.0 - implementation 'androidx.emoji:emoji-bundled:1.0.0' + implementation 'androidx.emoji:emoji-bundled:1.1.0' } repositories { mavenCentral() From 66c93f73b77ad5e15f98f4549151b91629e893a0 Mon Sep 17 00:00:00 2001 From: Javinator9889 Date: Sun, 28 Jun 2020 18:30:34 +0200 Subject: [PATCH 67/95] Using multiple deferreds and coroutines for loading data initially --- .../activities/DynamicFeatureProgress.kt | 13 +- .../activities/LauncherActivity.kt | 119 ++++++++---------- 2 files changed, 60 insertions(+), 72 deletions(-) diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/activities/DynamicFeatureProgress.kt b/app/src/main/java/com/javinator9889/handwashingreminder/activities/DynamicFeatureProgress.kt index d5c785e..0d5c794 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/activities/DynamicFeatureProgress.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/activities/DynamicFeatureProgress.kt @@ -97,10 +97,12 @@ class DynamicFeatureProgress : SplitCompatBaseActivity(), private fun setResultWithIntent(resultCode: Int) { val intent = if (launchOnInstall) - Intent().setClassName( - BuildConfig.APPLICATION_ID, - launchActivityName - ) + Intent().apply { + setClassName( + BuildConfig.APPLICATION_ID, + launchActivityName + ) + } else null setResult(resultCode, intent) @@ -117,7 +119,8 @@ class DynamicFeatureProgress : SplitCompatBaseActivity(), SplitInstallSessionStatus.FAILED -> { Toast.makeText( this, getString( - R.string.dynamic_module_loading_error, state.errorCode), + R.string.dynamic_module_loading_error, state.errorCode + ), Toast.LENGTH_LONG ).show() Timber.e( diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/activities/LauncherActivity.kt b/app/src/main/java/com/javinator9889/handwashingreminder/activities/LauncherActivity.kt index 8de1b4b..340e20f 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/activities/LauncherActivity.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/activities/LauncherActivity.kt @@ -37,6 +37,7 @@ import com.google.firebase.ktx.Firebase import com.google.firebase.perf.FirebasePerformance import com.google.firebase.remoteconfig.FirebaseRemoteConfigSettings import com.google.firebase.remoteconfig.ktx.remoteConfig +import com.javinator9889.handwashingreminder.BuildConfig import com.javinator9889.handwashingreminder.R import com.javinator9889.handwashingreminder.application.HandwashingApplication import com.javinator9889.handwashingreminder.data.UserProperties @@ -67,6 +68,7 @@ class LauncherActivity : AppCompatActivity() { private val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(app) private val dynamicFeatureDeferred = CompletableDeferred() + private val activityIntentDeferred = CompletableDeferred() private val splitInstallManager = SplitInstallManagerFactory.create(app) init { @@ -78,18 +80,17 @@ class LauncherActivity : AppCompatActivity() { } whenStarted { progressBar.show() - deferreds.add(showWelcomeScreenAsync()) + val welcomeScreenDeferred = showWelcomeScreenAsync() deferreds.add(installRequiredModulesAsync()) - deferreds.awaitAll() - if (!launchOnInstall) { - with(Intent(this@LauncherActivity, MainActivity::class.java)) { - if (launchFromNotification) - flags = Intent.FLAG_ACTIVITY_NEW_TASK or - Intent.FLAG_ACTIVITY_CLEAR_TASK - startActivity(this) - overridePendingTransition(0, android.R.anim.fade_out) - finish() - } + activityIntentDeferred.await().run { + Timber.d("Activity Init is now completed!") + if (launchFromNotification) + flags = Intent.FLAG_ACTIVITY_NEW_TASK or + Intent.FLAG_ACTIVITY_CLEAR_TASK + welcomeScreenDeferred.join() + startActivity(this) + overridePendingTransition(0, android.R.anim.fade_out) + finish() } } } @@ -100,7 +101,7 @@ class LauncherActivity : AppCompatActivity() { setContentView(R.layout.splash_screen) } - private fun showWelcomeScreenAsync() = lifecycleScope.async { + private fun showWelcomeScreenAsync() = lifecycleScope.launch(Dispatchers.Main) { app.firebaseInitDeferred.await() val isThereAnySpecialEvent = with(Firebase.remoteConfig) { getBoolean(SPECIAL_EVENT) && !launchFromNotification @@ -145,61 +146,30 @@ class LauncherActivity : AppCompatActivity() { Timber.i("Unknown request code $requestCode") return } - if (Ads.MODULE_NAME in splitInstallManager.installedModules) { + if (Ads.MODULE_NAME in splitInstallManager.installedModules && + sharedPreferences.getBoolean(ADS_ENABLED, true)) { when (resultCode) { - Activity.RESULT_OK -> { - initAds() - data?.let { - val launchIntent = Intent(data) - createPackageContext(packageName, 0).also { - SplitCompat.install(it) - } - if (launchFromNotification) { - launchIntent.flags = - Intent.FLAG_ACTIVITY_NEW_TASK or - Intent.FLAG_ACTIVITY_CLEAR_TASK - } - startActivity(launchIntent) - } - } + Activity.RESULT_OK -> initAds() Activity.RESULT_CANCELED -> app.adLoader = null } } - dynamicFeatureDeferred.complete(true) - /*if (requestCode == DYNAMIC_FEATURE_INSTALL_RESULT_CODE) { - EmojiLoader.loadAsync(this) - if (sharedPreferences.getBoolean(ADS_ENABLED, true)) { - when (resultCode) { - Activity.RESULT_OK -> { - initAds() - data.notNull { - createPackageContext(packageName, 0).also { - SplitCompat.install(it) - } - if (launchFromNotification) - data!!.flags = Intent.FLAG_ACTIVITY_NEW_TASK or - Intent.FLAG_ACTIVITY_CLEAR_TASK - startActivity(data) - finish() - } - } - Activity.RESULT_CANCELED -> app.adLoader = null + if (sharedPreferences.getBoolean(APP_INIT_KEY, false) && + AppIntro.MODULE_NAME in splitInstallManager.installedModules) { + data?.let { + val launchIntent = Intent(data) + createPackageContext(packageName, 0).also { + SplitCompat.install(it) } + Timber.d("Created launch intent $launchIntent") + activityIntentDeferred.complete(launchIntent) } - if (!launchOnInstall) { - Intent(this, MainActivity::class.java).also { - if (launchFromNotification) - it.flags = Intent.FLAG_ACTIVITY_NEW_TASK or - Intent.FLAG_ACTIVITY_CLEAR_TASK - startActivity(it) - overridePendingTransition(0, android.R.anim.fade_out) - } - } - if (canFinishActivity) - finish() - else - canFinishActivity = true - }*/ + } else { + Timber.d("Created launch intent at MainActivity") + activityIntentDeferred.complete( + Intent(this, MainActivity::class.java) + ) + } + dynamicFeatureDeferred.complete(true) } override fun finish() { @@ -210,11 +180,24 @@ class LauncherActivity : AppCompatActivity() { } private fun installRequiredModulesAsync() = lifecycleScope.async { - val modules = loadRequiredModules() + val (modules, installedModules) = loadRequiredModules() Timber.d("Required to install modules: $modules") - if (modules.isEmpty()) + if (modules.isEmpty()) { + val intent = if (AppIntro.MODULE_NAME in installedModules && + !sharedPreferences.getBoolean(APP_INIT_KEY, false)) { + with(Intent()) { + setClassName( + BuildConfig.APPLICATION_ID, + "${AppIntro.PACKAGE_NAME}.${AppIntro.MAIN_ACTIVITY_NAME}" + ) + this + } + } else Intent(this@LauncherActivity, MainActivity::class.java) + Timber.d("Created launch intent $intent") + activityIntentDeferred.complete(intent) return@async - val intent = if (launchOnInstall) { + } + val intent = if (AppIntro.MODULE_NAME in modules) { createDynamicFeatureActivityIntent( modules.toTypedArray(), launchOnInstall, @@ -228,7 +211,7 @@ class LauncherActivity : AppCompatActivity() { dynamicFeatureDeferred.await() } - private fun loadRequiredModules(): Set { + private fun loadRequiredModules(): Pair, Set> { val modules = mutableSetOf() val googleApi = GoogleApiAvailability.getInstance() if (sharedPreferences.getBoolean(ADS_ENABLED, true)) @@ -243,7 +226,8 @@ class LauncherActivity : AppCompatActivity() { ) != ConnectionResult.SUCCESS ) modules += BundledEmoji.MODULE_NAME - return modules - splitInstallManager.installedModules + return (modules - splitInstallManager.installedModules) to + splitInstallManager.installedModules } private fun initAds() { @@ -298,7 +282,8 @@ class LauncherActivity : AppCompatActivity() { } private fun setupFirebasePropertiesAsync() = lifecycleScope.launch { - val firebaseAnalytics = FirebaseAnalytics.getInstance(this@LauncherActivity) + val firebaseAnalytics = + FirebaseAnalytics.getInstance(this@LauncherActivity) val firebaseRemoteConfig = Firebase.remoteConfig val firebasePerformance = FirebasePerformance.getInstance() val config = with(FirebaseRemoteConfigSettings.Builder()) { From 6d2016321627a882a6131482c8a5dd1d9eb82397 Mon Sep 17 00:00:00 2001 From: Javinator9889 Date: Sun, 28 Jun 2020 19:00:19 +0200 Subject: [PATCH 68/95] Updated Ads logic (for trying to avoid resource not found errors) --- ads/src/main/AndroidManifest.xml | 1 + .../handwashingreminder/ads/AdLoaderImpl.kt | 21 ++++++++++++------- .../activities/LauncherActivity.kt | 11 ++++++++-- 3 files changed, 24 insertions(+), 9 deletions(-) diff --git a/ads/src/main/AndroidManifest.xml b/ads/src/main/AndroidManifest.xml index 8809df0..274f9c0 100644 --- a/ads/src/main/AndroidManifest.xml +++ b/ads/src/main/AndroidManifest.xml @@ -11,6 +11,7 @@ + (null) override fun instance(context: Context?): AdLoader { - val appInstance = instance.get() ?: AdLoaderImpl(context) - if (instance.get() == null) - instance = WeakReference(appInstance) - return appInstance + instance.get()?.let { return it } + synchronized(this) { + val instance = AdLoaderImpl(context?.applicationContext) + this.instance = WeakReference(instance) + return instance + } } } @@ -83,9 +90,9 @@ class AdLoaderImpl private constructor(context: Context?) : AdLoader { override fun loadAdForViewGroup(view: ViewGroup, removeAllViews: Boolean) { if (!isVideoEnded || !isConnected()) return - val adLoader = AdBase.Builder(view.context, ADMOB_APP_NATIVE_ID) + val adLoader = AdBase.Builder(moduleContext, ADMOB_APP_NATIVE_ID) .forUnifiedNativeAd { ad: UnifiedNativeAd -> - val adView = LayoutInflater.from(view.context) + val adView = LayoutInflater.from(moduleContext) .inflate(R.layout.native_ad_view, null) as CardView populateUnifiedNativeAdView(ad, adView) if (removeAllViews) diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/activities/LauncherActivity.kt b/app/src/main/java/com/javinator9889/handwashingreminder/activities/LauncherActivity.kt index 340e20f..3c878e2 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/activities/LauncherActivity.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/activities/LauncherActivity.kt @@ -19,6 +19,7 @@ package com.javinator9889.handwashingreminder.activities import android.app.Activity +import android.content.Context import android.content.Intent import android.os.Bundle import android.view.animation.Animation @@ -149,7 +150,13 @@ class LauncherActivity : AppCompatActivity() { if (Ads.MODULE_NAME in splitInstallManager.installedModules && sharedPreferences.getBoolean(ADS_ENABLED, true)) { when (resultCode) { - Activity.RESULT_OK -> initAds() + Activity.RESULT_OK -> { + createPackageContext(packageName, 0).also { + SplitCompat.install(it) + }.also { + initAds(it) + } + } Activity.RESULT_CANCELED -> app.adLoader = null } } @@ -230,7 +237,7 @@ class LauncherActivity : AppCompatActivity() { splitInstallManager.installedModules } - private fun initAds() { + private fun initAds(context: Context) { val className = "${Ads.PACKAGE_NAME}.${Ads .CLASS_NAME}\$${Ads.PROVIDER_NAME}" val adProvider = Class.forName(className).kotlin From 15728221ca0e2292d3a8e3f704e8a0d68493454a Mon Sep 17 00:00:00 2001 From: Javinator9889 Date: Mon, 29 Jun 2020 16:08:34 +0200 Subject: [PATCH 69/95] Updated Updater working method and RemoteConfig for listening to updates --- firebase.json | 27 +++++++++++++ functions/package-lock.json | 31 +++++++++++++++ functions/package.json | 1 + functions/src/bin/daemon.ts | 2 + functions/src/models/updater.ts | 11 ++++++ functions/src/rcdata.ts | 5 ++- functions/src/updater.ts | 69 ++++++++++++++++++++------------- 7 files changed, 118 insertions(+), 28 deletions(-) diff --git a/firebase.json b/firebase.json index 88da25d..b1a3ed3 100644 --- a/firebase.json +++ b/firebase.json @@ -23,5 +23,32 @@ "function": "www-webApi" } ] + }, + "firestore": { + "rules": "firestore.rules", + "indexes": "firestore.indexes.json" + }, + "emulators": { + "functions": { + "port": 5001 + }, + "firestore": { + "port": 8080 + }, + "hosting": { + "port": 5000 + }, + "pubsub": { + "port": 8085 + }, + "ui": { + "enabled": true + }, + "database": { + "port": 9000 + } + }, + "storage": { + "rules": "storage.rules" } } diff --git a/functions/package-lock.json b/functions/package-lock.json index df547f1..b486623 100644 --- a/functions/package-lock.json +++ b/functions/package-lock.json @@ -474,6 +474,14 @@ "array-filter": "^1.0.0" } }, + "axios": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.19.2.tgz", + "integrity": "sha512-fjgm5MvRHLhx+osE2xoekY70AhARk3a6hkN+3Io1jc00jtquGvxYlKlsFUhmUET0V5te6CcZI7lcv2Ym61mjHA==", + "requires": { + "follow-redirects": "1.5.10" + } + }, "babel-walk": { "version": "3.0.0-canary-5", "resolved": "https://registry.npmjs.org/babel-walk/-/babel-walk-3.0.0-canary-5.tgz", @@ -1159,6 +1167,29 @@ "lodash": "^4.17.5" } }, + "follow-redirects": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz", + "integrity": "sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==", + "requires": { + "debug": "=3.1.0" + }, + "dependencies": { + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + } + } + }, "foreach": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.5.tgz", diff --git a/functions/package.json b/functions/package.json index 128ac18..7b1e502 100644 --- a/functions/package.json +++ b/functions/package.json @@ -17,6 +17,7 @@ }, "main": "lib/bin/index.js", "dependencies": { + "axios": "^0.19.2", "body-parser": "^1.19.0", "cross-env": "^7.0.2", "express": "^4.17.1", diff --git a/functions/src/bin/daemon.ts b/functions/src/bin/daemon.ts index b96e737..8259abc 100644 --- a/functions/src/bin/daemon.ts +++ b/functions/src/bin/daemon.ts @@ -19,3 +19,5 @@ process.on('SIGINT', () => { exports.updater = functions.https.onRequest(async (req, resp) => { resp.sendStatus(200); }); + +exports.remoteConfigTrigger = functions.remoteConfig.onUpdate(updater.remoteConfigEventHandler) diff --git a/functions/src/models/updater.ts b/functions/src/models/updater.ts index d7bce2d..b4ad6ca 100644 --- a/functions/src/models/updater.ts +++ b/functions/src/models/updater.ts @@ -1,6 +1,9 @@ import {Updater} from '../updater'; import {RemoteConfigData} from "../rcdata"; import properties = require('../common/properties'); +import {TemplateVersion} from "firebase-functions/lib/providers/remoteConfig"; +import {languages} from "../common/properties"; +import {EventContext} from "firebase-functions"; const updaters: Record = {}; @@ -49,3 +52,11 @@ export async function stopScheduling() { clearInterval(timer); } } + +export async function remoteConfigEventHandler(event: TemplateVersion, _: EventContext) { + console.debug(`RemoteConfig values have changed - version ${event.versionNumber}`); + for (const language of languages) { + console.debug(`Updating search terms for language ${language}`); + remoteConfig.updaters[language].searchTerms = await remoteConfig.getSearchTermsForLanguage(language); + } +} diff --git a/functions/src/rcdata.ts b/functions/src/rcdata.ts index 86282ec..6dc3114 100644 --- a/functions/src/rcdata.ts +++ b/functions/src/rcdata.ts @@ -34,7 +34,9 @@ export class RemoteConfigData { } listenToRCChanges() { - functions.remoteConfig.onUpdate(_ => { + console.info('Listening to changes in RemoteConfig...'); + functions.remoteConfig.onUpdate((version, _) => { + console.debug(`RemoteConfig values have changed - version: ${version}`); return admin.credential.applicationDefault().getAccessToken() // tslint:disable-next-line:no-shadowed-variable .then(_ => { @@ -46,6 +48,7 @@ export class RemoteConfigData { template.parameters['search_terms'].conditionalValues[language]['value'] ); try { + console.debug("Updating updater search terms"); if (this.updaters[language].searchTerms.length !== terms.lenght) this.updaters[language].searchTerms = terms; } catch (e) { diff --git a/functions/src/updater.ts b/functions/src/updater.ts index 1716e1e..edf4dbd 100644 --- a/functions/src/updater.ts +++ b/functions/src/updater.ts @@ -1,6 +1,7 @@ import {NewsriverData} from "./newsriver"; import * as firebaseHelper from 'firebase-functions-helper'; -import * as fetch from 'node-fetch'; +import {AxiosInstance, default as axios} from "axios"; +import * as https from "https"; export class Updater { @@ -10,13 +11,14 @@ export class Updater { private _searchTerms: Array; private readonly language: string; private readonly auth: string; - private _url: string | undefined; + private _path: string | undefined; + private readonly network: AxiosInstance - get url(): Promise { - if (this._url === undefined) - return this.buildURL() - .then(url => this._url = url); - return Promise.resolve(this._url); + get path(): Promise { + if (this._path === undefined) + return this.buildPath() + .then(path => this._path = path); + return Promise.resolve(this._path); } get searchTerms(): Array { @@ -25,7 +27,7 @@ export class Updater { set searchTerms(value) { this._searchTerms = value; - this._url = undefined; + this._path = undefined; } constructor(db: FirebaseFirestore.Firestore | null, @@ -40,7 +42,18 @@ export class Updater { this.language = language; this.auth = auth; this.interval = intervalMins * 60 * 1000; - this.request() + this.network = axios.create({ + baseURL: 'https://api.newsriver.io/v2/', + headers: { + 'Authorization': this.auth, + 'Content-Type': 'application/json' + }, + withCredentials: true, + responseType: 'json', + httpsAgent: new https.Agent({ keepAlive: true }), + timeout: 500000 + }); + this.doRequest() .then(response => { this.updateData(response) // tslint:disable-next-line:no-empty @@ -53,8 +66,8 @@ export class Updater { schedule(): NodeJS.Timer { return setInterval(async () => { try { - const response = await this.request(); - await this.updateData(response); + const requestData = await this.doRequest(); + await this.updateData(requestData); } catch (e) { console.error(`Got error ${e} while querying data`); } @@ -65,11 +78,16 @@ export class Updater { try { for (const element of content) { try { - const exists = await firebaseHelper.firestore.checkDocumentExists(this.db, this.collectionName, element.id); - if (!exists) - await firebaseHelper.firestore.createDocumentWithID(this.db, this.collectionName, element.id, element); + const document = await this.db.collection(this.collectionName).doc(element.id).get(); + console.debug(`Item with id ${element.id} ${document.exists ? 'exists' : 'does not exist'}`) + if (!document.exists) + firebaseHelper.firestore.createDocumentWithID(this.db, this.collectionName, element.id, element) + .then(created => console.debug(`Item with ID: ${element.id} was ${created ? 'created' : 'not created'}`)) + .catch(err => console.error(`Error while creating document ${err}`)); else - await firebaseHelper.firestore.updateDocument(this.db, this.collectionName, element.id, element); + firebaseHelper.firestore.updateDocument(this.db, this.collectionName, element.id, element) + .then(updated => console.debug(`Item with ID ${element.id} was ${updated ? 'updated' : 'not updated'}`)) + .catch(err => console.error(`Error while updating document ${err}`)); } catch (err) { console.warn(`Error while creating/updating document - ${err}`); } @@ -80,23 +98,20 @@ export class Updater { } } - async request(): Promise> { - const requestUrl = await this.url; - const response = await fetch(requestUrl, { - method: 'GET', headers: new fetch.Headers({ - 'Authorization': this.auth, - 'Content-Type': 'application/json' - }) - }); - return await response.json() as Array; + async doRequest(): Promise> { + const response = await this.network.get(await this.path); + if (response.status === 200) + return response.data as Array; + else + throw new TypeError(`The response code is not valid - ${response.status}`); } - async buildURL(): Promise { - const parts = ['https://api.newsriver.io/v2/search?query=']; + async buildPath(): Promise { + const parts = ['/search?query='] this.searchTerms.forEach((term, i, _) => { if (i !== 0) parts.push(encodeURI(' OR ')); - parts.push(encodeURI(`title:${term} OR text:${term}`)); + parts.push(encodeURI(`title:${term} OR text:${term}`)); }); parts.push(encodeURI(` AND language:${this.language.toUpperCase()}`)); From f6842829815e6def058cd559c474b72781db39d6 Mon Sep 17 00:00:00 2001 From: Javinator9889 Date: Tue, 30 Jun 2020 10:30:06 +0200 Subject: [PATCH 70/95] Updated to version 1.0.1 --- functions/package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/functions/package.json b/functions/package.json index 7b1e502..fa57419 100644 --- a/functions/package.json +++ b/functions/package.json @@ -1,5 +1,6 @@ { "name": "functions", + "version": "1.0.1", "scripts": { "lint": "tslint --project tsconfig.json", "build": "tsc", @@ -10,10 +11,10 @@ "deploy-server": "cross-env-shell RUN_SERVER=true \"firebase deploy --only functions\"", "logs": "firebase functions:log", "server": "cross-env-shell RUN_SERVER=true RUNNING_LOCAL=true \"npm run build && firebase emulators:start --only functions\"", - "daemon": "cross-env-shell RUN_DAEMON=true RUNNING_LOCAL=true \"npm run build && firebase emulators:start --only functions\"" + "daemon": "cross-env-shell RUN_DAEMON=true RUNNING_LOCAL=true \"npm run build && firebase emulators:start --only functions,pubsub\"" }, "engines": { - "node": "10" + "node": ">=10.0.0" }, "main": "lib/bin/index.js", "dependencies": { @@ -26,7 +27,6 @@ "firebase-functions-helper": "^0.7.5", "http-errors": "^1.7.3", "morgan": "^1.10.0", - "node-fetch": "^2.6.0", "pug": "^3.0.0" }, "devDependencies": { From d752e11304c47b8ef726323b5ad6491d861a4847 Mon Sep 17 00:00:00 2001 From: Javinator9889 Date: Tue, 30 Jun 2020 10:35:37 +0200 Subject: [PATCH 71/95] Updated Updater working method and RemoteConfig for listening to updates --- functions/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/functions/package.json b/functions/package.json index fa57419..ec2d122 100644 --- a/functions/package.json +++ b/functions/package.json @@ -11,7 +11,7 @@ "deploy-server": "cross-env-shell RUN_SERVER=true \"firebase deploy --only functions\"", "logs": "firebase functions:log", "server": "cross-env-shell RUN_SERVER=true RUNNING_LOCAL=true \"npm run build && firebase emulators:start --only functions\"", - "daemon": "cross-env-shell RUN_DAEMON=true RUNNING_LOCAL=true \"npm run build && firebase emulators:start --only functions,pubsub\"" + "daemon": "cross-env-shell RUN_DAEMON=true RUNNING_LOCAL=true \"npm run build && firebase emulators:start --only functions\"" }, "engines": { "node": ">=10.0.0" From 35604f9aef01dad0d7ca1bde6e1364c9c1773e7f Mon Sep 17 00:00:00 2001 From: Javinator9889 Date: Tue, 30 Jun 2020 10:40:50 +0200 Subject: [PATCH 72/95] Updated Ads implementation for avoiding application force close when not available --- .../handwashingreminder/ads/AdLoaderImpl.kt | 31 ++++++++++--------- .../activities/LauncherActivity.kt | 30 ++++++++++-------- .../views/fragments/diseases/adapter/Ads.kt | 5 ++- .../handwashingreminder/gms/ads/AdLoader.kt | 2 +- .../handwashingreminder/gms/ads/AdsEnabler.kt | 2 +- 5 files changed, 39 insertions(+), 31 deletions(-) diff --git a/ads/src/main/java/com/javinator9889/handwashingreminder/ads/AdLoaderImpl.kt b/ads/src/main/java/com/javinator9889/handwashingreminder/ads/AdLoaderImpl.kt index 46db7d0..6a34dea 100644 --- a/ads/src/main/java/com/javinator9889/handwashingreminder/ads/AdLoaderImpl.kt +++ b/ads/src/main/java/com/javinator9889/handwashingreminder/ads/AdLoaderImpl.kt @@ -33,7 +33,6 @@ import com.google.android.gms.ads.* import com.google.android.gms.ads.formats.NativeAdOptions import com.google.android.gms.ads.formats.UnifiedNativeAd import com.google.android.gms.ads.formats.UnifiedNativeAdView -import com.google.android.play.core.splitcompat.SplitCompat import com.javinator9889.handwashingreminder.gms.ads.AdLoader import com.javinator9889.handwashingreminder.utils.isConnected import com.javinator9889.handwashingreminder.utils.isDebuggable @@ -62,8 +61,7 @@ class AdLoaderImpl private constructor(context: Context?) : AdLoader { "the first instance" ) moduleContext = context - SplitCompat.install(moduleContext) - MobileAds.initialize(moduleContext) + MobileAds.initialize(moduleContext.applicationContext) val videoOptions = VideoOptions.Builder() .setStartMuted(true) .build() @@ -79,7 +77,7 @@ class AdLoaderImpl private constructor(context: Context?) : AdLoader { override fun instance(context: Context?): AdLoader { instance.get()?.let { return it } synchronized(this) { - val instance = AdLoaderImpl(context?.applicationContext) + val instance = AdLoaderImpl(context) this.instance = WeakReference(instance) return instance } @@ -87,31 +85,34 @@ class AdLoaderImpl private constructor(context: Context?) : AdLoader { } @SuppressLint("InflateParams") - override fun loadAdForViewGroup(view: ViewGroup, removeAllViews: Boolean) { + override fun loadAdForViewGroup(view: ViewGroup, removeAllViews: Boolean) = runCatching { if (!isVideoEnded || !isConnected()) - return + throw IllegalStateException("Phone is not connected or the video didn't finished") val adLoader = AdBase.Builder(moduleContext, ADMOB_APP_NATIVE_ID) .forUnifiedNativeAd { ad: UnifiedNativeAd -> - val adView = LayoutInflater.from(moduleContext) - .inflate(R.layout.native_ad_view, null) as CardView - populateUnifiedNativeAdView(ad, adView) - if (removeAllViews) - view.removeAllViews() - view.addView(adView) + try { + val adView = LayoutInflater.from(moduleContext) + .inflate(R.layout.native_ad_view, null) as CardView + populateUnifiedNativeAdView(ad, adView) + if (removeAllViews) + view.removeAllViews() + view.addView(adView) + } catch (e: Throwable) { + Timber.w(e, "Cannot load ad in view") + } } .withNativeAdOptions(adOptions) .withAdListener(object : AdListener() { override fun onAdFailedToLoad(errorCode: Int) { when (errorCode) { AdRequest.ERROR_CODE_INVALID_REQUEST, - AdRequest.ERROR_CODE_NO_FILL -> - Timber.e("Error while loading the ad: $errorCode") + AdRequest.ERROR_CODE_NO_FILL -> throw IllegalAccessError(errorCode.toString()) else -> return } } }).build() adLoader.loadAd(AdRequest.Builder().build()) - } + }.exceptionOrNull() override fun destroy() { currentNativeAd?.destroy() diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/activities/LauncherActivity.kt b/app/src/main/java/com/javinator9889/handwashingreminder/activities/LauncherActivity.kt index 3c878e2..53e3202 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/activities/LauncherActivity.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/activities/LauncherActivity.kt @@ -81,14 +81,14 @@ class LauncherActivity : AppCompatActivity() { } whenStarted { progressBar.show() - val welcomeScreenDeferred = showWelcomeScreenAsync() + val welcomeScreenJob = showWelcomeScreenAsync() deferreds.add(installRequiredModulesAsync()) activityIntentDeferred.await().run { - Timber.d("Activity Init is now completed!") + Timber.d("Activity Init is now completed! - $this") if (launchFromNotification) flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK - welcomeScreenDeferred.join() + welcomeScreenJob.join() startActivity(this) overridePendingTransition(0, android.R.anim.fade_out) finish() @@ -153,9 +153,8 @@ class LauncherActivity : AppCompatActivity() { Activity.RESULT_OK -> { createPackageContext(packageName, 0).also { SplitCompat.install(it) - }.also { - initAds(it) } + initAds() } Activity.RESULT_CANCELED -> app.adLoader = null } @@ -237,14 +236,17 @@ class LauncherActivity : AppCompatActivity() { splitInstallManager.installedModules } - private fun initAds(context: Context) { - val className = "${Ads.PACKAGE_NAME}.${Ads - .CLASS_NAME}\$${Ads.PROVIDER_NAME}" - val adProvider = Class.forName(className).kotlin - .objectInstance as AdLoader.Provider - app.adLoader = adProvider.instance(app) - val adsEnabler = AdsEnabler(app) - adsEnabler.enableAds() + private fun initAds(context: Context = app) { + if (Ads.MODULE_NAME in splitInstallManager.installedModules && + sharedPreferences.getBoolean(ADS_ENABLED, true)) { + val className = "${Ads.PACKAGE_NAME}.${Ads + .CLASS_NAME}\$${Ads.PROVIDER_NAME}" + val adProvider = Class.forName(className).kotlin + .objectInstance as AdLoader.Provider + app.adLoader = adProvider.instance(context) + val adsEnabler = AdsEnabler(app) + adsEnabler.enableAds() + } } private fun createDynamicFeatureActivityIntent( @@ -283,6 +285,8 @@ class LauncherActivity : AppCompatActivity() { with(AlarmHandler(this)) { scheduleAllAlarms() } + Timber.d("Initializing Ads Provider") + initAds() Timber.d("Adding periodic notifications if not enqueued yet") Timber.d("Setting-up Firebase custom properties") setupFirebasePropertiesAsync().join() diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/diseases/adapter/Ads.kt b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/diseases/adapter/Ads.kt index 5849a81..6417eca 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/diseases/adapter/Ads.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/diseases/adapter/Ads.kt @@ -25,6 +25,7 @@ import com.javinator9889.handwashingreminder.application.HandwashingApplication import com.javinator9889.handwashingreminder.utils.notNull import com.mikepenz.fastadapter.FastAdapter import com.mikepenz.fastadapter.items.AbstractItem +import timber.log.Timber class Ads : AbstractItem() { override val layoutRes: Int = R.layout.simple_frame_view @@ -38,7 +39,9 @@ class Ads : AbstractItem() { override fun bindView(item: Ads, payloads: List) { ads.notNull { - it.loadAdForViewGroup(container, false) + it.loadAdForViewGroup(container, false)?.let { e -> + Timber.w(e, "Ads cannot be loaded") + } } } diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/gms/ads/AdLoader.kt b/app/src/main/java/com/javinator9889/handwashingreminder/gms/ads/AdLoader.kt index 8ef3b82..4752493 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/gms/ads/AdLoader.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/gms/ads/AdLoader.kt @@ -28,5 +28,5 @@ interface AdLoader { fun destroy() - fun loadAdForViewGroup(view: ViewGroup, removeAllViews: Boolean = true) + fun loadAdForViewGroup(view: ViewGroup, removeAllViews: Boolean = true): Throwable? } \ No newline at end of file diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/gms/ads/AdsEnabler.kt b/app/src/main/java/com/javinator9889/handwashingreminder/gms/ads/AdsEnabler.kt index dc2f780..4f031b5 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/gms/ads/AdsEnabler.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/gms/ads/AdsEnabler.kt @@ -26,7 +26,7 @@ import com.javinator9889.handwashingreminder.application.HandwashingApplication const val PACKAGE_NAME = "com.google.android.gms.ads" const val CLASS_NAME = "MobileAdsInitProvider" -class AdsEnabler(private val app: HandwashingApplication) { +class AdsEnabler(app: HandwashingApplication) { private val packageManager = app.packageManager private val componentName = ComponentName(app, "${PACKAGE_NAME}.${CLASS_NAME}") From 61a50cec72cca0d2a0586691554447834fee3d3e Mon Sep 17 00:00:00 2001 From: Javinator9889 Date: Tue, 30 Jun 2020 11:03:17 +0200 Subject: [PATCH 73/95] Load disease information whether is visible while sliding up the bottom sheet --- .../fragments/diseases/DiseasesFragment.kt | 26 ++++++++++++++----- .../fragments/diseases/adapter/Disease.kt | 6 ++++- .../viewmodels/DiseaseInformationViewModel.kt | 7 ++++- 3 files changed, 30 insertions(+), 9 deletions(-) diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/diseases/DiseasesFragment.kt b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/diseases/DiseasesFragment.kt index dbc255a..325629f 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/diseases/DiseasesFragment.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/diseases/DiseasesFragment.kt @@ -87,8 +87,6 @@ class DiseasesFragment : BaseFragmentView(), LayoutVisibilityChange { if (it.isEmpty()) return@observe parsedHTMLTexts = it - upperAdsAdapter.add(Ads()) - lowerAdsAdapter.add(Ads()) it.forEachIndexed { i, parsedText -> val animation = if (i % 2 == 0) R.raw.virus_red @@ -96,9 +94,9 @@ class DiseasesFragment : BaseFragmentView(), LayoutVisibilityChange { val layoutId = if (i % 2 == 0) R.layout.disease_card_layout else R.layout.disease_card_alt_layout - diseasesAdapter.add( - Disease(animation, parsedText, layoutId, i) - ) + val disease = Disease(animation, parsedText, layoutId, i) + if (diseasesAdapter.getAdapterPosition(disease) == -1) + diseasesAdapter.add(disease) } loading.visibility = View.INVISIBLE container.visibility = View.VISIBLE @@ -151,6 +149,20 @@ class DiseasesFragment : BaseFragmentView(), LayoutVisibilityChange { fastAdapter.addEventHook(DiseaseClickEventHook()) fastAdapter.withSavedInstanceState(savedInstanceState) behavior = BottomSheetBehavior.from(view.contentLayout) + behavior.addBottomSheetCallback(object : BottomSheetBehavior.BottomSheetCallback() { + override fun onStateChanged(bottomSheet: View, newState: Int) { + if (newState == STATE_EXPANDED) { + if (upperAdsAdapter.adapterItemCount == 0) + upperAdsAdapter.add(Ads()) + if (lowerAdsAdapter.adapterItemCount == 0) + lowerAdsAdapter.add(Ads()) + informationViewModel.parseHtml() + } + } + + override fun onSlide(bottomSheet: View, slideOffset: Float) {} + + }) view.countChart.setDrawGridBackground(false) view.countChart.axisLeft.setDrawGridLines(false) view.countChart.axisRight.setDrawGridLines(false) @@ -226,8 +238,8 @@ class DiseasesFragment : BaseFragmentView(), LayoutVisibilityChange { } override fun onVisibilityChanged(visibility: Int) { - if (visibility == View.VISIBLE) - lifecycleScope.launchWhenCreated { informationViewModel.parseHtml() } + /*if (visibility == View.VISIBLE) + lifecycleScope.launchWhenCreated { informationViewModel.parseHtml() }*/ } private suspend fun setCountText( diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/diseases/adapter/Disease.kt b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/diseases/adapter/Disease.kt index 15ed326..299c205 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/diseases/adapter/Disease.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/diseases/adapter/Disease.kt @@ -36,9 +36,13 @@ data class Disease( override val type: Int ) : AbstractItem() { + init { + identifier = type.toLong() + } + override fun getViewHolder(v: View): ViewHolder = ViewHolder(v) - class ViewHolder(private val view: View) : + class ViewHolder(view: View) : FastAdapter.ViewHolder(view) { val cardContainer: MaterialCardView = view.findViewById(R.id.cardDiseaseContainer) diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/viewmodels/DiseaseInformationViewModel.kt b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/viewmodels/DiseaseInformationViewModel.kt index 276d164..89b5868 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/viewmodels/DiseaseInformationViewModel.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/viewmodels/DiseaseInformationViewModel.kt @@ -35,6 +35,7 @@ import kotlinx.coroutines.* import org.sufficientlysecure.htmltextview.HtmlFormatter import org.sufficientlysecure.htmltextview.HtmlFormatterBuilder import timber.log.Timber +import java.util.concurrent.atomic.AtomicBoolean private const val DATA_KEY = "text:html:text" private const val PARSED_JSON_KEY = "text:json:parsed" @@ -43,6 +44,7 @@ class DiseaseInformationViewModel( private val state: SavedStateHandle ) : ViewModel() { private val informationList: DiseasesList = loadHtmlData() + private val isHTMLParsed = AtomicBoolean(false) val parsedHTMLText: MutableLiveData> = state.getLiveData(DATA_KEY, emptyList()) @@ -60,7 +62,9 @@ class DiseaseInformationViewModel( fun parseHtml() = viewModelScope.launch { Timber.d("Parsing HTML") - if (!state.get>(DATA_KEY).isNullOrEmpty()) + if (!state.get>(DATA_KEY) + .isNullOrEmpty() || isHTMLParsed.get() + ) return@launch val parsedItemsList = ArrayList(informationList.diseases.size) @@ -104,6 +108,7 @@ class DiseaseInformationViewModel( } } } + isHTMLParsed.set(true) } private fun createHTML(htmlText: String): Spanned = From 3e7041141921a059b95e066feb3f72420b4c7df5 Mon Sep 17 00:00:00 2001 From: Javinator9889 Date: Tue, 30 Jun 2020 11:48:27 +0200 Subject: [PATCH 74/95] Optimized main activity performance and fixed graph display --- .../fragments/diseases/DiseasesFragment.kt | 115 ++++++++++++------ .../handwashingreminder/utils/Collections.kt | 17 ++- 2 files changed, 91 insertions(+), 41 deletions(-) diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/diseases/DiseasesFragment.kt b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/diseases/DiseasesFragment.kt index 325629f..5bd477c 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/diseases/DiseasesFragment.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/diseases/DiseasesFragment.kt @@ -64,7 +64,7 @@ import kotlinx.coroutines.launch import timber.log.Timber -class DiseasesFragment : BaseFragmentView(), LayoutVisibilityChange { +class DiseasesFragment : BaseFragmentView(), LayoutVisibilityChange, View.OnClickListener { override val layoutId: Int = R.layout.main_disease_view private lateinit var parsedHTMLTexts: List @@ -84,23 +84,23 @@ class DiseasesFragment : BaseFragmentView(), LayoutVisibilityChange { loading.visibility = View.VISIBLE countLoader.visibility = View.VISIBLE informationViewModel.parsedHTMLText.observe(viewLifecycleOwner) { - if (it.isEmpty()) - return@observe - parsedHTMLTexts = it - it.forEachIndexed { i, parsedText -> - val animation = - if (i % 2 == 0) R.raw.virus_red - else R.raw.virus_loader - val layoutId = - if (i % 2 == 0) R.layout.disease_card_layout - else R.layout.disease_card_alt_layout - val disease = Disease(animation, parsedText, layoutId, i) - if (diseasesAdapter.getAdapterPosition(disease) == -1) - diseasesAdapter.add(disease) - } - loading.visibility = View.INVISIBLE - container.visibility = View.VISIBLE + if (it.isEmpty()) + return@observe + parsedHTMLTexts = it + it.forEachIndexed { i, parsedText -> + val animation = + if (i % 2 == 0) R.raw.virus_red + else R.raw.virus_loader + val layoutId = + if (i % 2 == 0) R.layout.disease_card_layout + else R.layout.disease_card_alt_layout + val disease = Disease(animation, parsedText, layoutId, i) + if (diseasesAdapter.getAdapterPosition(disease) == -1) + diseasesAdapter.add(disease) } + loading.visibility = View.INVISIBLE + container.visibility = View.VISIBLE + } handwashingViewModel.allData.observe(viewLifecycleOwner) { lifecycleScope.launch { val dataSet = BarDataSet(it.toBarEntry(), "label") @@ -149,26 +149,15 @@ class DiseasesFragment : BaseFragmentView(), LayoutVisibilityChange { fastAdapter.addEventHook(DiseaseClickEventHook()) fastAdapter.withSavedInstanceState(savedInstanceState) behavior = BottomSheetBehavior.from(view.contentLayout) - behavior.addBottomSheetCallback(object : BottomSheetBehavior.BottomSheetCallback() { - override fun onStateChanged(bottomSheet: View, newState: Int) { - if (newState == STATE_EXPANDED) { - if (upperAdsAdapter.adapterItemCount == 0) - upperAdsAdapter.add(Ads()) - if (lowerAdsAdapter.adapterItemCount == 0) - lowerAdsAdapter.add(Ads()) - informationViewModel.parseHtml() - } - } - - override fun onSlide(bottomSheet: View, slideOffset: Float) {} - - }) - view.countChart.setDrawGridBackground(false) - view.countChart.axisLeft.setDrawGridLines(false) - view.countChart.axisRight.setDrawGridLines(false) - view.countChart.xAxis.setDrawGridLines(false) - view.countChart.invalidate() - view.countUpButton.setOnClickListener { + behavior.addBottomSheetCallback(BottomSheetStateCallback()) +// view.countChart.setDrawGridBackground(false) +// view.countChart.axisLeft.setDrawGridLines(false) +// view.countChart.axisRight.setDrawGridLines(false) +// view.countChart.xAxis.setDrawGridLines(false) +// view.countChart.invalidate() + view.countUpButton.setOnClickListener(this) + view.countDownButton.setOnClickListener(this) + /*view.countUpButton.setOnClickListener { lifecycleScope.launch { val createdItem = handwashingViewModel.getAsync(CalendarUtils.today.time) @@ -200,7 +189,7 @@ class DiseasesFragment : BaseFragmentView(), LayoutVisibilityChange { ) handwashingViewModel.decrement(CalendarUtils.today.time) } - } + }*/ lifecycleScope.launch { val countUpText = getText(R.string.add_another) val countDownText = getText(R.string.reduce_count) @@ -242,6 +231,40 @@ class DiseasesFragment : BaseFragmentView(), LayoutVisibilityChange { lifecycleScope.launchWhenCreated { informationViewModel.parseHtml() }*/ } + override fun onClick(v: View?) { + when (v) { + countUpButton -> lifecycleScope.launch { + val createdItem = + handwashingViewModel.getAsync(CalendarUtils.today.time) + .await() + if (createdItem == null) + handwashingViewModel.create( + Handwashing( + CalendarUtils.today.time, + 0 + ) + ) + handwashingViewModel.increment(CalendarUtils.today.time) + leaves.visibility = View.VISIBLE + if (!leaves.isAnimating) + leaves.playAnimation() + } + countDownButton -> lifecycleScope.launch { + val createdItem = + handwashingViewModel.getAsync(CalendarUtils.today.time) + .await() + if (createdItem == null) + handwashingViewModel.create( + Handwashing( + CalendarUtils.today.time, + 0 + ) + ) + handwashingViewModel.decrement(CalendarUtils.today.time) + } + } + } + private suspend fun setCountText( view: MaterialTextView, @StringRes id: Int, @@ -285,4 +308,20 @@ class DiseasesFragment : BaseFragmentView(), LayoutVisibilityChange { ) } } + + private inner class BottomSheetStateCallback : + BottomSheetBehavior.BottomSheetCallback() { + + override fun onStateChanged(bottomSheet: View, newState: Int) { + if (newState == STATE_EXPANDED) { + if (upperAdsAdapter.adapterItemCount == 0) + upperAdsAdapter.add(Ads()) + if (lowerAdsAdapter.adapterItemCount == 0) + lowerAdsAdapter.add(Ads()) + informationViewModel.parseHtml() + } + } + + override fun onSlide(bottomSheet: View, slideOffset: Float) {} + } } diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/utils/Collections.kt b/app/src/main/java/com/javinator9889/handwashingreminder/utils/Collections.kt index 1d55204..dca5620 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/utils/Collections.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/utils/Collections.kt @@ -18,6 +18,8 @@ */ package com.javinator9889.handwashingreminder.utils +import android.util.SparseArray +import androidx.core.util.set import com.github.mikephil.charting.data.BarEntry import com.javinator9889.handwashingreminder.data.room.entities.Handwashing import com.javinator9889.handwashingreminder.utils.calendar.CalendarUtils @@ -53,14 +55,23 @@ fun List.closest(): Date { fun List.toBarEntry(): List { val entryBars = mutableListOf() + val daysSorted = mutableListOf() + val daysEntriesArray = SparseArray(size) for (entry in this) { val daysBetween = (CalendarUtils.today.time.time - entry.date.time).run { - TimeUnit.DAYS.convert(this, TimeUnit.MILLISECONDS).toFloat() + -TimeUnit.DAYS.convert(this, TimeUnit.MILLISECONDS).toInt() } - + daysEntriesArray[daysBetween] = entry.amount.toFloat() + daysSorted.add(daysBetween) + } + daysSorted.sort() + for (daysDifference in daysSorted) { entryBars.add( - BarEntry(daysBetween, entry.amount.toFloat()) + BarEntry( + daysDifference.toFloat(), + daysEntriesArray[daysDifference] + ) ) } return entryBars From 53d2d9c95b59f6fa985468742be35a76d9ac65c3 Mon Sep 17 00:00:00 2001 From: Javinator9889 Date: Tue, 30 Jun 2020 13:10:55 +0200 Subject: [PATCH 75/95] Working BarDataSet chart on new welcome screen (issue #13) --- .../fragments/diseases/DiseasesFragment.kt | 68 +++++++++++++++++-- 1 file changed, 61 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/diseases/DiseasesFragment.kt b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/diseases/DiseasesFragment.kt index 5bd477c..9863260 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/diseases/DiseasesFragment.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/diseases/DiseasesFragment.kt @@ -33,6 +33,7 @@ import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import com.github.mikephil.charting.data.BarData import com.github.mikephil.charting.data.BarDataSet +import com.github.mikephil.charting.formatter.ValueFormatter import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_COLLAPSED import com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_EXPANDED @@ -78,6 +79,7 @@ class DiseasesFragment : BaseFragmentView(), LayoutVisibilityChange, View.OnClic SavedViewModelFactory(DiseaseInformationViewModel.Factory, this) } private val handwashingViewModel: HandwashingViewModel by activityViewModels() + private var dataSet: BarDataSet? = null init { lifecycleScope.launchWhenStarted { @@ -103,11 +105,33 @@ class DiseasesFragment : BaseFragmentView(), LayoutVisibilityChange, View.OnClic } handwashingViewModel.allData.observe(viewLifecycleOwner) { lifecycleScope.launch { - val dataSet = BarDataSet(it.toBarEntry(), "label") + dataSet?.let { set -> + Timber.d("Adding new items to dataSet") + set.clear() + for (entry in it.toBarEntry()) { + set.addEntry(entry) +// countChart.notifyDataSetChanged() + } + } ?: with(BarDataSet(it.toBarEntry(), "")) { + Timber.d("dataSet not created so instantiating it") + valueFormatter = IntFormatter() + dataSet = this +// countChart.data.addDataSet(this) +// countChart.notifyDataSetChanged() + } countChart.data = BarData(dataSet) countChart.notifyDataSetChanged() - countChart.setVisibleXRangeMaximum(7F) +// countChart.data.notifyDataChanged() +// countChart.minOffset = 0F +// countChart.setExtraOffsets(4F, 4F, 4F, 4F) +// countChart.setViewPortOffsets(0F, 4F, 0F, 0F) +// countChart.data = BarData(dataSet) +// countChart.notifyDataSetChanged() +// countChart.axisLeft.axisMinimum = 0F +// countChart.data.notifyDataChanged() + countChart.fitScreen() countChart.moveViewToX(0F) + countChart.invalidate() val todayAmount = handwashingViewModel.getAsync(CalendarUtils.today.time) val weeklyAmount = @@ -150,11 +174,37 @@ class DiseasesFragment : BaseFragmentView(), LayoutVisibilityChange, View.OnClic fastAdapter.withSavedInstanceState(savedInstanceState) behavior = BottomSheetBehavior.from(view.contentLayout) behavior.addBottomSheetCallback(BottomSheetStateCallback()) -// view.countChart.setDrawGridBackground(false) -// view.countChart.axisLeft.setDrawGridLines(false) -// view.countChart.axisRight.setDrawGridLines(false) -// view.countChart.xAxis.setDrawGridLines(false) -// view.countChart.invalidate() + view.countChart.setDrawGridBackground(false) + view.countChart.axisLeft.apply { + setDrawGridLines(false) + valueFormatter = IntFormatter() + isGranularityEnabled = true + granularity = 1F + axisMinimum = 0F +// spaceMax = .01F + } + view.countChart.axisRight.apply { + setDrawGridLines(false) + valueFormatter = IntFormatter() + isGranularityEnabled = true + granularity = 1F + axisMinimum = 0F +// spaceMax = .01F + } + view.countChart.xAxis.apply { + setDrawGridLines(false) + valueFormatter = IntFormatter() + isGranularityEnabled = true + granularity = 1F + axisMaximum = 0F + } + view.countChart.setVisibleXRangeMaximum(7F) + view.countChart.isAutoScaleMinMaxEnabled = true + view.countChart.legend.isEnabled = false + view.countChart.description.isEnabled = false +// view.countChart.extraTopOffset = 16F +// view.countChart.extraTopOffset = dpToPx(4F) + view.countChart.invalidate() view.countUpButton.setOnClickListener(this) view.countDownButton.setOnClickListener(this) /*view.countUpButton.setOnClickListener { @@ -324,4 +374,8 @@ class DiseasesFragment : BaseFragmentView(), LayoutVisibilityChange, View.OnClic override fun onSlide(bottomSheet: View, slideOffset: Float) {} } + + private inner class IntFormatter : ValueFormatter() { + override fun getFormattedValue(value: Float) = value.toInt().toString() + } } From 3625997a37d11abd1cc12879a6af1310494d95fd Mon Sep 17 00:00:00 2001 From: Javinator9889 Date: Tue, 30 Jun 2020 13:25:12 +0200 Subject: [PATCH 76/95] Moved onViewCreated code to a coroutine (issues #11 & #14) --- .../fragments/diseases/DiseasesFragment.kt | 198 +++++++----------- 1 file changed, 77 insertions(+), 121 deletions(-) diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/diseases/DiseasesFragment.kt b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/diseases/DiseasesFragment.kt index 9863260..147958e 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/diseases/DiseasesFragment.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/diseases/DiseasesFragment.kt @@ -65,7 +65,8 @@ import kotlinx.coroutines.launch import timber.log.Timber -class DiseasesFragment : BaseFragmentView(), LayoutVisibilityChange, View.OnClickListener { +class DiseasesFragment : BaseFragmentView(), LayoutVisibilityChange, + View.OnClickListener { override val layoutId: Int = R.layout.main_disease_view private lateinit var parsedHTMLTexts: List @@ -88,47 +89,38 @@ class DiseasesFragment : BaseFragmentView(), LayoutVisibilityChange, View.OnClic informationViewModel.parsedHTMLText.observe(viewLifecycleOwner) { if (it.isEmpty()) return@observe - parsedHTMLTexts = it - it.forEachIndexed { i, parsedText -> - val animation = - if (i % 2 == 0) R.raw.virus_red - else R.raw.virus_loader - val layoutId = - if (i % 2 == 0) R.layout.disease_card_layout - else R.layout.disease_card_alt_layout - val disease = Disease(animation, parsedText, layoutId, i) - if (diseasesAdapter.getAdapterPosition(disease) == -1) - diseasesAdapter.add(disease) + lifecycleScope.launch { + parsedHTMLTexts = it + it.forEachIndexed { i, parsedText -> + val animation = + if (i % 2 == 0) R.raw.virus_red + else R.raw.virus_loader + val layoutId = + if (i % 2 == 0) R.layout.disease_card_layout + else R.layout.disease_card_alt_layout + val disease = + Disease(animation, parsedText, layoutId, i) + if (diseasesAdapter.getAdapterPosition(disease) == -1) + diseasesAdapter.add(disease) + } + loading.visibility = View.INVISIBLE + container.visibility = View.VISIBLE } - loading.visibility = View.INVISIBLE - container.visibility = View.VISIBLE } handwashingViewModel.allData.observe(viewLifecycleOwner) { lifecycleScope.launch { dataSet?.let { set -> Timber.d("Adding new items to dataSet") set.clear() - for (entry in it.toBarEntry()) { + for (entry in it.toBarEntry()) set.addEntry(entry) -// countChart.notifyDataSetChanged() - } } ?: with(BarDataSet(it.toBarEntry(), "")) { Timber.d("dataSet not created so instantiating it") valueFormatter = IntFormatter() dataSet = this -// countChart.data.addDataSet(this) -// countChart.notifyDataSetChanged() } countChart.data = BarData(dataSet) countChart.notifyDataSetChanged() -// countChart.data.notifyDataChanged() -// countChart.minOffset = 0F -// countChart.setExtraOffsets(4F, 4F, 4F, 4F) -// countChart.setViewPortOffsets(0F, 4F, 0F, 0F) -// countChart.data = BarData(dataSet) -// countChart.notifyDataSetChanged() -// countChart.axisLeft.axisMinimum = 0F -// countChart.data.notifyDataChanged() countChart.fitScreen() countChart.moveViewToX(0F) countChart.invalidate() @@ -162,96 +154,7 @@ class DiseasesFragment : BaseFragmentView(), LayoutVisibilityChange, View.OnClic override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - emojiLoader = EmojiLoader.loadAsync(view.context) - val adapters = listOf(upperAdsAdapter, diseasesAdapter, lowerAdsAdapter) - fastAdapter = FastAdapter.with(adapters) - val rvManager = LinearLayoutManager(context) - with(view.container) { - layoutManager = rvManager - adapter = fastAdapter - } - fastAdapter.addEventHook(DiseaseClickEventHook()) - fastAdapter.withSavedInstanceState(savedInstanceState) - behavior = BottomSheetBehavior.from(view.contentLayout) - behavior.addBottomSheetCallback(BottomSheetStateCallback()) - view.countChart.setDrawGridBackground(false) - view.countChart.axisLeft.apply { - setDrawGridLines(false) - valueFormatter = IntFormatter() - isGranularityEnabled = true - granularity = 1F - axisMinimum = 0F -// spaceMax = .01F - } - view.countChart.axisRight.apply { - setDrawGridLines(false) - valueFormatter = IntFormatter() - isGranularityEnabled = true - granularity = 1F - axisMinimum = 0F -// spaceMax = .01F - } - view.countChart.xAxis.apply { - setDrawGridLines(false) - valueFormatter = IntFormatter() - isGranularityEnabled = true - granularity = 1F - axisMaximum = 0F - } - view.countChart.setVisibleXRangeMaximum(7F) - view.countChart.isAutoScaleMinMaxEnabled = true - view.countChart.legend.isEnabled = false - view.countChart.description.isEnabled = false -// view.countChart.extraTopOffset = 16F -// view.countChart.extraTopOffset = dpToPx(4F) - view.countChart.invalidate() - view.countUpButton.setOnClickListener(this) - view.countDownButton.setOnClickListener(this) - /*view.countUpButton.setOnClickListener { - lifecycleScope.launch { - val createdItem = - handwashingViewModel.getAsync(CalendarUtils.today.time) - .await() - if (createdItem == null) - handwashingViewModel.create( - Handwashing( - CalendarUtils.today.time, - 0 - ) - ) - handwashingViewModel.increment(CalendarUtils.today.time) - leaves.visibility = View.VISIBLE - if (!leaves.isAnimating) - leaves.playAnimation() - } - } - view.countDownButton.setOnClickListener { - lifecycleScope.launch { - val createdItem = - handwashingViewModel.getAsync(CalendarUtils.today.time) - .await() - if (createdItem == null) - handwashingViewModel.create( - Handwashing( - CalendarUtils.today.time, - 0 - ) - ) - handwashingViewModel.decrement(CalendarUtils.today.time) - } - }*/ - lifecycleScope.launch { - val countUpText = getText(R.string.add_another) - val countDownText = getText(R.string.reduce_count) - val emojiCompat = emojiLoader.await() - try { - countUpButton.text = emojiCompat.process(countUpText) - countDownButton.text = emojiCompat.process(countDownText) - } catch (_: IllegalStateException) { - countUpButton.text = countUpText - countDownButton.text = countDownText - } - } + onViewCreatedAsync(view, savedInstanceState) } override fun onSaveInstanceState(outState: Bundle) { @@ -276,10 +179,7 @@ class DiseasesFragment : BaseFragmentView(), LayoutVisibilityChange, View.OnClic } } - override fun onVisibilityChanged(visibility: Int) { - /*if (visibility == View.VISIBLE) - lifecycleScope.launchWhenCreated { informationViewModel.parseHtml() }*/ - } + override fun onVisibilityChanged(visibility: Int) {} override fun onClick(v: View?) { when (v) { @@ -332,6 +232,62 @@ class DiseasesFragment : BaseFragmentView(), LayoutVisibilityChange, View.OnClic } } + private fun onViewCreatedAsync(view: View, savedInstanceState: Bundle?) = + lifecycleScope.launch { + emojiLoader = EmojiLoader.loadAsync(view.context) + val adapters = + listOf(upperAdsAdapter, diseasesAdapter, lowerAdsAdapter) + fastAdapter = FastAdapter.with(adapters) + val rvManager = LinearLayoutManager(context) + with(view.container) { + layoutManager = rvManager + adapter = fastAdapter + } + fastAdapter.addEventHook(DiseaseClickEventHook()) + fastAdapter.withSavedInstanceState(savedInstanceState) + behavior = BottomSheetBehavior.from(view.contentLayout) + behavior.addBottomSheetCallback(BottomSheetStateCallback()) + view.countChart.setDrawGridBackground(false) + view.countChart.axisLeft.apply { + setDrawGridLines(false) + valueFormatter = IntFormatter() + isGranularityEnabled = true + granularity = 1F + axisMinimum = 0F + } + view.countChart.axisRight.apply { + setDrawGridLines(false) + valueFormatter = IntFormatter() + isGranularityEnabled = true + granularity = 1F + axisMinimum = 0F + } + view.countChart.xAxis.apply { + setDrawGridLines(false) + valueFormatter = IntFormatter() + isGranularityEnabled = true + granularity = 1F + axisMaximum = 0F + } + view.countChart.setVisibleXRangeMaximum(7F) + view.countChart.isAutoScaleMinMaxEnabled = true + view.countChart.legend.isEnabled = false + view.countChart.description.isEnabled = false + view.countChart.invalidate() + view.countUpButton.setOnClickListener(this@DiseasesFragment) + view.countDownButton.setOnClickListener(this@DiseasesFragment) + val countUpText = getText(R.string.add_another) + val countDownText = getText(R.string.reduce_count) + val emojiCompat = emojiLoader.await() + try { + countUpButton.text = emojiCompat.process(countUpText) + countDownButton.text = emojiCompat.process(countDownText) + } catch (_: IllegalStateException) { + countUpButton.text = countUpText + countDownButton.text = countDownText + } + } + private inner class DiseaseClickEventHook : ClickEventHook() { override fun onBind(viewHolder: RecyclerView.ViewHolder) = if (viewHolder is Disease.ViewHolder) viewHolder.cardContainer From 272d327646abaf68de38c5809d4d69bccf5708c8 Mon Sep 17 00:00:00 2001 From: Javinator9889 Date: Tue, 30 Jun 2020 14:06:11 +0200 Subject: [PATCH 77/95] Created new setting for managing the time in between activity transitions notifications --- .../views/fragments/settings/SettingsView.kt | 6 ++- .../data/SettingsLoader.kt | 45 ++++++++++++++++--- .../handwashingreminder/utils/Constants.kt | 1 + .../main/res/layout/preference_edit_text.xml | 31 +++++++++++++ app/src/main/res/values-es/strings.xml | 7 +++ app/src/main/res/values/strings.xml | 7 +++ app/src/main/res/xml/preferences.xml | 9 ++++ 7 files changed, 100 insertions(+), 6 deletions(-) create mode 100644 app/src/main/res/layout/preference_edit_text.xml diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/settings/SettingsView.kt b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/settings/SettingsView.kt index e798545..69e75e7 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/settings/SettingsView.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/settings/SettingsView.kt @@ -27,6 +27,7 @@ import com.javinator9889.handwashingreminder.R import com.javinator9889.handwashingreminder.activities.base.LayoutVisibilityChange import com.javinator9889.handwashingreminder.data.SettingsLoader import com.javinator9889.handwashingreminder.gms.vendor.BillingService +import timber.log.Timber import java.lang.ref.WeakReference class SettingsView : PreferenceFragmentCompat(), @@ -61,7 +62,10 @@ class SettingsView : PreferenceFragmentCompat(), override fun onPreferenceChange( preference: Preference?, newValue: Any? - ): Boolean = loader.onPreferenceChange(preference, newValue) + ): Boolean { + Timber.d("Preference $preference changed - ${preference?.key}") + return loader.onPreferenceChange(preference, newValue) + } override fun onVisibilityChanged(visibility: Int) { if (visibility == View.VISIBLE) diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/data/SettingsLoader.kt b/app/src/main/java/com/javinator9889/handwashingreminder/data/SettingsLoader.kt index d532594..8786c8d 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/data/SettingsLoader.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/data/SettingsLoader.kt @@ -27,6 +27,7 @@ import androidx.annotation.StringRes import androidx.emoji.text.EmojiCompat import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.lifecycleScope +import androidx.preference.EditTextPreference import androidx.preference.ListPreference import androidx.preference.Preference import androidx.preference.SwitchPreference @@ -293,6 +294,24 @@ class SettingsLoader( ), dispatcher = Dispatchers.Main ).also { deferreds.add(it) } + setupPreferenceAsync( + Preferences.ACTIVITY_MINIMUM_TIME, + Ionicons.Icon.ion_ios_stopwatch_outline, + onInitialized = { it, _ -> + runCatching { + it as EditTextPreference + val timeText = Integer.parseInt(it.text) + val minutes = resources.getQuantityString( + R.plurals.minutes, + timeText, + timeText + ) + it.summary = + getString(R.string.minimum_time_summ, minutes) + } + }, + onChangeListener = this@with + ).also { deferreds.add(it) } deferreds.awaitAll() arePreferencesInitialized.set(true) } @@ -454,9 +473,10 @@ class SettingsLoader( preference: Preference?, newValue: Any? ): Boolean = - when (preference) { - view.firebaseAnalyticsPreference.get() -> { + when (preference?.key) { + Preferences.ANALYTICS_ENABLED -> { val enabled = newValue as Boolean + Timber.d("Analytics collection is $enabled") with(FirebaseAnalytics.getInstance(view.requireContext())) { setAnalyticsCollectionEnabled(enabled) if (!enabled) @@ -464,15 +484,17 @@ class SettingsLoader( } true } - view.firebasePerformancePreference.get() -> { + Preferences.PERFORMANCE_ENABLED -> { val enabled = newValue as Boolean + Timber.d("Performance is $enabled") with(FirebasePerformance.getInstance()) { isPerformanceCollectionEnabled = enabled } true } - view.adsPreference.get() -> { + Preferences.ADS_ENABLED -> { val enabled = newValue as Boolean + Timber.d("Ads are enabled $enabled") var ret = false val adEnabler = AdsEnabler(HandwashingApplication.instance) if (enabled) { @@ -512,7 +534,7 @@ class SettingsLoader( } ret } - view.donationsPreference.get() -> { + Preferences.DONATIONS -> { Timber.d("Purchase clicked - $newValue") val purchaseId = newValue as String if (isConnected()) @@ -531,6 +553,19 @@ class SettingsLoader( } false } + Preferences.ACTIVITY_MINIMUM_TIME -> runCatching { + preference as EditTextPreference + Timber.d("Changing activity interval - $newValue") + val timeText = Integer.parseInt(newValue as String) + val minutes = view.resources.getQuantityString( + R.plurals.minutes, + timeText, + timeText + ) + preference.summary = + view.getString(R.string.minimum_time_summ, minutes) + true + }.getOrElse { Timber.w(it); false } else -> true } } diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/utils/Constants.kt b/app/src/main/java/com/javinator9889/handwashingreminder/utils/Constants.kt index 9495bb5..8ff8004 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/utils/Constants.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/utils/Constants.kt @@ -42,6 +42,7 @@ object Preferences { ) const val DONATIONS = "donations" const val INITIAL_TUTORIAL_DONE = "app:tutorial:is_done" + const val ACTIVITY_MINIMUM_TIME = "activity:gms:minimumInterval" } object TimeConfig { diff --git a/app/src/main/res/layout/preference_edit_text.xml b/app/src/main/res/layout/preference_edit_text.xml new file mode 100644 index 0000000..fe704d1 --- /dev/null +++ b/app/src/main/res/layout/preference_edit_text.xml @@ -0,0 +1,31 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 3f658f5..563cab6 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -278,4 +278,11 @@ ¡Añade otra más! 🙌 Bueno, quizás una menos 😅 Información de enfermedades + Tiempo mínimo en minutos… + Tiempo mínimo entre notificaciones + Recibirás notificaciones con un tiempo mínimo de %1$s entre ellas + + %d minuto + %d minutos + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index aedd584..ff5966c 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -306,4 +306,11 @@ Add another! 🙌 Well, maybe one less 😅 Diseases information + Minimum time in minutes… + Minimum time in between notifications + You will receive notifications with at least %1$s in between them + + %d minute + %d minutes + diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml index 6d6758a..4924349 100644 --- a/app/src/main/res/xml/preferences.xml +++ b/app/src/main/res/xml/preferences.xml @@ -57,6 +57,15 @@ android:key="activity:gms:tracking" android:title="@string/activity_recognition" app:summary="For enabling the activity recognition, you need both Google Play Services and permissions" /> + + From f29cc65dc0ecdaeac7b6075a7660a97fb3c3c949 Mon Sep 17 00:00:00 2001 From: Javinator9889 Date: Tue, 30 Jun 2020 14:21:36 +0200 Subject: [PATCH 78/95] Implemented logic of ActivityNotifications --- .../gms/activity/ActivityReceiver.kt | 32 ++++++++++++++++++- .../utils/calendar/Calendar.kt | 12 +++++++ 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/gms/activity/ActivityReceiver.kt b/app/src/main/java/com/javinator9889/handwashingreminder/gms/activity/ActivityReceiver.kt index 745a03e..8c449ec 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/gms/activity/ActivityReceiver.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/gms/activity/ActivityReceiver.kt @@ -23,6 +23,7 @@ import android.content.Context import android.content.Intent import androidx.annotation.StringRes import androidx.emoji.text.EmojiCompat +import androidx.preference.PreferenceManager import com.google.android.gms.location.ActivityTransition import com.google.android.gms.location.ActivityTransitionResult import com.google.android.gms.location.DetectedActivity @@ -30,11 +31,16 @@ import com.javinator9889.handwashingreminder.R import com.javinator9889.handwashingreminder.emoji.EmojiLoader import com.javinator9889.handwashingreminder.notifications.NotificationsHandler import com.javinator9889.handwashingreminder.utils.ACTIVITY_CHANNEL_ID +import com.javinator9889.handwashingreminder.utils.Preferences +import com.javinator9889.handwashingreminder.utils.calendar.CalendarUtils import com.javinator9889.handwashingreminder.utils.goAsync import kotlinx.coroutines.Deferred import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import timber.log.Timber +import java.io.* +import java.util.* +import java.util.concurrent.TimeUnit class ActivityReceiver : BroadcastReceiver() { /** @@ -82,6 +88,24 @@ class ActivityReceiver : BroadcastReceiver() { detectedActivity: Int, context: Context ) { + val preferences = PreferenceManager.getDefaultSharedPreferences(context) + val timeInBetweenNotifications = + preferences.getInt(Preferences.ACTIVITY_MINIMUM_TIME, 15) + val timeFile = File(context.cacheDir, "activity.time") + var latestNotificationTime = 0L + withContext(Dispatchers.IO) { + if (timeFile.exists()) { + DataInputStream(FileInputStream(timeFile)).use { + latestNotificationTime = it.readLong() + } + } + } + val timeDifference = CalendarUtils.timeBetweenIn( + TimeUnit.MINUTES, + latestNotificationTime + ) + if (timeDifference <= timeInBetweenNotifications) + return val notificationContent = when (detectedActivity) { DetectedActivity.WALKING -> NotificationContent( @@ -113,7 +137,8 @@ class ActivityReceiver : BroadcastReceiver() { val emojiCompat = emojiLoader.await() title = emojiCompat.process(title) content = emojiCompat.process(content) - } catch (_: IllegalStateException) { } + } catch (_: IllegalStateException) { + } withContext(Dispatchers.Main) { notificationsHandler.createNotification( iconDrawable = R.drawable.ic_stat_handwashing, @@ -123,6 +148,11 @@ class ActivityReceiver : BroadcastReceiver() { longContent = content ) } + withContext(Dispatchers.IO) { + DataOutputStream(FileOutputStream(timeFile)).use { + it.writeLong(Calendar.getInstance().timeInMillis) + } + } } } diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/utils/calendar/Calendar.kt b/app/src/main/java/com/javinator9889/handwashingreminder/utils/calendar/Calendar.kt index 28118a4..a58c7c4 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/utils/calendar/Calendar.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/utils/calendar/Calendar.kt @@ -19,6 +19,7 @@ package com.javinator9889.handwashingreminder.utils.calendar import java.util.* +import java.util.concurrent.TimeUnit object CalendarUtils { val today: Calendar @@ -41,4 +42,15 @@ object CalendarUtils { today.add(Calendar.MONTH, -1) today } + + fun timeBetweenIn( + unit: TimeUnit, + to: Long, + from: Long = Calendar.getInstance().timeInMillis + ): Long = unit.convert(timeBetween(from, to), TimeUnit.MILLISECONDS) + + fun timeBetween( + to: Long, + from: Long = Calendar.getInstance().timeInMillis + ): Long = from - to } \ No newline at end of file From 5f31f031ef530355009aafde8426eeb63b7902cc Mon Sep 17 00:00:00 2001 From: Javinator9889 Date: Tue, 30 Jun 2020 14:35:06 +0200 Subject: [PATCH 79/95] Included new settings' section for disabling animations --- app/build.gradle | 2 +- .../data/SettingsLoader.kt | 4 ++++ .../LottieAdaptedPerformanceAnimationView.kt | 15 +++++++++---- .../handwashingreminder/utils/Constants.kt | 1 + app/src/main/res/values-es/strings.xml | 5 +++++ app/src/main/res/values/strings.xml | 5 +++++ app/src/main/res/xml/preferences.xml | 22 +++++++++++++------ 7 files changed, 42 insertions(+), 12 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 3bed3f6..9f45cfb 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -40,7 +40,7 @@ android { applicationId "com.javinator9889.handwashingreminder" minSdkVersion 17 targetSdkVersion 29 - versionCode 132 + versionCode 133 versionName "1.2.0-${gitCommitHash}" multiDexEnabled true resConfigs "en", "es" diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/data/SettingsLoader.kt b/app/src/main/java/com/javinator9889/handwashingreminder/data/SettingsLoader.kt index 8786c8d..f2608c6 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/data/SettingsLoader.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/data/SettingsLoader.kt @@ -312,6 +312,10 @@ class SettingsLoader( }, onChangeListener = this@with ).also { deferreds.add(it) } + setupPreferenceAsync( + Preferences.PERFORMANCE_ANIMATIONS, + Ionicons.Icon.ion_battery_low + ).also { deferreds.add(it) } deferreds.awaitAll() arePreferencesInitialized.set(true) } diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/graphics/LottieAdaptedPerformanceAnimationView.kt b/app/src/main/java/com/javinator9889/handwashingreminder/graphics/LottieAdaptedPerformanceAnimationView.kt index 708d9e9..45e52c6 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/graphics/LottieAdaptedPerformanceAnimationView.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/graphics/LottieAdaptedPerformanceAnimationView.kt @@ -20,29 +20,36 @@ package com.javinator9889.handwashingreminder.graphics import android.content.Context import android.util.AttributeSet +import androidx.preference.PreferenceManager import com.airbnb.lottie.LottieAnimationView import com.airbnb.lottie.LottieComposition import com.airbnb.lottie.LottieOnCompositionLoadedListener +import com.javinator9889.handwashingreminder.utils.Preferences import com.javinator9889.handwashingreminder.utils.isHighPerformingDevice class LottieAdaptedPerformanceAnimationView : LottieAnimationView, LottieOnCompositionLoadedListener { - constructor(context: Context): super(context) - constructor(context: Context, attrs: AttributeSet): super(context, attrs) - constructor(context: Context, attrs: AttributeSet, attrStyle: Int): + constructor(context: Context) : super(context) + constructor(context: Context, attrs: AttributeSet) : super(context, attrs) + constructor(context: Context, attrs: AttributeSet, attrStyle: Int) : super(context, attrs, attrStyle) + private val areAnimationsEnabled: Boolean + init { addLottieOnCompositionLoadedListener(this) enableMergePathsForKitKatAndAbove(true) setCacheComposition(true) + val preferences = PreferenceManager.getDefaultSharedPreferences(context) + areAnimationsEnabled = + preferences.getBoolean(Preferences.PERFORMANCE_ANIMATIONS, true) } override fun getDuration(): Long = if (isHighPerformingDevice()) super.getDuration() else 100L override fun onCompositionLoaded(composition: LottieComposition?) { - if (!isHighPerformingDevice()) { + if (!isHighPerformingDevice() || !areAnimationsEnabled) { setMinFrame(maxFrame.toInt()) repeatCount = 0 } diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/utils/Constants.kt b/app/src/main/java/com/javinator9889/handwashingreminder/utils/Constants.kt index 8ff8004..3b6ab6f 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/utils/Constants.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/utils/Constants.kt @@ -43,6 +43,7 @@ object Preferences { const val DONATIONS = "donations" const val INITIAL_TUTORIAL_DONE = "app:tutorial:is_done" const val ACTIVITY_MINIMUM_TIME = "activity:gms:minimumInterval" + const val PERFORMANCE_ANIMATIONS = "app:performance:animations" } object TimeConfig { diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 563cab6..7a50a2b 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -285,4 +285,9 @@ %d minuto %d minutos + Opciones de rendimiento de la app + Deshabilitar las animaciones en la app + Actualmente, las animaciones están activadas. + Ten en cuenta que esto puede afectar tanto al rendimiento como a la batería + No se mostrará ninguna animación diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index ff5966c..e55dd41 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -313,4 +313,9 @@ %d minute %d minutes + App\'s performance options + Disable app\'s animations + Currently, the app will play some animations. + Keep in mind that it can affect both performance and battery life + No animations will be played diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml index 4924349..f1e5a8e 100644 --- a/app/src/main/res/xml/preferences.xml +++ b/app/src/main/res/xml/preferences.xml @@ -10,24 +10,24 @@ + android:title="@string/playstore" /> + android:title="@string/github" /> + android:title="@string/linkedin" /> + android:title="@string/twitter" /> + + + Date: Tue, 30 Jun 2020 15:58:25 +0200 Subject: [PATCH 80/95] Solved an issue in which the endless recycler view stopped working on layout refresh --- .../views/fragments/news/NewsFragment.kt | 13 ++++- .../views/viewmodels/NewsViewModel.kt | 57 ++++++++++++------- .../collections/NewsInformation.kt | 16 +++--- .../main/res/layout/loading_recycler_view.xml | 36 +++++++++++- app/src/main/res/raw/error_state.json | 1 + 5 files changed, 90 insertions(+), 33 deletions(-) create mode 100644 app/src/main/res/raw/error_state.json diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/news/NewsFragment.kt b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/news/NewsFragment.kt index 5ace171..b612294 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/news/NewsFragment.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/news/NewsFragment.kt @@ -69,6 +69,11 @@ class NewsFragment : BaseFragmentView(), LayoutVisibilityChange { newsViewModel.newsData.observe(viewLifecycleOwner, Observer { if (::footerAdapter.isInitialized) footerAdapter.clear() + if (it.hasError) { + if (newsAdapter.adapterItemCount == 0) { + + } + } if (it.id !in activeItems) { val newsObject = News( title = it.title, @@ -128,9 +133,15 @@ class NewsFragment : BaseFragmentView(), LayoutVisibilityChange { refreshLayout.isRefreshing = true newsAdapter.clear() activeItems.clear() + footerAdapter.clear() + scrollListener.disable() lifecycleScope.launch { newsViewModel.populateData(language = UserProperties.language) - }.invokeOnCompletion { refreshLayout.isRefreshing = false } + }.invokeOnCompletion { + refreshLayout.isRefreshing = false + scrollListener.enable() + scrollListener.resetPageCount() + } container.visibility = View.INVISIBLE } } diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/viewmodels/NewsViewModel.kt b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/viewmodels/NewsViewModel.kt index a2a9075..5a0f430 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/viewmodels/NewsViewModel.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/viewmodels/NewsViewModel.kt @@ -32,6 +32,7 @@ import kotlinx.coroutines.withContext import okhttp3.Headers import timber.log.Timber import java.io.Reader +import java.util.* class NewsViewModel : ViewModel() { @@ -42,30 +43,44 @@ class NewsViewModel : ViewModel() { amount: Int = 10, @Language language: String = Language.ENGLISH ) { - val httpRequest = HttpDownloader() - val klaxon = with(Klaxon()) { - propertyStrategy(newsStrategy) - fieldConverter(KlaxonDate::class, dateConverter) - fieldConverter(KlaxonElements::class, elementConverter) - } - var requestReader: Reader? = null - Auth.init() - val token = Auth.token() - Timber.d("Auth token: $token") - withContext(Dispatchers.IO) { - requestReader = httpRequest.json( - "${API_URL}/api/v1?from=$from&amount=$amount&lang=$language", - headers = Headers.of(mapOf("Authorization" to "Bearer $token")) - ) - } - withContext(Dispatchers.Default) { - JsonReader(requestReader!!).use { reader -> - reader.beginArray { - while (reader.hasNext()) { - newsData.postValue(klaxon.parse(reader)) + try { + val httpRequest = HttpDownloader() + val klaxon = with(Klaxon()) { + propertyStrategy(newsStrategy) + fieldConverter(KlaxonDate::class, dateConverter) + fieldConverter(KlaxonElements::class, elementConverter) + } + var requestReader: Reader? = null + Auth.init() + val token = Auth.token() + Timber.d("Auth token: $token") + withContext(Dispatchers.IO) { + requestReader = httpRequest.json( + "${API_URL}/api/v1?from=$from&amount=$amount&lang=$language", + headers = Headers.of(mapOf("Authorization" to "Bearer $token")) + ) + } + withContext(Dispatchers.Default) { + JsonReader(requestReader!!).use { reader -> + reader.beginArray { + while (reader.hasNext()) { + newsData.postValue(klaxon.parse(reader)) + } } } } + } catch (e: Throwable) { + Timber.w(e, "Exception while populating data") + withContext(Dispatchers.Main) { + newsData.value = NewsData( + hasError = true, + id = "", + discoverDate = Date(), + title = "", + text = "", + url = "" + ) + } } } } \ No newline at end of file diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/collections/NewsInformation.kt b/app/src/main/java/com/javinator9889/handwashingreminder/collections/NewsInformation.kt index aaac9a0..02e26bd 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/collections/NewsInformation.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/collections/NewsInformation.kt @@ -35,7 +35,9 @@ data class NewsData( val url: String, @KlaxonElements val elements: Elements? = null, - val website: Website? = null + val website: Website? = null, + @Json(ignored = true) + val hasError: Boolean = false ) : Parcelable { constructor(parcel: Parcel) : this( parcel.readString()!!, @@ -44,7 +46,8 @@ data class NewsData( parcel.readString()!!, parcel.readString()!!, parcel.readParcelable(Elements::class.java.classLoader), - parcel.readParcelable(Website::class.java.classLoader) + parcel.readParcelable(Website::class.java.classLoader), + parcel.readInt() == 1 ) override fun writeToParcel(parcel: Parcel, flags: Int) { @@ -55,18 +58,15 @@ data class NewsData( parcel.writeString(url) parcel.writeParcelable(elements, flags) parcel.writeParcelable(website, flags) + parcel.writeInt(if (hasError) 1 else 0) } override fun describeContents() = 0 companion object CREATOR : Parcelable.Creator { - override fun createFromParcel(parcel: Parcel): NewsData { - return NewsData(parcel) - } + override fun createFromParcel(parcel: Parcel) = NewsData(parcel) - override fun newArray(size: Int): Array { - return arrayOfNulls(size) - } + override fun newArray(size: Int) = arrayOfNulls(size) } } diff --git a/app/src/main/res/layout/loading_recycler_view.xml b/app/src/main/res/layout/loading_recycler_view.xml index 44e8cf6..82161bb 100644 --- a/app/src/main/res/layout/loading_recycler_view.xml +++ b/app/src/main/res/layout/loading_recycler_view.xml @@ -1,8 +1,8 @@ - + + + + + + + Date: Wed, 1 Jul 2020 17:09:26 +0200 Subject: [PATCH 81/95] When querying data, it is now sorted by discover date. In addition, the updater now "creates" the document always --- functions/package.json | 2 +- functions/src/models/newsriver.ts | 10 +++++++- functions/src/updater.ts | 39 ++++++++++++++----------------- 3 files changed, 27 insertions(+), 24 deletions(-) diff --git a/functions/package.json b/functions/package.json index ec2d122..75168c8 100644 --- a/functions/package.json +++ b/functions/package.json @@ -1,6 +1,6 @@ { "name": "functions", - "version": "1.0.1", + "version": "1.0.2", "scripts": { "lint": "tslint --project tsconfig.json", "build": "tsc", diff --git a/functions/src/models/newsriver.ts b/functions/src/models/newsriver.ts index 6c8c690..8511114 100644 --- a/functions/src/models/newsriver.ts +++ b/functions/src/models/newsriver.ts @@ -4,6 +4,8 @@ import properties = require('../common/properties'); const databases: Record = {}; +const latestResults: Record> = {}; +const lastUpdateMsForLanguage: Record = {}; let initCalled = false; export async function initialize() { @@ -14,6 +16,8 @@ export async function initialize() { projectProperties.database, `${projectProperties.collection}_${language}` ); + lastUpdateMsForLanguage[language] = 0; + latestResults[language] = null; } } @@ -22,12 +26,16 @@ export async function newsForLanguage(language: string) { throw new Error('`initialize` not called'); if (language ! in properties.languages) throw new RangeError(`invalid language "${language}"`); + if (Math.floor((Date.now() - lastUpdateMsForLanguage[language]) / 60000) <= 15) + return latestResults[language]; + lastUpdateMsForLanguage[language] = Date.now(); const collection = databases[language].collection; - const snapshot = await collection.get(); + const snapshot = await collection.orderBy("discoverDate", "desc").get(); const data = new Array(); snapshot.forEach(item => { if (item.data() !== null) data.push(item.data() as NewsriverData) }); + latestResults[language] = data; return data; } diff --git a/functions/src/updater.ts b/functions/src/updater.ts index edf4dbd..5586948 100644 --- a/functions/src/updater.ts +++ b/functions/src/updater.ts @@ -12,7 +12,6 @@ export class Updater { private readonly language: string; private readonly auth: string; private _path: string | undefined; - private readonly network: AxiosInstance get path(): Promise { if (this._path === undefined) @@ -30,6 +29,20 @@ export class Updater { this._path = undefined; } + get network(): AxiosInstance { + return axios.create({ + baseURL: 'https://api.newsriver.io/v2/', + headers: { + 'Authorization': this.auth, + 'Content-Type': 'application/json' + }, + withCredentials: true, + responseType: 'json', + httpsAgent: new https.Agent({ keepAlive: true }), + timeout: 500000 + }); + } + constructor(db: FirebaseFirestore.Firestore | null, collectionName: string | null, searchTerms: Array, @@ -42,17 +55,6 @@ export class Updater { this.language = language; this.auth = auth; this.interval = intervalMins * 60 * 1000; - this.network = axios.create({ - baseURL: 'https://api.newsriver.io/v2/', - headers: { - 'Authorization': this.auth, - 'Content-Type': 'application/json' - }, - withCredentials: true, - responseType: 'json', - httpsAgent: new https.Agent({ keepAlive: true }), - timeout: 500000 - }); this.doRequest() .then(response => { this.updateData(response) @@ -78,16 +80,9 @@ export class Updater { try { for (const element of content) { try { - const document = await this.db.collection(this.collectionName).doc(element.id).get(); - console.debug(`Item with id ${element.id} ${document.exists ? 'exists' : 'does not exist'}`) - if (!document.exists) - firebaseHelper.firestore.createDocumentWithID(this.db, this.collectionName, element.id, element) - .then(created => console.debug(`Item with ID: ${element.id} was ${created ? 'created' : 'not created'}`)) - .catch(err => console.error(`Error while creating document ${err}`)); - else - firebaseHelper.firestore.updateDocument(this.db, this.collectionName, element.id, element) - .then(updated => console.debug(`Item with ID ${element.id} was ${updated ? 'updated' : 'not updated'}`)) - .catch(err => console.error(`Error while updating document ${err}`)); + firebaseHelper.firestore.createDocumentWithID(this.db, this.collectionName, element.id, element) + .then(created => console.debug(`Item with ID: ${element.id} was ${created ? 'created' : 'not created'}`)) + .catch(err => console.error(`Error while creating document ${err}`)); } catch (err) { console.warn(`Error while creating/updating document - ${err}`); } From 645d94dfad90a5aa16ebd2df95191e2db3d080d7 Mon Sep 17 00:00:00 2001 From: Javinator9889 Date: Wed, 1 Jul 2020 17:14:43 +0200 Subject: [PATCH 82/95] Changed NodeJS version --- functions/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/functions/package.json b/functions/package.json index 75168c8..da9db9d 100644 --- a/functions/package.json +++ b/functions/package.json @@ -14,7 +14,7 @@ "daemon": "cross-env-shell RUN_DAEMON=true RUNNING_LOCAL=true \"npm run build && firebase emulators:start --only functions\"" }, "engines": { - "node": ">=10.0.0" + "node": "10" }, "main": "lib/bin/index.js", "dependencies": { From a8ab1f8d49a94e7f6d269dc701fb35e641b12d77 Mon Sep 17 00:00:00 2001 From: Javinator9889 Date: Wed, 1 Jul 2020 18:43:48 +0200 Subject: [PATCH 83/95] Solved an issue with activity minimum time preference (Int casted to String and viceversa) --- app/src/main/AndroidManifest.xml | 6 +++--- .../handwashingreminder/gms/activity/ActivityHandler.kt | 9 +++++---- .../handwashingreminder/gms/activity/ActivityReceiver.kt | 5 +++-- .../handwashingreminder/utils/LogReportTree.kt | 7 +++++-- .../handwashingreminder/utils/calendar/Calendar.kt | 2 +- 5 files changed, 17 insertions(+), 12 deletions(-) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 432e7ab..19a171c 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -50,9 +50,9 @@ android:name=".gms.activity.ActivityReceiver" android:enabled="true" android:exported="true"> - - - + + + ( ActivityTransition.Builder() .setActivityType(DetectedActivity.IN_VEHICLE) @@ -53,7 +53,8 @@ internal val TRANSITIONS = listOf( ) class ActivityHandler private constructor(private val context: Context) { - private var pendingIntent: PendingIntent = createPendingIntent() + private val pendingIntent: PendingIntent + get() = createPendingIntent() private var activityRegistered = false companion object { @@ -95,7 +96,7 @@ class ActivityHandler private constructor(private val context: Context) { private fun createPendingIntent(): PendingIntent = with(Intent(context, ActivityReceiver::class.java)) { -// action = TRANSITIONS_RECEIVER_ACTION + action = TRANSITIONS_RECEIVER_ACTION PendingIntent.getBroadcast( context, ACTIVITY_REQUEST_CODE, diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/gms/activity/ActivityReceiver.kt b/app/src/main/java/com/javinator9889/handwashingreminder/gms/activity/ActivityReceiver.kt index 8c449ec..cd52308 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/gms/activity/ActivityReceiver.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/gms/activity/ActivityReceiver.kt @@ -88,9 +88,9 @@ class ActivityReceiver : BroadcastReceiver() { detectedActivity: Int, context: Context ) { - val preferences = PreferenceManager.getDefaultSharedPreferences(context) + val prefs = PreferenceManager.getDefaultSharedPreferences(context) val timeInBetweenNotifications = - preferences.getInt(Preferences.ACTIVITY_MINIMUM_TIME, 15) + prefs.getString(Preferences.ACTIVITY_MINIMUM_TIME, "15")!!.toInt() val timeFile = File(context.cacheDir, "activity.time") var latestNotificationTime = 0L withContext(Dispatchers.IO) { @@ -104,6 +104,7 @@ class ActivityReceiver : BroadcastReceiver() { TimeUnit.MINUTES, latestNotificationTime ) + Timber.d("$timeDifference - $timeInBetweenNotifications") if (timeDifference <= timeInBetweenNotifications) return val notificationContent = when (detectedActivity) { diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/utils/LogReportTree.kt b/app/src/main/java/com/javinator9889/handwashingreminder/utils/LogReportTree.kt index f7aa96f..ca54fee 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/utils/LogReportTree.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/utils/LogReportTree.kt @@ -36,8 +36,11 @@ class LogReportTree : Timber.Tree() { t: Throwable? ) { when (priority) { - Log.DEBUG, Log.VERBOSE -> return - Log.WARN -> crashlytics.log("W: $tag: $message") + Log.DEBUG, Log.VERBOSE, Log.INFO -> return + Log.WARN -> { + crashlytics.log("W: $tag: $message") + t?.let { crashlytics.recordException(it) } + } Log.ERROR -> { crashlytics.log("E/$tag: $message") t?.let { crashlytics.recordException(t) } diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/utils/calendar/Calendar.kt b/app/src/main/java/com/javinator9889/handwashingreminder/utils/calendar/Calendar.kt index a58c7c4..11275ed 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/utils/calendar/Calendar.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/utils/calendar/Calendar.kt @@ -47,7 +47,7 @@ object CalendarUtils { unit: TimeUnit, to: Long, from: Long = Calendar.getInstance().timeInMillis - ): Long = unit.convert(timeBetween(from, to), TimeUnit.MILLISECONDS) + ): Long = unit.convert(timeBetween(to, from), TimeUnit.MILLISECONDS) fun timeBetween( to: Long, From 0a9cb85cc47ca3e2ffbfe26538be01cfc7c3ff08 Mon Sep 17 00:00:00 2001 From: Javinator9889 Date: Wed, 1 Jul 2020 18:59:43 +0200 Subject: [PATCH 84/95] Solved an error with the authentication library --- .../handwashingreminder/firebase/Auth.kt | 27 +++---------------- 1 file changed, 4 insertions(+), 23 deletions(-) diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/firebase/Auth.kt b/app/src/main/java/com/javinator9889/handwashingreminder/firebase/Auth.kt index 59cd5e9..b3e8704 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/firebase/Auth.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/firebase/Auth.kt @@ -20,9 +20,8 @@ package com.javinator9889.handwashingreminder.firebase import com.google.firebase.auth.FirebaseAuth import com.google.firebase.auth.FirebaseUser -import kotlinx.coroutines.channels.Channel +import com.javinator9889.handwashingreminder.utils.threading.await import timber.log.Timber -import kotlin.IllegalStateException object Auth { @@ -34,38 +33,20 @@ object Auth { if (!::auth.isInitialized) auth = FirebaseAuth.getInstance() val currentUser = auth.currentUser - val signedIn = Channel(0) if (currentUser == null) { Timber.d("No user defined lo signing-in") - auth.signInAnonymously() - .addOnCompleteListener { task -> - signedIn.offer(task.isSuccessful) - if (!task.isSuccessful) - Timber.e(task.exception, "Error while logging") - } - if (signedIn.receive()) { - Timber.d("Signing-in successful so saving user") - this.currentUser = auth.currentUser!! - } + this.currentUser = auth.signInAnonymously().await().user!! } else this.currentUser = currentUser } suspend fun token(): String { - if (!::auth.isInitialized) + if (!::auth.isInitialized || !::currentUser.isInitialized) throw IllegalStateException("init must be called first") if (::privateToken.isInitialized) { Timber.d("Obtaining previous generated token") return privateToken } - val tokenChannel = Channel(0) - currentUser.getIdToken(true) - .addOnCompleteListener { task -> - if (task.isSuccessful) - tokenChannel.offer(task.result!!.token!!) - else - tokenChannel.offer(null) - } - privateToken = tokenChannel.receive() + privateToken = currentUser.getIdToken(true).await().token ?: throw IllegalStateException("token not valid") return privateToken } From 1ecd615e82aa722a19914c8ea12f13fe63dd2a7a Mon Sep 17 00:00:00 2001 From: Javinator9889 Date: Wed, 1 Jul 2020 19:17:51 +0200 Subject: [PATCH 85/95] Solved a problem with news loading when an exception occurs --- .../views/fragments/news/NewsFragment.kt | 18 ++++++++++-------- .../main/res/layout/loading_recycler_view.xml | 19 ++++++++++++++----- app/src/main/res/values-es/strings.xml | 1 + app/src/main/res/values/strings.xml | 1 + 4 files changed, 26 insertions(+), 13 deletions(-) diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/news/NewsFragment.kt b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/news/NewsFragment.kt index b612294..5bbb985 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/news/NewsFragment.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/news/NewsFragment.kt @@ -24,8 +24,8 @@ import android.os.Bundle import android.view.View import androidx.annotation.LayoutRes import androidx.fragment.app.viewModels -import androidx.lifecycle.Observer import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.observe import androidx.lifecycle.whenStarted import androidx.recyclerview.widget.DefaultItemAnimator import androidx.recyclerview.widget.LinearLayoutManager @@ -66,14 +66,15 @@ class NewsFragment : BaseFragmentView(), LayoutVisibilityChange { whenStarted { loading.visibility = View.VISIBLE refreshLayout.isEnabled = false - newsViewModel.newsData.observe(viewLifecycleOwner, Observer { + newsViewModel.newsData.observe(viewLifecycleOwner) { if (::footerAdapter.isInitialized) footerAdapter.clear() - if (it.hasError) { - if (newsAdapter.adapterItemCount == 0) { - - } - } + if (it.hasError && newsAdapter.adapterItemCount == 0) { + errorScreen.visibility = View.VISIBLE + container.visibility = View.INVISIBLE + refreshLayout.isEnabled = true + return@observe + } else errorScreen.visibility = View.INVISIBLE if (it.id !in activeItems) { val newsObject = News( title = it.title, @@ -91,7 +92,7 @@ class NewsFragment : BaseFragmentView(), LayoutVisibilityChange { refreshLayout.isEnabled = true activeItems.add(it.id) } - }) + } } } } @@ -143,6 +144,7 @@ class NewsFragment : BaseFragmentView(), LayoutVisibilityChange { scrollListener.resetPageCount() } container.visibility = View.INVISIBLE + errorScreen.visibility = View.INVISIBLE } } diff --git a/app/src/main/res/layout/loading_recycler_view.xml b/app/src/main/res/layout/loading_recycler_view.xml index 82161bb..800d5f7 100644 --- a/app/src/main/res/layout/loading_recycler_view.xml +++ b/app/src/main/res/layout/loading_recycler_view.xml @@ -1,6 +1,7 @@ @@ -27,22 +28,30 @@ + app:layout_constraintTop_toTopOf="parent" + app:lottie_autoPlay="true" + app:lottie_loop="true" + app:lottie_rawRes="@raw/error_state" /> + app:layout_constraintTop_toBottomOf="@+id/errorAnimation" + tools:text="Error while obtaining new data" /> Actualmente, las animaciones están activadas. Ten en cuenta que esto puede afectar tanto al rendimiento como a la batería No se mostrará ninguna animación + Error al cargar información sobre las noticias diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index e55dd41..f6df4e1 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -318,4 +318,5 @@ Currently, the app will play some animations. Keep in mind that it can affect both performance and battery life No animations will be played + Error while loading news data From 1c7e6973f9a059a37df70bc9a14e1b16f4f34ac4 Mon Sep 17 00:00:00 2001 From: Javinator9889 Date: Wed, 1 Jul 2020 20:04:13 +0200 Subject: [PATCH 86/95] Removed Glide support in favor of Coil --- ads/build.gradle | 10 +- app/build.gradle | 15 ++- .../views/fragments/news/adapter/News.kt | 54 ++++---- .../fragments/washinghands/SliderView.kt | 11 +- .../graphics/CustomGraphicsModule.java | 49 -------- .../graphics/RecyclingImageView.java | 116 ------------------ .../graphics/RecyclingImageView.kt | 103 ++++++++++++++++ app/src/main/res/layout/news_card_view.xml | 3 + .../appintro/timeconfig/TimeConfigActivity.kt | 7 +- .../appintro/timeconfig/TimeConfigItem.kt | 7 +- bundledemoji/build.gradle | 7 ++ 11 files changed, 164 insertions(+), 218 deletions(-) delete mode 100644 app/src/main/java/com/javinator9889/handwashingreminder/graphics/CustomGraphicsModule.java delete mode 100644 app/src/main/java/com/javinator9889/handwashingreminder/graphics/RecyclingImageView.java create mode 100644 app/src/main/java/com/javinator9889/handwashingreminder/graphics/RecyclingImageView.kt diff --git a/ads/build.gradle b/ads/build.gradle index 1a212ef..e279d01 100644 --- a/ads/build.gradle +++ b/ads/build.gradle @@ -22,6 +22,14 @@ android { proguardFiles 'proguard-rules.pro' } } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + kotlinOptions { + jvmTarget = JavaVersion.VERSION_1_8.toString() + } } dependencies { @@ -29,7 +37,7 @@ dependencies { implementation project(':app') // https://firebase.google.com/docs/admob/android/quick-start#import_the_mobile_ads_sdk - implementation 'com.google.firebase:firebase-ads:19.1.0' + implementation 'com.google.firebase:firebase-ads:19.2.0' } repositories { mavenCentral() diff --git a/app/build.gradle b/app/build.gradle index 9f45cfb..dba7b5f 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -99,7 +99,12 @@ android { kotlinOptions { jvmTarget = JavaVersion.VERSION_1_8.toString() } +} +tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all { + kotlinOptions { + jvmTarget = "1.8" + } } dependencies { @@ -176,10 +181,6 @@ dependencies { implementation 'androidx.lifecycle:lifecycle-viewmodel-savedstate:2.2.0' // https://developer.android.com/reference/kotlin/androidx/preference/package-summary api 'androidx.preference:preference-ktx:1.1.1' - // https://github.com/bumptech/glide - api 'com.github.bumptech.glide:glide:4.11.0' - api 'com.github.bumptech.glide:okhttp3-integration:4.11.0' - kapt 'com.github.bumptech.glide:compiler:4.11.0' // https://github.com/afollestad/material-dialogs/ implementation 'com.afollestad.material-dialogs:core:3.3.0' // https://developer.android.com/google/play/billing/billing_library_overview @@ -190,9 +191,9 @@ dependencies { // https://github.com/SufficientlySecure/html-textview implementation 'org.sufficientlysecure:html-textview:3.9' // https://github.com/square/okhttp/tree/okhttp_3.12.x - implementation 'com.squareup.okhttp3:okhttp:3.12.1' + implementation 'com.squareup.okhttp3:okhttp:3.12.11' // https://square.github.io/okio/#releases - implementation 'com.squareup.okio:okio:2.5.0' + implementation 'com.squareup.okio:okio:2.6.0' // https://github.com/deano2390/MaterialShowcaseView implementation 'com.github.deano2390:MaterialShowcaseView:1.3.4' // https://github.com/PhilJay/MPAndroidChart @@ -200,5 +201,7 @@ dependencies { // https://developer.android.com/training/data-storage/room implementation "androidx.room:room-ktx:$room_version" kapt "androidx.room:room-compiler:$room_version" + // https://coil-kt.github.io/coil/ + api "io.coil-kt:coil:0.11.0" } apply plugin: 'com.google.gms.google-services' diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/news/adapter/News.kt b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/news/adapter/News.kt index b5bea1e..446f3b8 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/news/adapter/News.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/news/adapter/News.kt @@ -23,13 +23,16 @@ import android.view.View import android.widget.ImageView import android.widget.TextView import androidx.lifecycle.LifecycleCoroutineScope +import coil.Coil +import coil.api.load +import coil.request.GetRequest import com.airbnb.lottie.LottieAnimationView import com.google.android.material.card.MaterialCardView import com.javinator9889.handwashingreminder.R -import com.javinator9889.handwashingreminder.graphics.GlideApp import com.mikepenz.fastadapter.FastAdapter import com.mikepenz.fastadapter.items.AbstractItem -import kotlinx.coroutines.* +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch import timber.log.Timber import java.text.DateFormat import java.util.* @@ -60,10 +63,6 @@ data class News( val cardContainer: MaterialCardView = view.findViewById(R.id.root) val shareImage: ImageView = view.findViewById(R.id.share) - /*init { - setIsRecyclable(!isHighPerformingDevice()) - }*/ - @SuppressLint("SetTextI18n") override fun bindView(item: News, payloads: List) { val formatter = DateFormat.getDateTimeInstance() @@ -72,33 +71,32 @@ data class News( item.lifecycleScope.launch(context = Dispatchers.Main) { title.text = item.title description.text = item.short - val deferreds = mutableSetOf>() - if (item.imageUrl != null) - deferreds.add( - async { - GlideApp.with(context) - .load(item.imageUrl) - .optionalCenterCrop() - .into(imageHeader) - } - ) - else imageHeader.visibility = View.GONE - if (item.websiteImageUrl != null) - deferreds.add( - async { - GlideApp.with(context) - .load(item.websiteImageUrl) - .optionalCenterCrop() - .into(websiteLogo) - } - ) - else websiteLogo.visibility = View.GONE + val imageLoader = Coil.imageLoader(view.context) + if (item.imageUrl != null) { + val request = GetRequest.Builder(view.context) + .data(item.imageUrl) + .size(imageHeader.width, imageHeader.height) + .allowHardware(true) + .build() + launch(Dispatchers.IO) { + imageHeader.load(imageLoader.execute(request).drawable) + } + } else imageHeader.visibility = View.GONE + if (item.websiteImageUrl != null) { + val request = GetRequest.Builder(view.context) + .data(item.websiteImageUrl) + .size(websiteLogo.width, websiteLogo.height) + .allowHardware(true) + .build() + launch(Dispatchers.IO) { + websiteLogo.load(imageLoader.execute(request).drawable) + } + } else websiteLogo.visibility = View.GONE websiteName.text = item.website ?: context.getString(R.string.no_website) publishDate.text = item.discoverDate?.let { formatter.format(it) } ?: context.getString(R.string.no_date) - deferreds.awaitAll() } } diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/washinghands/SliderView.kt b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/washinghands/SliderView.kt index a48fca7..27dc1a3 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/washinghands/SliderView.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/washinghands/SliderView.kt @@ -27,10 +27,10 @@ import androidx.fragment.app.viewModels import androidx.lifecycle.Observer import androidx.lifecycle.lifecycleScope import androidx.lifecycle.whenStarted +import coil.api.load import com.javinator9889.handwashingreminder.R import com.javinator9889.handwashingreminder.activities.base.BaseFragmentView import com.javinator9889.handwashingreminder.activities.views.viewmodels.* -import com.javinator9889.handwashingreminder.graphics.GlideApp import kotlinx.android.synthetic.main.wash_your_hands_demo.* import kotlinx.coroutines.launch import timber.log.Timber @@ -69,9 +69,7 @@ class SliderView : BaseFragmentView() { }) washingHandsModel.image.observe(viewLifecycleOwner, Observer { try { - GlideApp.with(this@SliderView) - .load(it) - .into(image) + image.load(it) } catch (e: Exception) { Timber.e(e, "Error while loading Glide view") image.setImageResource(it) @@ -124,10 +122,7 @@ class SliderView : BaseFragmentView() { video.requestFocus() video.start() try { - GlideApp.with(this) - .load(drawableId) - .centerInside() - .into(image) + image.load(drawableId) } catch (e: Exception) { Timber.e(e, "Error while loading Glide view") image.setImageResource(drawableId) diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/graphics/CustomGraphicsModule.java b/app/src/main/java/com/javinator9889/handwashingreminder/graphics/CustomGraphicsModule.java deleted file mode 100644 index c1a1739..0000000 --- a/app/src/main/java/com/javinator9889/handwashingreminder/graphics/CustomGraphicsModule.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright © 2020 - present | Handwashing reminder by Javinator9889 - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see https://www.gnu.org/licenses/. - * - * Created by Javinator9889 on 15/04/20 - Handwashing reminder. - */ -package com.javinator9889.handwashingreminder.graphics; - -import android.content.Context; - -import androidx.annotation.NonNull; - -import com.bumptech.glide.GlideBuilder; -import com.bumptech.glide.annotation.GlideModule; -import com.bumptech.glide.load.DecodeFormat; -import com.bumptech.glide.load.engine.cache.InternalCacheDiskCacheFactory; -import com.bumptech.glide.module.AppGlideModule; -import com.bumptech.glide.request.RequestOptions; - -import static com.javinator9889.handwashingreminder.utils.AndroidKt.isHighPerformingDevice; - -@GlideModule -public final class CustomGraphicsModule extends AppGlideModule { - @Override - public void applyOptions(@NonNull Context context, @NonNull GlideBuilder builder) { - builder.setDefaultRequestOptions( - new RequestOptions() - .format(DecodeFormat.PREFER_ARGB_8888) - ); - int diskCacheSizeBytes = isHighPerformingDevice() - ? (2 << 20) * 200 - : (2 << 20) * 20; // 200 MB or 20 MB - builder.setDiskCache( - new InternalCacheDiskCacheFactory(context, diskCacheSizeBytes) - ); - } -} diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/graphics/RecyclingImageView.java b/app/src/main/java/com/javinator9889/handwashingreminder/graphics/RecyclingImageView.java deleted file mode 100644 index 194d5c9..0000000 --- a/app/src/main/java/com/javinator9889/handwashingreminder/graphics/RecyclingImageView.java +++ /dev/null @@ -1,116 +0,0 @@ -/* - * Copyright (C) 2013 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.javinator9889.handwashingreminder.graphics; - -import android.content.Context; -import android.graphics.drawable.Drawable; -import android.graphics.drawable.LayerDrawable; -import android.util.AttributeSet; - -import androidx.annotation.DrawableRes; -import androidx.annotation.Nullable; -import androidx.appcompat.widget.AppCompatImageView; - -/** - * Sub-class of ImageView which automatically notifies the drawable when it is - * being displayed. - */ -public class RecyclingImageView extends AppCompatImageView { - @DrawableRes - private Integer mSavedDrawableRes = null; - - public RecyclingImageView(Context context) { - super(context); - } - - public RecyclingImageView(Context context, AttributeSet attrs) { - super(context, attrs); - } - - public void setSavedDrawableRes(@Nullable @DrawableRes Integer mSavedDrawableRes) { - this.mSavedDrawableRes = mSavedDrawableRes; - } - - @Nullable - @DrawableRes - public Integer getSavedDrawableRes() { - return mSavedDrawableRes; - } - - @Override - protected void onWindowVisibilityChanged(int visibility) { - super.onWindowVisibilityChanged(visibility); - if (mSavedDrawableRes != null && - visibility == VISIBLE && - getDrawable() == null) { - try { - GlideApp.with(this) - .load(mSavedDrawableRes) - .centerInside() - .into(this); - } catch (Exception ignored) { - setImageResource(mSavedDrawableRes); - } - } else if (visibility == INVISIBLE || visibility == GONE) - onDetachedFromWindow(); - } - - /** - * @see android.widget.ImageView#onDetachedFromWindow() - */ - @Override - public void onDetachedFromWindow() { - // This has been detached from Window, so clear the drawable - setImageDrawable(null); - - super.onDetachedFromWindow(); - } - - /** - * @see android.widget.ImageView#setImageDrawable(android.graphics.drawable.Drawable) - */ - @Override - public void setImageDrawable(Drawable drawable) { - // Keep hold of previous Drawable - final Drawable previousDrawable = getDrawable(); - - // Call super to set new Drawable - super.setImageDrawable(drawable); - - // Notify new Drawable that it is being displayed - notifyDrawable(drawable, true); - - // Notify old Drawable so it is no longer being displayed - notifyDrawable(previousDrawable, false); - } - - /** - * Notifies the drawable that it's displayed state has changed. - */ - private static void notifyDrawable(Drawable drawable, final boolean isDisplayed) { - if (drawable instanceof RecyclingBitmapDrawable) { - // The drawable is a CountingBitmapDrawable, so notify it - ((RecyclingBitmapDrawable) drawable).setIsDisplayed(isDisplayed); - } else if (drawable instanceof LayerDrawable) { - // The drawable is a LayerDrawable, so recurse on each layer - LayerDrawable layerDrawable = (LayerDrawable) drawable; - for (int i = 0, z = layerDrawable.getNumberOfLayers(); i < z; i++) { - notifyDrawable(layerDrawable.getDrawable(i), isDisplayed); - } - } - } - -} diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/graphics/RecyclingImageView.kt b/app/src/main/java/com/javinator9889/handwashingreminder/graphics/RecyclingImageView.kt new file mode 100644 index 0000000..64a3818 --- /dev/null +++ b/app/src/main/java/com/javinator9889/handwashingreminder/graphics/RecyclingImageView.kt @@ -0,0 +1,103 @@ +/* + * Copyright © 2020 - present | Handwashing reminder by Javinator9889 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see https://www.gnu.org/licenses/. + * + * Created by Javinator9889 on 1/07/20 - Handwashing reminder. + */ +package com.javinator9889.handwashingreminder.graphics + +import android.content.Context +import android.graphics.drawable.Drawable +import android.graphics.drawable.LayerDrawable +import android.util.AttributeSet +import android.view.View +import androidx.annotation.DrawableRes +import androidx.appcompat.widget.AppCompatImageView +import coil.api.load + +class RecyclingImageView : AppCompatImageView { + constructor(context: Context) : super(context) + constructor(context: Context, attrs: AttributeSet) : super(context, attrs) + constructor( + context: Context, + attrs: AttributeSet, + defStyleAttr: Int + ) : super(context, attrs, defStyleAttr) + + @DrawableRes + var savedDrawableRes: Int? = null + + override fun onWindowVisibilityChanged(visibility: Int) { + super.onWindowVisibilityChanged(visibility) + if (savedDrawableRes != null + && visibility == View.VISIBLE + && drawable != null + ) + try { + load(savedDrawableRes!!) + } catch (ignored: Throwable) { + setImageResource(savedDrawableRes!!) + } + else if (visibility == View.INVISIBLE || visibility == View.GONE) + onDetachedFromWindow() + } + + /** + * @see android.widget.ImageView.onDetachedFromWindow + */ + public override fun onDetachedFromWindow() { + // This has been detached from Window, so clear the drawable + setImageDrawable(null) + super.onDetachedFromWindow() + } + + /** + * @see android.widget.ImageView.setImageDrawable + */ + override fun setImageDrawable(drawable: Drawable?) { + // Keep hold of previous Drawable + val previousDrawable = getDrawable() + + // Call super to set new Drawable + super.setImageDrawable(drawable) + + // Notify new Drawable that it is being displayed + notifyDrawable(drawable, true) + + // Notify old Drawable so it is no longer being displayed + notifyDrawable(previousDrawable, false) + } + + /** + * Notifies the drawable that it's displayed state has changed. + */ + private fun notifyDrawable( + drawable: Drawable?, + isDisplayed: Boolean + ) { + if (drawable is RecyclingBitmapDrawable) { + // The drawable is a CountingBitmapDrawable, so notify it + drawable.setIsDisplayed(isDisplayed) + } else if (drawable is LayerDrawable) { + // The drawable is a LayerDrawable, so recurse on each layer + var i = 0 + val z = drawable.numberOfLayers + while (i < z) { + notifyDrawable(drawable.getDrawable(i), isDisplayed) + i++ + } + } + } +} \ No newline at end of file diff --git a/app/src/main/res/layout/news_card_view.xml b/app/src/main/res/layout/news_card_view.xml index d652344..6ebb48f 100644 --- a/app/src/main/res/layout/news_card_view.xml +++ b/app/src/main/res/layout/news_card_view.xml @@ -98,6 +98,7 @@ android:layout_width="20dp" android:layout_height="20dp" app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toStartOf="@+id/ws_name" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> @@ -106,8 +107,10 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="8dp" + android:maxWidth="120dp" android:textSize="12sp" app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toStartOf="@+id/bullet" app:layout_constraintStart_toEndOf="@+id/ws_logo" app:layout_constraintTop_toTopOf="parent" tools:text="Website name" /> diff --git a/appintro/src/main/java/com/javinator9889/handwashingreminder/appintro/timeconfig/TimeConfigActivity.kt b/appintro/src/main/java/com/javinator9889/handwashingreminder/appintro/timeconfig/TimeConfigActivity.kt index 03e903c..865c61f 100644 --- a/appintro/src/main/java/com/javinator9889/handwashingreminder/appintro/timeconfig/TimeConfigActivity.kt +++ b/appintro/src/main/java/com/javinator9889/handwashingreminder/appintro/timeconfig/TimeConfigActivity.kt @@ -30,10 +30,10 @@ import android.widget.TextView import android.widget.TimePicker import androidx.constraintlayout.widget.ConstraintLayout import androidx.core.view.ViewCompat +import coil.api.load import com.google.android.play.core.splitcompat.SplitCompat import com.javinator9889.handwashingreminder.activities.support.ActionBarBase import com.javinator9889.handwashingreminder.appintro.R -import com.javinator9889.handwashingreminder.graphics.GlideApp import com.javinator9889.handwashingreminder.utils.AndroidVersion import com.javinator9889.handwashingreminder.utils.TimeConfig import com.javinator9889.handwashingreminder.utils.formatTime @@ -129,10 +129,7 @@ class TimeConfigActivity : } if (imageRes != null) try { - GlideApp.with(this) - .load(imageRes) - .centerInside() - .into(image) + image.load(imageRes) } catch (e: Exception) { Timber.e(e, "Error while loading Glide view") image.setImageResource(imageRes) diff --git a/appintro/src/main/java/com/javinator9889/handwashingreminder/appintro/timeconfig/TimeConfigItem.kt b/appintro/src/main/java/com/javinator9889/handwashingreminder/appintro/timeconfig/TimeConfigItem.kt index 1720ca2..4ed510e 100644 --- a/appintro/src/main/java/com/javinator9889/handwashingreminder/appintro/timeconfig/TimeConfigItem.kt +++ b/appintro/src/main/java/com/javinator9889/handwashingreminder/appintro/timeconfig/TimeConfigItem.kt @@ -22,8 +22,8 @@ import android.view.View import android.widget.TextView import androidx.annotation.LayoutRes import androidx.cardview.widget.CardView +import coil.api.load import com.javinator9889.handwashingreminder.appintro.R -import com.javinator9889.handwashingreminder.graphics.GlideApp import com.javinator9889.handwashingreminder.graphics.RecyclingImageView import com.javinator9889.handwashingreminder.utils.TimeConfig import com.javinator9889.handwashingreminder.utils.notNull @@ -70,10 +70,7 @@ class TimeConfigItem( TimeConfig.DINNER_ID -> R.drawable.ic_dinner else -> null }.notNull { - GlideApp.with(view) - .load(it) - .centerInside() - .into(image) + image.load(it) image.savedDrawableRes = it } } diff --git a/bundledemoji/build.gradle b/bundledemoji/build.gradle index 6898fb8..51644f3 100644 --- a/bundledemoji/build.gradle +++ b/bundledemoji/build.gradle @@ -13,7 +13,14 @@ android { versionName "1.0" } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = JavaVersion.VERSION_1_8.toString() + } } From 7884aa796722b09f68862673e06d1ab8fa2632c1 Mon Sep 17 00:00:00 2001 From: Javinator9889 Date: Wed, 1 Jul 2020 20:30:14 +0200 Subject: [PATCH 87/95] Migrated from Glide to Coil - tests needed for detecting possible issues --- app/build.gradle | 2 +- .../views/fragments/news/NewsFragment.kt | 2 +- .../views/fragments/news/adapter/News.kt | 41 +++++++++++++------ app/src/main/res/layout/news_card_view.xml | 3 +- 4 files changed, 33 insertions(+), 15 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index dba7b5f..e4fd328 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -40,7 +40,7 @@ android { applicationId "com.javinator9889.handwashingreminder" minSdkVersion 17 targetSdkVersion 29 - versionCode 133 + versionCode 134 versionName "1.2.0-${gitCommitHash}" multiDexEnabled true resConfigs "en", "es" diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/news/NewsFragment.kt b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/news/NewsFragment.kt index 5bbb985..aeebd52 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/news/NewsFragment.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/news/NewsFragment.kt @@ -84,7 +84,7 @@ class NewsFragment : BaseFragmentView(), LayoutVisibilityChange { imageUrl = it.elements?.url, website = it.website?.name, websiteImageUrl = it.website?.iconURL, - lifecycleScope = this@NewsFragment.lifecycleScope + lifecycleOwner = this@NewsFragment ) newsAdapter.add(newsObject) loading.visibility = View.INVISIBLE diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/news/adapter/News.kt b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/news/adapter/News.kt index 446f3b8..d059c38 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/news/adapter/News.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/news/adapter/News.kt @@ -22,10 +22,11 @@ import android.annotation.SuppressLint import android.view.View import android.widget.ImageView import android.widget.TextView -import androidx.lifecycle.LifecycleCoroutineScope +import androidx.lifecycle.LifecycleOwner +import androidx.lifecycle.lifecycleScope import coil.Coil import coil.api.load -import coil.request.GetRequest +import coil.size.Scale import com.airbnb.lottie.LottieAnimationView import com.google.android.material.card.MaterialCardView import com.javinator9889.handwashingreminder.R @@ -45,7 +46,7 @@ data class News( val imageUrl: String?, val website: String?, val websiteImageUrl: String?, - val lifecycleScope: LifecycleCoroutineScope, + val lifecycleOwner: LifecycleOwner, override val layoutRes: Int = R.layout.news_card_view, override val type: Int = 1 ) : AbstractItem() { @@ -68,29 +69,45 @@ data class News( val formatter = DateFormat.getDateTimeInstance() val context = view.context imageHeader.setAnimation(R.raw.downloading) - item.lifecycleScope.launch(context = Dispatchers.Main) { + item.lifecycleOwner.lifecycleScope.launch(Dispatchers.Main) { title.text = item.title description.text = item.short val imageLoader = Coil.imageLoader(view.context) if (item.imageUrl != null) { - val request = GetRequest.Builder(view.context) + imageHeader.load(item.imageUrl) { + scale(Scale.FILL) + lifecycle(item.lifecycleOwner) + allowHardware(true) + } + /*val request = GetRequest.Builder(view.context) .data(item.imageUrl) - .size(imageHeader.width, imageHeader.height) .allowHardware(true) + .scale(Scale.FILL) .build() launch(Dispatchers.IO) { - imageHeader.load(imageLoader.execute(request).drawable) - } + val drawable = imageLoader.execute(request).drawable + withContext(Dispatchers.Main) { + imageHeader.setImageDrawable(drawable) + } + }*/ } else imageHeader.visibility = View.GONE if (item.websiteImageUrl != null) { - val request = GetRequest.Builder(view.context) + websiteLogo.load(item.websiteImageUrl) { + scale(Scale.FILL) + lifecycle(item.lifecycleOwner) + allowHardware(true) + } + /*val request = GetRequest.Builder(view.context) .data(item.websiteImageUrl) - .size(websiteLogo.width, websiteLogo.height) .allowHardware(true) + .scale(Scale.FILL) .build() launch(Dispatchers.IO) { - websiteLogo.load(imageLoader.execute(request).drawable) - } + val drawable = imageLoader.execute(request).drawable + withContext(Dispatchers.Main) { + websiteLogo.setImageDrawable(drawable) + } + }*/ } else websiteLogo.visibility = View.GONE websiteName.text = item.website ?: context.getString(R.string.no_website) diff --git a/app/src/main/res/layout/news_card_view.xml b/app/src/main/res/layout/news_card_view.xml index 6ebb48f..46e81a7 100644 --- a/app/src/main/res/layout/news_card_view.xml +++ b/app/src/main/res/layout/news_card_view.xml @@ -29,8 +29,9 @@ Date: Thu, 2 Jul 2020 17:48:45 +0200 Subject: [PATCH 88/95] Possible solution to an error with Alarms not working in time --- .../handwashingreminder/jobs/alarms/AlarmReceiver.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/jobs/alarms/AlarmReceiver.kt b/app/src/main/java/com/javinator9889/handwashingreminder/jobs/alarms/AlarmReceiver.kt index 7c436fd..70b6bfa 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/jobs/alarms/AlarmReceiver.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/jobs/alarms/AlarmReceiver.kt @@ -21,7 +21,6 @@ package com.javinator9889.handwashingreminder.jobs.alarms import android.content.BroadcastReceiver import android.content.Context import android.content.Intent -import com.javinator9889.handwashingreminder.application.HandwashingApplication import com.javinator9889.handwashingreminder.jobs.workers.BreakfastNotificationWorker import com.javinator9889.handwashingreminder.jobs.workers.DinnerNotificationWorker import com.javinator9889.handwashingreminder.jobs.workers.LunchNotificationWorker @@ -36,6 +35,6 @@ class AlarmReceiver : BroadcastReceiver() { Alarms.DINNER_ALARM.identifier -> DinnerNotificationWorker(context) else -> return } - goAsync(coroutineScope = HandwashingApplication.scope) { worker.doWork() } + goAsync { worker.doWork() } } } \ No newline at end of file From 07aeee769ddb70384cd65403cd6601976fa951e6 Mon Sep 17 00:00:00 2001 From: Javinator9889 Date: Thu, 2 Jul 2020 19:21:14 +0200 Subject: [PATCH 89/95] Solved a problem with notifications - they should be working now --- .../activities/LauncherActivity.kt | 92 +++++++++++-------- .../data/SettingsLoader.kt | 22 +++++ .../handwashingreminder/jobs/alarms/Alarms.kt | 30 +++++- .../workers/ScheduledNotificationWorker.kt | 11 ++- .../notifications/NotificationsHandler.kt | 41 +++++---- app/src/main/res/values-es/strings.xml | 4 + app/src/main/res/values/strings.xml | 4 + app/src/main/res/xml/preferences.xml | 5 + .../appintro/timeconfig/TimeConfigItem.kt | 7 +- .../src/main/res/layout/time_card_view.xml | 2 +- 10 files changed, 151 insertions(+), 67 deletions(-) diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/activities/LauncherActivity.kt b/app/src/main/java/com/javinator9889/handwashingreminder/activities/LauncherActivity.kt index 53e3202..915bbb2 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/activities/LauncherActivity.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/activities/LauncherActivity.kt @@ -46,6 +46,8 @@ import com.javinator9889.handwashingreminder.gms.activity.ActivityHandler import com.javinator9889.handwashingreminder.gms.ads.AdLoader import com.javinator9889.handwashingreminder.gms.ads.AdsEnabler import com.javinator9889.handwashingreminder.jobs.alarms.AlarmHandler +import com.javinator9889.handwashingreminder.jobs.alarms.Alarms +import com.javinator9889.handwashingreminder.notifications.NotificationsHandler import com.javinator9889.handwashingreminder.utils.* import com.javinator9889.handwashingreminder.utils.Preferences.ADS_ENABLED import com.javinator9889.handwashingreminder.utils.Preferences.APP_INIT_KEY @@ -102,40 +104,41 @@ class LauncherActivity : AppCompatActivity() { setContentView(R.layout.splash_screen) } - private fun showWelcomeScreenAsync() = lifecycleScope.launch(Dispatchers.Main) { - app.firebaseInitDeferred.await() - val isThereAnySpecialEvent = with(Firebase.remoteConfig) { - getBoolean(SPECIAL_EVENT) && !launchFromNotification - } - var sleepDuration = 0L - val animationLoaded = CompletableDeferred() - val fadeInAnimation = AnimationUtils.loadAnimation( - this@LauncherActivity, - android.R.anim.fade_in - ) - fadeInAnimation.duration = 300L - fadeInAnimation.setAnimationListener(object : - Animation.AnimationListener { - override fun onAnimationStart(animation: Animation?) {} - override fun onAnimationRepeat(animation: Animation?) {} - override fun onAnimationEnd(animation: Animation?) { - animationLoaded.complete(true) - logo.playAnimation() + private fun showWelcomeScreenAsync() = + lifecycleScope.launch(Dispatchers.Main) { + app.firebaseInitDeferred.await() + val isThereAnySpecialEvent = with(Firebase.remoteConfig) { + getBoolean(SPECIAL_EVENT) && !launchFromNotification } - }) - if (isThereAnySpecialEvent) { - logo.setAnimation(AnimatedResources.STAY_SAFE_STAY_HOME.res) - logo.addLottieOnCompositionLoadedListener { + var sleepDuration = 0L + val animationLoaded = CompletableDeferred() + val fadeInAnimation = AnimationUtils.loadAnimation( + this@LauncherActivity, + android.R.anim.fade_in + ) + fadeInAnimation.duration = 300L + fadeInAnimation.setAnimationListener(object : + Animation.AnimationListener { + override fun onAnimationStart(animation: Animation?) {} + override fun onAnimationRepeat(animation: Animation?) {} + override fun onAnimationEnd(animation: Animation?) { + animationLoaded.complete(true) + logo.playAnimation() + } + }) + if (isThereAnySpecialEvent) { + logo.setAnimation(AnimatedResources.STAY_SAFE_STAY_HOME.res) + logo.addLottieOnCompositionLoadedListener { + logo.startAnimation(fadeInAnimation) + sleepDuration = logo.duration + } + animationLoaded.await() + delay(sleepDuration) + } else { + logo.setImageResource(R.drawable.handwashing_app_logo) logo.startAnimation(fadeInAnimation) - sleepDuration = logo.duration } - animationLoaded.await() - delay(sleepDuration) - } else { - logo.setImageResource(R.drawable.handwashing_app_logo) - logo.startAnimation(fadeInAnimation) } - } override fun onActivityResult( requestCode: Int, @@ -148,7 +151,8 @@ class LauncherActivity : AppCompatActivity() { return } if (Ads.MODULE_NAME in splitInstallManager.installedModules && - sharedPreferences.getBoolean(ADS_ENABLED, true)) { + sharedPreferences.getBoolean(ADS_ENABLED, true) + ) { when (resultCode) { Activity.RESULT_OK -> { createPackageContext(packageName, 0).also { @@ -160,7 +164,8 @@ class LauncherActivity : AppCompatActivity() { } } if (sharedPreferences.getBoolean(APP_INIT_KEY, false) && - AppIntro.MODULE_NAME in splitInstallManager.installedModules) { + AppIntro.MODULE_NAME in splitInstallManager.installedModules + ) { data?.let { val launchIntent = Intent(data) createPackageContext(packageName, 0).also { @@ -190,7 +195,8 @@ class LauncherActivity : AppCompatActivity() { Timber.d("Required to install modules: $modules") if (modules.isEmpty()) { val intent = if (AppIntro.MODULE_NAME in installedModules && - !sharedPreferences.getBoolean(APP_INIT_KEY, false)) { + !sharedPreferences.getBoolean(APP_INIT_KEY, false) + ) { with(Intent()) { setClassName( BuildConfig.APPLICATION_ID, @@ -238,7 +244,8 @@ class LauncherActivity : AppCompatActivity() { private fun initAds(context: Context = app) { if (Ads.MODULE_NAME in splitInstallManager.installedModules && - sharedPreferences.getBoolean(ADS_ENABLED, true)) { + sharedPreferences.getBoolean(ADS_ENABLED, true) + ) { val className = "${Ads.PACKAGE_NAME}.${Ads .CLASS_NAME}\$${Ads.PROVIDER_NAME}" val adProvider = Class.forName(className).kotlin @@ -268,6 +275,8 @@ class LauncherActivity : AppCompatActivity() { app.firebaseInitDeferred.await() } Timber.d("Firebase initialized correctly") + Timber.d("Setting-up Firebase custom properties") + val propertiesJob = setupFirebasePropertiesAsync() Timber.d("Initializing Iconics") Iconics.init(this) Timber.d("Setting-up activity recognition") @@ -288,8 +297,19 @@ class LauncherActivity : AppCompatActivity() { Timber.d("Initializing Ads Provider") initAds() Timber.d("Adding periodic notifications if not enqueued yet") - Timber.d("Setting-up Firebase custom properties") - setupFirebasePropertiesAsync().join() + Timber.d("Creating alarms notification channels...") + for (alarm in Alarms.values()) { + Timber.d("Creating notification channel for ${alarm.identifier}") + NotificationsHandler( + context = this, + channelId = alarm.channelId, + channelName = getString(R.string.time_notification_channel_name), + channelDesc = getString(R.string.time_notification_channel_desc), + groupId = alarm.identifier, + groupName = getString(alarm.groupName) + ) + } + propertiesJob.join() } private fun setupFirebasePropertiesAsync() = lifecycleScope.launch { diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/data/SettingsLoader.kt b/app/src/main/java/com/javinator9889/handwashingreminder/data/SettingsLoader.kt index f2608c6..8892291 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/data/SettingsLoader.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/data/SettingsLoader.kt @@ -23,6 +23,7 @@ import android.content.ClipDescription import android.content.Intent import android.net.Uri import android.os.Bundle +import android.provider.Settings import androidx.annotation.StringRes import androidx.emoji.text.EmojiCompat import androidx.lifecycle.LifecycleOwner @@ -316,6 +317,27 @@ class SettingsLoader( Preferences.PERFORMANCE_ANIMATIONS, Ionicons.Icon.ion_battery_low ).also { deferreds.add(it) } + setupPreferenceAsync( + "notifications:settings", + onClickListener = { + val intent = when { + isAtLeast(AndroidVersion.O) -> Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS).apply { + putExtra(Settings.EXTRA_APP_PACKAGE, requireContext().packageName) + } + isAtLeast(AndroidVersion.LOLLIPOP) -> Intent("android.settings.APP_NOTIFICATION_SETTINGS").apply { + putExtra("app_package", requireContext().packageName) + putExtra("app_uid", requireContext().applicationInfo.uid) + } + else -> Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply { + addCategory(Intent.CATEGORY_DEFAULT) + data = Uri.parse("package: ${requireContext().packageName}") + } + } + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + requireContext().startActivity(intent) + true + } + ).also { deferreds.add(it) } deferreds.awaitAll() arePreferencesInitialized.set(true) } diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/jobs/alarms/Alarms.kt b/app/src/main/java/com/javinator9889/handwashingreminder/jobs/alarms/Alarms.kt index 4353aa2..3e0593e 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/jobs/alarms/Alarms.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/jobs/alarms/Alarms.kt @@ -18,14 +18,36 @@ */ package com.javinator9889.handwashingreminder.jobs.alarms +import androidx.annotation.StringRes +import com.javinator9889.handwashingreminder.R import com.javinator9889.handwashingreminder.utils.Preferences enum class Alarms( val identifier: String, val code: Int, - val preferenceKey: String + val preferenceKey: String, + @StringRes val groupName: Int, + val channelId: String ) { - BREAKFAST_ALARM("alarms:breakfast", 0, Preferences.BREAKFAST_TIME), - LUNCH_ALARM("alarms:lunch", 1, Preferences.LUNCH_TIME), - DINNER_ALARM("alarms:dinner", 2, Preferences.DINNER_TIME) + BREAKFAST_ALARM( + "alarms:breakfast", + 0, + Preferences.BREAKFAST_TIME, + R.string.breakfast_notifications, + "notifications:breakfast" + ), + LUNCH_ALARM( + "alarms:lunch", + 1, + Preferences.LUNCH_TIME, + R.string.lunch_notifications, + "notifications:lunch" + ), + DINNER_ALARM( + "alarms:dinner", + 2, + Preferences.DINNER_TIME, + R.string.dinner_notifications, + "notifications:dinner" + ) } \ No newline at end of file diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/jobs/workers/ScheduledNotificationWorker.kt b/app/src/main/java/com/javinator9889/handwashingreminder/jobs/workers/ScheduledNotificationWorker.kt index 11a44f8..e48872b 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/jobs/workers/ScheduledNotificationWorker.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/jobs/workers/ScheduledNotificationWorker.kt @@ -21,13 +21,13 @@ package com.javinator9889.handwashingreminder.jobs.workers import android.content.Context import androidx.annotation.ArrayRes import androidx.annotation.StringRes +import androidx.core.app.NotificationCompat import com.javinator9889.handwashingreminder.R import com.javinator9889.handwashingreminder.application.HandwashingApplication import com.javinator9889.handwashingreminder.emoji.EmojiLoader import com.javinator9889.handwashingreminder.jobs.alarms.AlarmHandler import com.javinator9889.handwashingreminder.jobs.alarms.Alarms import com.javinator9889.handwashingreminder.notifications.NotificationsHandler -import com.javinator9889.handwashingreminder.utils.TIME_CHANNEL_ID import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.withContext @@ -46,9 +46,11 @@ abstract class ScheduledNotificationWorker(context: Context) { val emojiLoader = EmojiLoader.loadAsync(context) val notificationsHandler = NotificationsHandler( context = context, - channelId = TIME_CHANNEL_ID, + channelId = alarm.channelId, channelName = getString(R.string.time_notification_channel_name), - channelDesc = getString(R.string.time_notification_channel_desc) + channelDesc = getString(R.string.time_notification_channel_desc), + groupId = alarm.identifier, + groupName = getString(alarm.groupName) ) val emojiCompat = emojiLoader.await() var title = getText(titleRes) @@ -64,7 +66,8 @@ abstract class ScheduledNotificationWorker(context: Context) { largeIcon = R.drawable.handwashing_app_logo, title = title, content = content, - longContent = content + longContent = content, + priority = NotificationCompat.PRIORITY_MAX ) } Timber.d( diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/notifications/NotificationsHandler.kt b/app/src/main/java/com/javinator9889/handwashingreminder/notifications/NotificationsHandler.kt index e16ec20..a59953e 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/notifications/NotificationsHandler.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/notifications/NotificationsHandler.kt @@ -19,6 +19,7 @@ package com.javinator9889.handwashingreminder.notifications import android.app.NotificationChannel +import android.app.NotificationChannelGroup import android.app.NotificationManager import android.app.PendingIntent import android.content.Context @@ -31,7 +32,6 @@ import androidx.annotation.DrawableRes import androidx.annotation.StringRes import androidx.core.app.NotificationCompat import androidx.core.app.NotificationManagerCompat -import androidx.core.content.edit import androidx.preference.PreferenceManager import com.javinator9889.handwashingreminder.R import com.javinator9889.handwashingreminder.activities.FAST_START_KEY @@ -47,20 +47,30 @@ class NotificationsHandler( private val context: Context, private val channelId: String, private val channelName: String = "", - private val channelDesc: String = "" + private val channelDesc: String = "", + private val groupId: String = "", + private val groupName: String = "" ) { private val preferences: SharedPreferences = PreferenceManager.getDefaultSharedPreferences(context) private val notificationId = 1 private val vibrationPattern = longArrayOf(300L, 300L, 300L, 300L) + private val manager = + context.getSystemService(Context.NOTIFICATION_SERVICE) as + NotificationManager init { - if (isNotificationChannelCreated() || createChannelRequired()) { - createNotificationChannel() - preferences.edit { - putBoolean(Preferences.CREATE_CHANNEL_KEY, false) - } + if (groupId.isNotEmpty() && groupName.isNotEmpty() + && isAtLeast(AndroidVersion.O) + ) { + manager.createNotificationChannelGroup( + NotificationChannelGroup( + groupId, + groupName + ) + ) } + createNotificationChannel(groupId) } fun createNotification( @@ -165,7 +175,7 @@ class NotificationsHandler( } } - private fun createNotificationChannel() { + private fun createNotificationChannel(groupId: String = "") { if (isAtLeast(AndroidVersion.O)) { val importance = NotificationManager.IMPORTANCE_HIGH val that = this @@ -175,19 +185,15 @@ class NotificationsHandler( description = channelDesc vibrationPattern = that.vibrationPattern enableVibration(true) + if (groupId.isNotEmpty()) + group = groupId } - val notificationManager: NotificationManager = - context.getSystemService(Context.NOTIFICATION_SERVICE) as - NotificationManager - notificationManager.createNotificationChannel(channel) + manager.createNotificationChannel(channel) } } private fun isNotificationChannelCreated(): Boolean { if (isAtLeast(AndroidVersion.O)) { - val manager = context - .getSystemService(Context.NOTIFICATION_SERVICE) as - NotificationManager val channel = manager.getNotificationChannel(channelId) channel?.let { return it.importance != NotificationManager.IMPORTANCE_NONE @@ -198,7 +204,6 @@ class NotificationsHandler( } } - private fun createChannelRequired(): Boolean { - return preferences.getBoolean(Preferences.CREATE_CHANNEL_KEY, true) - } + private fun createChannelRequired() = + preferences.getBoolean(Preferences.CREATE_CHANNEL_KEY, true) } \ No newline at end of file diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 4bb1a48..ca5ad10 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -68,6 +68,9 @@ ¿30 segundos por una buena salud? ¡Dónde firmo! Solo lávate las manos 😉💦👏 + Recordatorios en el desayuno + Recordatorios en la comida + Recordatorios en la cenas ¡Hora de comer y lavarse las manos! 🍱 @@ -291,4 +294,5 @@ Ten en cuenta que esto puede afectar tanto al rendimiento como a la batería No se mostrará ninguna animación Error al cargar información sobre las noticias + Ajustes de notificaciones diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index f6df4e1..b08dcaf 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -69,6 +69,7 @@ 30 seconds for good health? Where do I sign! Just wash your hands 😉💦👏 + Breakfast reminders Time to lunch and wash your hands! 🍱 @@ -79,6 +80,7 @@ If you have gone to the street is even more important to wash your hands before having lunch 💦👏 + Lunch reminders Let\'s have a clean dinner 🥗 The day is finishing and you can do it fully by @@ -88,6 +90,7 @@ Do you know that washing your hands can keep you safe from more than 15 diseases? That\'s a lot 😮 + Dinner reminders Let\'s learn how to wash our hands Join me in this trip @@ -319,4 +322,5 @@ Keep in mind that it can affect both performance and battery life No animations will be played Error while loading news data + Edit notification settings diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml index f1e5a8e..a091e21 100644 --- a/app/src/main/res/xml/preferences.xml +++ b/app/src/main/res/xml/preferences.xml @@ -49,6 +49,11 @@ android:key="app:dinner" android:selectAllOnFocus="true" android:singleLine="true" /> + + diff --git a/appintro/src/main/java/com/javinator9889/handwashingreminder/appintro/timeconfig/TimeConfigItem.kt b/appintro/src/main/java/com/javinator9889/handwashingreminder/appintro/timeconfig/TimeConfigItem.kt index 4ed510e..59016de 100644 --- a/appintro/src/main/java/com/javinator9889/handwashingreminder/appintro/timeconfig/TimeConfigItem.kt +++ b/appintro/src/main/java/com/javinator9889/handwashingreminder/appintro/timeconfig/TimeConfigItem.kt @@ -19,12 +19,12 @@ package com.javinator9889.handwashingreminder.appintro.timeconfig import android.view.View +import android.widget.ImageView import android.widget.TextView import androidx.annotation.LayoutRes import androidx.cardview.widget.CardView import coil.api.load import com.javinator9889.handwashingreminder.appintro.R -import com.javinator9889.handwashingreminder.graphics.RecyclingImageView import com.javinator9889.handwashingreminder.utils.TimeConfig import com.javinator9889.handwashingreminder.utils.notNull import com.mikepenz.fastadapter.FastAdapter @@ -52,7 +52,7 @@ class TimeConfigItem( private val hours: TextView = view.findViewById(R.id.hours) private val ddot: TextView = view.findViewById(R.id.ddot) private val minutes: TextView = view.findViewById(R.id.minutes) - private val image: RecyclingImageView = view.findViewById(R.id.infoImage) + private val image: ImageView = view.findViewById(R.id.infoImage) private val clockIcon: IconicsImageView = view.findViewById(R.id.clockIcon) val cardView: CardView = view.findViewById(R.id.timeCard) @@ -71,7 +71,6 @@ class TimeConfigItem( else -> null }.notNull { image.load(it) - image.savedDrawableRes = it } } @@ -80,7 +79,7 @@ class TimeConfigItem( hours.text = null ddot.text = null minutes.text = null - image.onDetachedFromWindow() + image.setImageDrawable(null) clockIcon.icon = null } } diff --git a/appintro/src/main/res/layout/time_card_view.xml b/appintro/src/main/res/layout/time_card_view.xml index 2ba32f4..9b95689 100644 --- a/appintro/src/main/res/layout/time_card_view.xml +++ b/appintro/src/main/res/layout/time_card_view.xml @@ -60,7 +60,7 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/title"> - Date: Thu, 2 Jul 2020 20:01:40 +0200 Subject: [PATCH 90/95] Custom action for incrementing the hand-washed value when clicking notification --- app/src/main/AndroidManifest.xml | 8 ++ .../gms/activity/ActivityReceiver.kt | 3 +- .../jobs/HandsWashedReceiver.kt | 77 +++++++++++++++++++ .../workers/ScheduledNotificationWorker.kt | 24 +++++- .../notifications/NotificationsHandler.kt | 39 +++++++--- app/src/main/res/values-es/strings.xml | 2 + app/src/main/res/values/strings.xml | 2 + 7 files changed, 142 insertions(+), 13 deletions(-) create mode 100644 app/src/main/java/com/javinator9889/handwashingreminder/jobs/HandsWashedReceiver.kt diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 19a171c..7558e6a 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -55,6 +55,14 @@ + + + + + + diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/gms/activity/ActivityReceiver.kt b/app/src/main/java/com/javinator9889/handwashingreminder/gms/activity/ActivityReceiver.kt index cd52308..d7b2845 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/gms/activity/ActivityReceiver.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/gms/activity/ActivityReceiver.kt @@ -146,7 +146,8 @@ class ActivityReceiver : BroadcastReceiver() { largeIcon = R.drawable.handwashing_app_logo, title = title, content = content, - longContent = content + longContent = content, + notificationId = 2 ) } withContext(Dispatchers.IO) { diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/jobs/HandsWashedReceiver.kt b/app/src/main/java/com/javinator9889/handwashingreminder/jobs/HandsWashedReceiver.kt new file mode 100644 index 0000000..08b0fbd --- /dev/null +++ b/app/src/main/java/com/javinator9889/handwashingreminder/jobs/HandsWashedReceiver.kt @@ -0,0 +1,77 @@ +/* + * Copyright © 2020 - present | Handwashing reminder by Javinator9889 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see https://www.gnu.org/licenses/. + * + * Created by Javinator9889 on 2/07/20 - Handwashing reminder. + */ +package com.javinator9889.handwashingreminder.jobs + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.widget.Toast +import androidx.core.app.NotificationManagerCompat +import com.javinator9889.handwashingreminder.R +import com.javinator9889.handwashingreminder.data.repositories.HandwashingRepository +import com.javinator9889.handwashingreminder.data.room.db.HandwashingDatabase +import com.javinator9889.handwashingreminder.data.room.entities.Handwashing +import com.javinator9889.handwashingreminder.emoji.EmojiLoader +import com.javinator9889.handwashingreminder.utils.calendar.CalendarUtils +import com.javinator9889.handwashingreminder.utils.goAsync +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext + +internal const val HANDS_WASHED_CODE = 128 +internal const val HANDS_WASHED_ACTION = + "com.javinator9889.handwashingreminder.HANDSWASHED_EVENT" + +class HandsWashedReceiver : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + val emojiLoader = EmojiLoader.loadAsync(context) + val repository = + with(HandwashingDatabase.getDatabase(context).handwashingDao()) { + HandwashingRepository(this) + } + with(NotificationManagerCompat.from(context)) { + cancel(1) + } + goAsync { + val createdItem = withContext(Dispatchers.IO) { + repository.get(CalendarUtils.today.time) + } + if (createdItem == null) { + withContext(Dispatchers.IO) { + repository.create(Handwashing(CalendarUtils.today.time, 0)) + } + } + withContext(Dispatchers.IO) { + repository.increment(CalendarUtils.today.time) + } + val emojiCompat = emojiLoader.await() + val toastText = context.getText(R.string.hurray) + withContext(Dispatchers.Main) { + try { + Toast.makeText( + context, + emojiCompat.process(toastText), + Toast.LENGTH_LONG + ).show() + } catch (_: IllegalStateException) { + Toast.makeText(context, toastText, Toast.LENGTH_LONG).show() + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/jobs/workers/ScheduledNotificationWorker.kt b/app/src/main/java/com/javinator9889/handwashingreminder/jobs/workers/ScheduledNotificationWorker.kt index e48872b..ac7f67e 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/jobs/workers/ScheduledNotificationWorker.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/jobs/workers/ScheduledNotificationWorker.kt @@ -18,15 +18,21 @@ */ package com.javinator9889.handwashingreminder.jobs.workers +import android.app.PendingIntent import android.content.Context +import android.content.Intent import androidx.annotation.ArrayRes import androidx.annotation.StringRes import androidx.core.app.NotificationCompat import com.javinator9889.handwashingreminder.R import com.javinator9889.handwashingreminder.application.HandwashingApplication import com.javinator9889.handwashingreminder.emoji.EmojiLoader +import com.javinator9889.handwashingreminder.jobs.HANDS_WASHED_ACTION +import com.javinator9889.handwashingreminder.jobs.HANDS_WASHED_CODE +import com.javinator9889.handwashingreminder.jobs.HandsWashedReceiver import com.javinator9889.handwashingreminder.jobs.alarms.AlarmHandler import com.javinator9889.handwashingreminder.jobs.alarms.Alarms +import com.javinator9889.handwashingreminder.notifications.Action import com.javinator9889.handwashingreminder.notifications.NotificationsHandler import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.coroutineScope @@ -59,7 +65,16 @@ abstract class ScheduledNotificationWorker(context: Context) { try { title = emojiCompat.process(title) content = emojiCompat.process(content) - } catch (_: IllegalStateException) { } + } catch (_: IllegalStateException) { + } + val washedPendingIntent = PendingIntent.getBroadcast( + context, + HANDS_WASHED_CODE, + Intent(context, HandsWashedReceiver::class.java).apply { + action = HANDS_WASHED_ACTION + }, + PendingIntent.FLAG_UPDATE_CURRENT + ) withContext(Dispatchers.Main) { notificationsHandler.createNotification( iconDrawable = R.drawable.ic_stat_handwashing, @@ -67,7 +82,12 @@ abstract class ScheduledNotificationWorker(context: Context) { title = title, content = content, longContent = content, - priority = NotificationCompat.PRIORITY_MAX + priority = NotificationCompat.PRIORITY_MAX, + action = Action( + R.drawable.ic_stat_handwashing, + getString(R.string.just_washed), + washedPendingIntent + ) ) } Timber.d( diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/notifications/NotificationsHandler.kt b/app/src/main/java/com/javinator9889/handwashingreminder/notifications/NotificationsHandler.kt index a59953e..5b1c3f7 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/notifications/NotificationsHandler.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/notifications/NotificationsHandler.kt @@ -48,12 +48,11 @@ class NotificationsHandler( private val channelId: String, private val channelName: String = "", private val channelDesc: String = "", - private val groupId: String = "", - private val groupName: String = "" + groupId: String = "", + groupName: String = "" ) { private val preferences: SharedPreferences = PreferenceManager.getDefaultSharedPreferences(context) - private val notificationId = 1 private val vibrationPattern = longArrayOf(300L, 300L, 300L, 300L) private val manager = context.getSystemService(Context.NOTIFICATION_SERVICE) as @@ -79,7 +78,9 @@ class NotificationsHandler( @StringRes title: Int, @StringRes content: Int, priority: Int = NotificationCompat.PRIORITY_DEFAULT, - @StringRes longContent: Int = -1 + @StringRes longContent: Int = -1, + action: Action? = null, + notificationId: Int = -1 ) { val longContentProcessed = if (longContent != -1) context.getText(longContent) else null @@ -89,7 +90,9 @@ class NotificationsHandler( context.getText(title), context.getText(content), priority, - longContentProcessed + longContentProcessed, + action, + notificationId ) } @@ -99,7 +102,9 @@ class NotificationsHandler( title: CharSequence, content: CharSequence, priority: Int = NotificationCompat.PRIORITY_DEFAULT, - longContent: CharSequence? = null + longContent: CharSequence? = null, + action: Action? = null, + notificationId: Int = -1 ) { val bitmapIcon = if (isAtLeast(AndroidVersion.JELLY_BEAN_MR2)) { if (isAtLeast(AndroidVersion.P)) { @@ -121,7 +126,9 @@ class NotificationsHandler( title, content, priority, - longContent + longContent, + action, + notificationId ) } @@ -131,7 +138,9 @@ class NotificationsHandler( title: CharSequence, content: CharSequence, priority: Int = NotificationCompat.PRIORITY_DEFAULT, - longContent: CharSequence? = null + longContent: CharSequence? = null, + action: Action? = null, + notificationId: Int = -1 ) { val notifyIntent = Intent(context, LauncherActivity::class.java).apply { flags = @@ -158,6 +167,9 @@ class NotificationsHandler( setPriority(priority) setVibrate(vibrationPattern) setContentIntent(notifyPendingIntent) + action?.let { + addAction(action.drawable, action.text, action.pendingIntent) + } addAction( R.drawable.ic_share_black, context.getString(R.string.share), @@ -170,7 +182,8 @@ class NotificationsHandler( build() }.let { with(NotificationManagerCompat.from(context)) { - notify(notificationId, it) + val id = if (notificationId == -1) 1 else notificationId + notify(id, it) } } } @@ -206,4 +219,10 @@ class NotificationsHandler( private fun createChannelRequired() = preferences.getBoolean(Preferences.CREATE_CHANNEL_KEY, true) -} \ No newline at end of file +} + +data class Action( + @DrawableRes val drawable: Int, + val text: CharSequence, + val pendingIntent: PendingIntent +) \ No newline at end of file diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index ca5ad10..8e25685 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -295,4 +295,6 @@ No se mostrará ninguna animación Error al cargar información sobre las noticias Ajustes de notificaciones + ¡Acabo de lavarlas! + ¡Yupiiii! 🙌😱 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index b08dcaf..aff6b05 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -323,4 +323,6 @@ No animations will be played Error while loading news data Edit notification settings + Just washed them! + Hurray! 🙌😱 From 9999898c37def7f95ca6601d089c4cfb1fcef31c Mon Sep 17 00:00:00 2001 From: Javinator9889 Date: Thu, 2 Jul 2020 20:24:02 +0200 Subject: [PATCH 91/95] Solved an error with dates sorting --- .../fragments/diseases/DiseasesFragment.kt | 5 +++++ .../utils/calendar/Calendar.kt | 18 ++++++++++-------- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/diseases/DiseasesFragment.kt b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/diseases/DiseasesFragment.kt index 147958e..0d8f2c8 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/diseases/DiseasesFragment.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/diseases/DiseasesFragment.kt @@ -123,6 +123,7 @@ class DiseasesFragment : BaseFragmentView(), LayoutVisibilityChange, countChart.notifyDataSetChanged() countChart.fitScreen() countChart.moveViewToX(0F) + countChart.setVisibleXRangeMaximum(7F) countChart.invalidate() val todayAmount = handwashingViewModel.getAsync(CalendarUtils.today.time) @@ -195,6 +196,9 @@ class DiseasesFragment : BaseFragmentView(), LayoutVisibilityChange, ) ) handwashingViewModel.increment(CalendarUtils.today.time) + Timber.d("${CalendarUtils.lastWeek.time}") + Timber.d("${CalendarUtils.lastMonth.time}") + handwashingViewModel.create(Handwashing(CalendarUtils.lastWeek.time, 12)) leaves.visibility = View.VISIBLE if (!leaves.isAnimating) leaves.playAnimation() @@ -211,6 +215,7 @@ class DiseasesFragment : BaseFragmentView(), LayoutVisibilityChange, ) ) handwashingViewModel.decrement(CalendarUtils.today.time) + handwashingViewModel.create(Handwashing(CalendarUtils.lastMonth.time, 123)) } } } diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/utils/calendar/Calendar.kt b/app/src/main/java/com/javinator9889/handwashingreminder/utils/calendar/Calendar.kt index 11275ed..58f2a1d 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/utils/calendar/Calendar.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/utils/calendar/Calendar.kt @@ -32,25 +32,27 @@ object CalendarUtils { } val lastWeek: Calendar - get() = with(today) { - today.add(Calendar.DAY_OF_YEAR, -7) - today + get() { + val aWeekAgo = today + aWeekAgo.add(Calendar.DAY_OF_MONTH, -7) + return aWeekAgo } val lastMonth: Calendar - get() = with(today) { - today.add(Calendar.MONTH, -1) - today + get() { + val aMonthAgo = today + aMonthAgo.add(Calendar.MONTH, -1) + return aMonthAgo } fun timeBetweenIn( unit: TimeUnit, to: Long, - from: Long = Calendar.getInstance().timeInMillis + from: Long = today.timeInMillis ): Long = unit.convert(timeBetween(to, from), TimeUnit.MILLISECONDS) fun timeBetween( to: Long, - from: Long = Calendar.getInstance().timeInMillis + from: Long = today.timeInMillis ): Long = from - to } \ No newline at end of file From 0f60320daf0512174c247245af9dc96995ae1ab5 Mon Sep 17 00:00:00 2001 From: Javinator9889 Date: Thu, 2 Jul 2020 20:25:49 +0200 Subject: [PATCH 92/95] Finished issues #14, #13, #11, #10 --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index e4fd328..1fceafa 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -40,7 +40,7 @@ android { applicationId "com.javinator9889.handwashingreminder" minSdkVersion 17 targetSdkVersion 29 - versionCode 134 + versionCode 135 versionName "1.2.0-${gitCommitHash}" multiDexEnabled true resConfigs "en", "es" From fe896311f8b1b70708a8e51f55678aa90ab53af8 Mon Sep 17 00:00:00 2001 From: Javinator9889 Date: Thu, 2 Jul 2020 20:33:03 +0200 Subject: [PATCH 93/95] Activity recognition properly working (issue #12) --- app/src/main/res/values-es/strings.xml | 20 ++++++++++---------- app/src/main/res/values/strings.xml | 16 ++++++++-------- appintro/src/main/res/values-es/strings.xml | 2 +- appintro/src/main/res/values/strings.xml | 4 ++-- 4 files changed, 21 insertions(+), 21 deletions(-) diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 8e25685..7ca6c4e 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -13,25 +13,25 @@ ¿Disfrutaste del paseo? 🚶 Andar 30 minutos al día - es una de las mejores prácticas para tener una buena salud 💪 + es una de las mejores prácticas para tener una buena salud 💪\n Recuerda lavarte las manos antes de comer o tocarte la cara 🤭 y así podrás tener todavía mejor salud 😉 ¿Qué tal fue la carrera? 🏃‍♀️ Siempre es divertido - correr 🙂 pero sueles acabar sudando mucho 😅. Intenta + correr 🙂 pero sueles acabar sudando mucho 😅.\nIntenta lavarte las manos lo antes posible o, mejor todavía, date una ducha 🛀 - ¿Teniendo una buena vuelta? + ¿Pasando un buen rato? 🚴‍♀️ Siempre disfruto el - ir en bici - te hace sentir libre 🌲. Los manillares de tu + ir en bici - te hace sentir libre 🌲.\nLos manillares de tu bici posiblemente estuvieran sucios así que lávate las manos lo antes que puedas 💦👏 Hora de lavarse las manos - 🚉 + 🚗🚉 En el transporte - público hay mucha gente y no sabes si están enfermos o no 😷. + público hay mucha gente y no sabes si están enfermos o no 😷.\n Incluso en tu propio coche pueden haber enfermedades en los pomos o en el volante 🚗, así que lávate las manos lo antes que puedas para evitar enfermedades 💦👏 @@ -93,9 +93,9 @@ Aprendamos cómo lavarnos las manos Únete a mí en este viaje en donde aprenderemos cómo lavarnos las manos completamente - 💦👏. Es muy importante no solo por ti sino por todos + 💦👏.\nEs muy importante no solo por ti sino por todos los que te rodean: desde un bebé 👶 hasta tus abuelos - 👴👵. Es un pequeño acto de responsabilidad que + 👴👵.\n\nEs un pequeño acto de responsabilidad que dura solo 30 segundos. Recuerda, no es solo por ti sino por ellos. @@ -257,8 +257,8 @@ enfermedades Aquí puedes aprender a cómo lavarte completamente las manos - ¿Buscando las últimas noticias? Aquí - estarán disponibles (todavía está bajo desarrollo) + ¿Buscando las últimas noticias? ¡Aquí + estarán actualizadas! Finalmente, aquí podrás configurar la aplicación para que encaje con tus gustos y preferencias Valorar Handwashing reminder diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index aff6b05..5812a91 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -12,26 +12,26 @@ Did you have a good walk? 🚶 Walking 30 minutes a - day is one of the greatest practices for having a good health 💪 + day is one of the greatest practices for having a good health 💪\n Remember to wash your hands before eating or touching your face 🤭 so you can have even better health 😉 Did you enjoy running? 🏃‍♀️ It\'s always funny to run 🙂 but usually it comes with a lot of sweat - 😅. Try washing your hands as soon as possible or, even + 😅.\nTry washing your hands as soon as possible or, even better, have a shower 🛀 Having a good ride? 🚴‍♀️ I always enjoy - going cycling - it makes you feel free 🌲. Your bike\'s handles were + going cycling - it makes you feel free 🌲.\nYour bike\'s handles were probably dirty so try to wash your hands as soon as you can 💦👏 Time to wash your - hands 🚉 + hands 🚗🚉
In public transport there are lots of people and you don\'t know if they - are ill or not 😷. Even in your own car there can be + are ill or not 😷.\nEven in your own car there can be diseases in the handles or in the steering wheel 🚗, so try to wash your hands as soon as you can for avoiding diseases 💦👏 @@ -95,9 +95,9 @@ our hands Join me in this trip in which we will learn how to completely wash our hands - 💦👏. This is very important not only for you but + 💦👏.\nThis is very important not only for you but for everyone who is around you: from a baby 👶 to your - grandparents 👴👵. It\'s a small responsibility + grandparents 👴👵.\n\nIt\'s a small responsibility act that only lasts 30 seconds. Remember, it\'s not only for you but for them. @@ -286,7 +286,7 @@ Here you can learn how to completely wash your hands Looking for latest news? Here you will - have it (is still under construction) + have them updated!
Finally, here you will be able to change the application settings so it can fit your requirements Rate Handwashing reminder diff --git a/appintro/src/main/res/values-es/strings.xml b/appintro/src/main/res/values-es/strings.xml index 5b5aea0..f8d9050 100644 --- a/appintro/src/main/res/values-es/strings.xml +++ b/appintro/src/main/res/values-es/strings.xml @@ -28,7 +28,7 @@ Se todavía más preciso Si permites el reconocimiento de tu actividad podrás recibir notificaciones cuando, por ejemplo, te bajas - de un vehículo 🚇 o después de correr 🏃‍. + de un vehículo 🚇 o después de correr 🏃‍.\n Puedes deshabilitar esta opción en cualquier momento Política de privacidad y términos de servicio diff --git a/appintro/src/main/res/values/strings.xml b/appintro/src/main/res/values/strings.xml index 31ca5de..8debd4c 100644 --- a/appintro/src/main/res/values/strings.xml +++ b/appintro/src/main/res/values/strings.xml @@ -18,7 +18,7 @@ application
Handwashing reminder Do you know how crucial it can be - washing your hands so you can avoid some diseases? With this app, you + washing your hands so you can avoid some diseases?\nWith this app, you will never forget it 😉 Custom hours @@ -28,7 +28,7 @@ Be even more precise If you allow access to user activity, you will receive notifications when, for example, you get off from a - vehicle 🚇 or after running 🤼‍🏃‍️. You can disable this option at any + vehicle 🚇 or after running 🤼‍🏃‍️.\nYou can disable this option at any moment Privacy policy and terms of service From d78d41b20c65ad5d0c4aed422c5a280fba4bee96 Mon Sep 17 00:00:00 2001 From: Javinator9889 Date: Fri, 3 Jul 2020 11:05:24 +0200 Subject: [PATCH 94/95] Dropped hardware support in Coil for avoiding possible errors --- app/build.gradle | 2 +- .../views/fragments/news/adapter/News.kt | 26 ------------------- 2 files changed, 1 insertion(+), 27 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 1fceafa..57a866c 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -40,7 +40,7 @@ android { applicationId "com.javinator9889.handwashingreminder" minSdkVersion 17 targetSdkVersion 29 - versionCode 135 + versionCode 136 versionName "1.2.0-${gitCommitHash}" multiDexEnabled true resConfigs "en", "es" diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/news/adapter/News.kt b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/news/adapter/News.kt index d059c38..97e04a2 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/news/adapter/News.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/news/adapter/News.kt @@ -24,7 +24,6 @@ import android.widget.ImageView import android.widget.TextView import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.lifecycleScope -import coil.Coil import coil.api.load import coil.size.Scale import com.airbnb.lottie.LottieAnimationView @@ -72,42 +71,17 @@ data class News( item.lifecycleOwner.lifecycleScope.launch(Dispatchers.Main) { title.text = item.title description.text = item.short - val imageLoader = Coil.imageLoader(view.context) if (item.imageUrl != null) { imageHeader.load(item.imageUrl) { scale(Scale.FILL) lifecycle(item.lifecycleOwner) - allowHardware(true) } - /*val request = GetRequest.Builder(view.context) - .data(item.imageUrl) - .allowHardware(true) - .scale(Scale.FILL) - .build() - launch(Dispatchers.IO) { - val drawable = imageLoader.execute(request).drawable - withContext(Dispatchers.Main) { - imageHeader.setImageDrawable(drawable) - } - }*/ } else imageHeader.visibility = View.GONE if (item.websiteImageUrl != null) { websiteLogo.load(item.websiteImageUrl) { scale(Scale.FILL) lifecycle(item.lifecycleOwner) - allowHardware(true) } - /*val request = GetRequest.Builder(view.context) - .data(item.websiteImageUrl) - .allowHardware(true) - .scale(Scale.FILL) - .build() - launch(Dispatchers.IO) { - val drawable = imageLoader.execute(request).drawable - withContext(Dispatchers.Main) { - websiteLogo.setImageDrawable(drawable) - } - }*/ } else websiteLogo.visibility = View.GONE websiteName.text = item.website ?: context.getString(R.string.no_website) From 2fcfff1e719c5c2e2f58d6050cf489e47d4ebd9e Mon Sep 17 00:00:00 2001 From: Javinator9889 Date: Fri, 3 Jul 2020 11:38:01 +0200 Subject: [PATCH 95/95] Timeout in network petitions for avoiding "forever" requests --- app/build.gradle | 2 +- .../views/fragments/diseases/DiseasesFragment.kt | 4 ---- .../handwashingreminder/network/HttpDownloader.kt | 14 +++++++++----- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 57a866c..3db0149 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -40,7 +40,7 @@ android { applicationId "com.javinator9889.handwashingreminder" minSdkVersion 17 targetSdkVersion 29 - versionCode 136 + versionCode 137 versionName "1.2.0-${gitCommitHash}" multiDexEnabled true resConfigs "en", "es" diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/diseases/DiseasesFragment.kt b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/diseases/DiseasesFragment.kt index 0d8f2c8..b15d819 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/diseases/DiseasesFragment.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/activities/views/fragments/diseases/DiseasesFragment.kt @@ -196,9 +196,6 @@ class DiseasesFragment : BaseFragmentView(), LayoutVisibilityChange, ) ) handwashingViewModel.increment(CalendarUtils.today.time) - Timber.d("${CalendarUtils.lastWeek.time}") - Timber.d("${CalendarUtils.lastMonth.time}") - handwashingViewModel.create(Handwashing(CalendarUtils.lastWeek.time, 12)) leaves.visibility = View.VISIBLE if (!leaves.isAnimating) leaves.playAnimation() @@ -215,7 +212,6 @@ class DiseasesFragment : BaseFragmentView(), LayoutVisibilityChange, ) ) handwashingViewModel.decrement(CalendarUtils.today.time) - handwashingViewModel.create(Handwashing(CalendarUtils.lastMonth.time, 123)) } } } diff --git a/app/src/main/java/com/javinator9889/handwashingreminder/network/HttpDownloader.kt b/app/src/main/java/com/javinator9889/handwashingreminder/network/HttpDownloader.kt index 664b0bd..df7561f 100644 --- a/app/src/main/java/com/javinator9889/handwashingreminder/network/HttpDownloader.kt +++ b/app/src/main/java/com/javinator9889/handwashingreminder/network/HttpDownloader.kt @@ -18,16 +18,20 @@ */ package com.javinator9889.handwashingreminder.network -import okhttp3.CacheControl -import okhttp3.Headers -import okhttp3.OkHttpClient -import okhttp3.Request +import com.javinator9889.handwashingreminder.application.HandwashingApplication +import okhttp3.* import okio.BufferedSource import java.io.IOException import java.io.Reader +import java.util.concurrent.TimeUnit class HttpDownloader : OkHttpDownloader { - private val client = OkHttpClient() + private val client: OkHttpClient = OkHttpClient.Builder() + .cache(Cache(HandwashingApplication.instance.cacheDir, 2024 * 10)) + .callTimeout(5, TimeUnit.SECONDS) + .readTimeout(1, TimeUnit.MINUTES) + .followRedirects(true) + .build() override fun downloadFile(url: String): BufferedSource { val request = with(Request.Builder()) {