├── .editorconfig
├── .gitignore
├── .travis.yml
├── LICENSE
├── README.md
├── config.xml
├── config
├── karma-test-shim.js
├── karma.conf.js
├── protractor.conf.js
├── uglifyjs.conf.js
└── webpack.test.js
├── e2e
├── account-creation
│ ├── account-creation.e2e-spec.ts
│ └── account-creation.po.ts
├── account
│ ├── account.e2e-spec.ts
│ └── account.po.ts
├── amount
│ ├── amount.e2e-spec.ts
│ └── amount.po.ts
├── main.po.ts
└── tsconfig.json
├── hooks
└── README.md
├── ionic.config.json
├── package-lock.json
├── package.json
├── res
├── drawable-port-hdpi
│ └── screen.png
├── drawable-port-ldpi
│ └── screen.png
├── drawable-port-mdpi
│ └── screen.png
├── drawable-port-xhdpi
│ └── screen.png
├── drawable-port-xxhdpi
│ └── screen.png
├── drawable-port-xxxhdpi
│ └── screen.png
├── mipmap-hdpi
│ └── icon.png
├── mipmap-ldpi
│ └── icon.png
├── mipmap-mdpi
│ └── icon.png
├── mipmap-xhdpi
│ └── icon.png
├── mipmap-xxhdpi
│ └── icon.png
└── mipmap-xxxhdpi
│ └── icon.png
├── resources
├── android
│ ├── icon
│ │ ├── drawable-hdpi-icon.png
│ │ ├── drawable-ldpi-icon.png
│ │ ├── drawable-mdpi-icon.png
│ │ ├── drawable-xhdpi-icon.png
│ │ ├── drawable-xxhdpi-icon.png
│ │ └── drawable-xxxhdpi-icon.png
│ └── splash
│ │ ├── drawable-land-hdpi-screen.png
│ │ ├── drawable-land-ldpi-screen.png
│ │ ├── drawable-land-mdpi-screen.png
│ │ ├── drawable-land-xhdpi-screen.png
│ │ ├── drawable-land-xxhdpi-screen.png
│ │ ├── drawable-land-xxxhdpi-screen.png
│ │ ├── drawable-port-hdpi-screen.png
│ │ ├── drawable-port-ldpi-screen.png
│ │ ├── drawable-port-mdpi-screen.png
│ │ ├── drawable-port-xhdpi-screen.png
│ │ ├── drawable-port-xxhdpi-screen.png
│ │ └── drawable-port-xxxhdpi-screen.png
├── icon.png
├── icon.png.md5
├── ios
│ ├── icon
│ │ ├── icon-40.png
│ │ ├── icon-40@2x.png
│ │ ├── icon-50.png
│ │ ├── icon-50@2x.png
│ │ ├── icon-60.png
│ │ ├── icon-60@2x.png
│ │ ├── icon-60@3x.png
│ │ ├── icon-72.png
│ │ ├── icon-72@2x.png
│ │ ├── icon-76.png
│ │ ├── icon-76@2x.png
│ │ ├── icon-small.png
│ │ ├── icon-small@2x.png
│ │ ├── icon-small@3x.png
│ │ ├── icon.png
│ │ └── icon@2x.png
│ └── splash
│ │ ├── Default-568h@2x~iphone.png
│ │ ├── Default-667h.png
│ │ ├── Default-736h.png
│ │ ├── Default-Landscape-736h.png
│ │ ├── Default-Landscape@2x~ipad.png
│ │ ├── Default-Landscape~ipad.png
│ │ ├── Default-Portrait@2x~ipad.png
│ │ ├── Default-Portrait~ipad.png
│ │ ├── Default@2x~iphone.png
│ │ └── Default~iphone.png
├── splash.png
└── splash.png.md5
├── src
├── api
│ ├── account.ts
│ ├── currency-exchange-rate.ts
│ ├── currency-exchange-service.ts
│ ├── payment-request-handler.ts
│ ├── payment-request.ts
│ ├── payment-service.ts
│ ├── transaction-filter.ts
│ ├── transaction-service.ts
│ └── transaction.ts
├── app
│ ├── app.component.ts
│ ├── app.html
│ ├── app.module.ts
│ ├── app.scss
│ ├── main.prod.ts
│ ├── main.ts
│ └── scss
│ │ ├── _buttons.scss
│ │ ├── _content.scss
│ │ ├── _logo.scss
│ │ ├── _md.scss
│ │ ├── _menu.scss
│ │ ├── _numpad.scss
│ │ ├── _payment.scss
│ │ └── _utilities.scss
├── assets
│ ├── fonts
│ │ ├── Ubuntu-B.ttf
│ │ ├── Ubuntu-C.ttf
│ │ ├── Ubuntu-L.ttf
│ │ ├── Ubuntu-M.ttf
│ │ └── Ubuntu-R.ttf
│ ├── i18n
│ │ ├── de.json
│ │ ├── en.json
│ │ └── pl.json
│ ├── icon
│ │ └── favicon.ico
│ ├── img
│ │ ├── background.png
│ │ ├── bitcoin-logo.png
│ │ ├── bitpocket_appicon_1024x1024.png
│ │ ├── bitpocket_appicon_white1024x1024.png
│ │ ├── bitpocket_icon_135x135.svg
│ │ ├── bitpocket_menu_header.jpg
│ │ ├── noconnection.png
│ │ ├── scanner.svg
│ │ └── testnet-logo.png
│ └── sound
│ │ └── paid.mp3
├── components
│ ├── dynamic-font-size
│ │ ├── dynamic-font-size.module.ts
│ │ └── dynamic-font-size.ts
│ └── logo
│ │ ├── logo.module.ts
│ │ └── logo.ts
├── declarations.d.ts
├── index.html
├── manifest.json
├── pages
│ ├── account-creation
│ │ ├── account-creation.html
│ │ ├── account-creation.module.ts
│ │ ├── account-creation.scss
│ │ └── account-creation.ts
│ ├── account-form
│ │ ├── account-form.html
│ │ ├── account-form.module.ts
│ │ └── account-form.ts
│ ├── account
│ │ ├── account.html
│ │ ├── account.module.ts
│ │ └── account.ts
│ ├── amount
│ │ ├── amount.html
│ │ ├── amount.module.ts
│ │ └── amount.ts
│ ├── export
│ │ ├── export.html
│ │ ├── export.module.ts
│ │ └── export.ts
│ ├── history
│ │ ├── history.html
│ │ ├── history.module.ts
│ │ ├── history.scss
│ │ └── history.ts
│ ├── offline
│ │ ├── offline.html
│ │ ├── offline.module.ts
│ │ ├── offline.scss
│ │ └── offline.ts
│ ├── payment-result
│ │ ├── payment-result.html
│ │ ├── payment-result.module.ts
│ │ └── payment-result.ts
│ ├── payment
│ │ ├── payment.html
│ │ ├── payment.module.ts
│ │ └── payment.ts
│ ├── pincode
│ │ ├── pincode.html
│ │ ├── pincode.module.ts
│ │ ├── pincode.scss
│ │ └── pincode.ts
│ ├── qrscanner
│ │ ├── qrscanner.html
│ │ ├── qrscanner.module.ts
│ │ ├── qrscanner.scss
│ │ └── qrscanner.ts
│ ├── settings
│ │ ├── currency
│ │ │ ├── currency.html
│ │ │ ├── currency.module.ts
│ │ │ ├── currency.scss
│ │ │ └── currency.ts
│ │ ├── general
│ │ │ ├── general.html
│ │ │ ├── general.module.ts
│ │ │ └── general.ts
│ │ ├── settings.html
│ │ ├── settings.module.ts
│ │ └── settings.ts
│ └── transaction
│ │ ├── transaction.html
│ │ ├── transaction.module.ts
│ │ ├── transaction.scss
│ │ └── transaction.ts
├── pipes
│ ├── bitpocket-currency
│ │ ├── bitpocket-currency.module.ts
│ │ └── bitpocket-currency.ts
│ ├── bitpocket-fiat
│ │ ├── bitpocket-fiat.module.ts
│ │ └── bitpocket-fiat.ts
│ └── bitpocket-unit
│ │ ├── bitpocket-unit.module.ts
│ │ └── bitpocket-unit.ts
├── providers
│ ├── account
│ │ ├── account-service.ts
│ │ └── account-sync-service.ts
│ ├── config.ts
│ ├── currency
│ │ ├── bitcoin-unit.spec.ts
│ │ ├── bitcoin-unit.ts
│ │ ├── bitcoinaverage-service.ts
│ │ ├── cryptocurrency-service.spec.ts
│ │ ├── cryptocurrency-service.ts
│ │ ├── currency-service.spec.ts
│ │ └── currency-service.ts
│ ├── index.ts
│ ├── payment
│ │ ├── payment-request-handler
│ │ │ └── insight-payment-request-handler.ts
│ │ └── payment-service.ts
│ ├── qrscanner
│ │ └── qrscanner.ts
│ ├── repository.ts
│ └── transaction
│ │ ├── blockchain-info-service
│ │ ├── blockchain-info-data.ts
│ │ ├── blockchain-info-service.spec.ts
│ │ └── blockchain-info-service.ts
│ │ ├── blockcypher-service
│ │ ├── blockcypher-data.ts
│ │ ├── blockcypher-service.spec.ts
│ │ └── blockcypher-service.ts
│ │ ├── insight-transaction-service
│ │ ├── insight-transaction-data.ts
│ │ ├── insight-transaction-service.spec.ts
│ │ └── insight-transaction-service.ts
│ │ ├── transaction-service-wrapper.ts
│ │ └── transaction-storage-service.ts
├── service-worker.js
└── theme
│ ├── _font.scss
│ └── variables.scss
├── tsconfig.json
├── tslint.json
└── www
└── index.html
/.editorconfig:
--------------------------------------------------------------------------------
1 | # EditorConfig helps developers define and maintain consistent coding styles between different editors and IDEs
2 | # editorconfig.org
3 |
4 | root = true
5 |
6 | [*]
7 | indent_style = space
8 | indent_size = 4
9 |
10 | # We recommend you to keep these unchanged
11 | end_of_line = lf
12 | charset = utf-8
13 | trim_trailing_whitespace = true
14 | insert_final_newline = true
15 |
16 | [*.md]
17 | trim_trailing_whitespace = false
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Specifies intentionally untracked files to ignore when using Git
2 | # http://git-scm.com/docs/gitignore
3 |
4 | *~
5 | *.sw[mnpcod]
6 | *.log
7 | *.tmp
8 | *.tmp.*
9 | log.txt
10 | *.sublime-project
11 | *.sublime-workspace
12 | .vscode/
13 | npm-debug.log*
14 |
15 | .idea/
16 | .sass-cache/
17 | .tmp/
18 | .versions/
19 | coverage/
20 | dist/
21 | node_modules/
22 | tmp/
23 | temp/
24 | hooks/
25 | platforms/
26 | plugins/
27 | plugins/android.json
28 | plugins/ios.json
29 | www/
30 | $RECYCLE.BIN/
31 |
32 | .DS_Store
33 | Thumbs.db
34 | UserInterfaceState.xcuserstate
35 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | sudo: false
3 | dist: trusty
4 | node_js:
5 | - "8"
6 |
7 | install:
8 | - npm i ionic -g
9 | - npm i
10 |
11 | script:
12 | - npm test
13 |
14 | addons:
15 | apt:
16 | packages:
17 | - google-chrome-stable
18 |
19 | before_script:
20 | - ionic serve &
21 | - export DISPLAY=:99.0
22 | - sh -e /etc/init.d/xvfb start &
23 | - sleep 15
24 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | BSD 2-Clause License
2 |
3 | Copyright (c) 2017, Stefan Huber
4 | All rights reserved.
5 |
6 | Redistribution and use in source and binary forms, with or without
7 | modification, are permitted provided that the following conditions are met:
8 |
9 | * Redistributions of source code must retain the above copyright notice, this
10 | list of conditions and the following disclaimer.
11 |
12 | * Redistributions in binary form must reproduce the above copyright notice,
13 | this list of conditions and the following disclaimer in the documentation
14 | and/or other materials provided with the distribution.
15 |
16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
20 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
22 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
23 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
24 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
25 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://travis-ci.org/getbitpocket/bitpocket-mobile-app) [](https://gitter.im/getbitpocket/bitpocket-mobile-app?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [](https://david-dm.org/getbitpocket/bitpocket-mobile-app)
2 |
3 |
4 | # BitPocket
5 |
6 | Enabling Bitcoin Payments at the Point of Sale.
7 |
8 | ## Screenshots
9 |
10 |
11 |
12 |  |
13 |  |
14 |  |
15 |
16 |
17 |
18 | ## Main features
19 |
20 | - Multiple accounts
21 | - BIP32 HD Keys
22 | - Testnet support
23 | - Support for different currencies
24 | - i18n support
25 |
26 | ## Development setup
27 |
28 | BitPocket is an App based on the Ionic/Cordova Frameworks. Therefore a couple of node modules are required to get started. In order to setup your environment the following global node modules need to be installed: `npm i cordova ionic -g`
29 |
30 | After successful installation of the required global modules, do a `npm i` inside the project folder to gather all local module dependencies. Additionally, some cordova plugins are required also. `ionic state reset --plugin` does the trick here.
31 |
32 | - `ionic serve` can be used to test the app locally in the browser, however not all networking features are available.
33 | - `ionic run (android|ios)` can be used to test the app on a connected device
34 | - `ionic emulate (android|ios)` can be used to test the app on a emulator/simulator
35 |
36 | ## Testing
37 |
38 | BitPocket is developed alongside unit and e2e testing. Test can be run:
39 |
40 | - `npm run test:unit` for unit tests
41 | - `npm run test:e2e` for e2e tests
42 |
43 | ## Translations
44 |
45 | - [English](https://github.com/getbitpocket/bitpocket-mobile-app/blob/master/src/assets/i18n/en.json)
46 | - [German](https://github.com/getbitpocket/bitpocket-mobile-app/blob/master/src/assets/i18n/de.json)
47 | - [Polish](https://github.com/getbitpocket/bitpocket-mobile-app/blob/master/src/assets/i18n/pl.json)
--------------------------------------------------------------------------------
/config/karma-test-shim.js:
--------------------------------------------------------------------------------
1 | Error.stackTraceLimit = Infinity;
2 |
3 | require('core-js/es6');
4 | require('core-js/es7/reflect');
5 |
6 | require('zone.js/dist/zone');
7 | require('zone.js/dist/long-stack-trace-zone');
8 | require('zone.js/dist/proxy');
9 | require('zone.js/dist/sync-test');
10 | require('zone.js/dist/jasmine-patch');
11 | require('zone.js/dist/async-test');
12 | require('zone.js/dist/fake-async-test');
13 |
14 | var appContext = require.context('../src', true, /\.spec\.ts/);
15 |
16 | appContext.keys().forEach(appContext);
17 |
18 | var testing = require('@angular/core/testing');
19 | var browser = require('@angular/platform-browser-dynamic/testing');
20 |
21 | testing.TestBed.initTestEnvironment(browser.BrowserDynamicTestingModule, browser.platformBrowserDynamicTesting());
--------------------------------------------------------------------------------
/config/karma.conf.js:
--------------------------------------------------------------------------------
1 | var webpackConfig = require('./webpack.test.js');
2 |
3 | module.exports = function (config) {
4 | var _config = {
5 | basePath: '',
6 |
7 | frameworks: ['jasmine'],
8 |
9 | files: [
10 | {pattern: './karma-test-shim.js', watched: true}
11 | ],
12 |
13 | preprocessors: {
14 | './karma-test-shim.js': ['webpack', 'sourcemap']
15 | },
16 |
17 | webpack: webpackConfig,
18 |
19 | webpackMiddleware: {
20 | stats: 'errors-only'
21 | },
22 |
23 | webpackServer: {
24 | noInfo: true
25 | },
26 |
27 | browserConsoleLogOptions: {
28 | level: 'log',
29 | format: '%b %T: %m',
30 | terminal: true
31 | },
32 |
33 | reporters: ['kjhtml', 'dots'],
34 | port: 9876,
35 | colors: true,
36 | logLevel: config.LOG_INFO,
37 | autoWatch: true,
38 | browsers: ['Chrome'],
39 | singleRun: true
40 | };
41 |
42 | config.set(_config);
43 | };
--------------------------------------------------------------------------------
/config/protractor.conf.js:
--------------------------------------------------------------------------------
1 | // Protractor configuration file, see link for more information
2 | // https://github.com/angular/protractor/blob/master/lib/config.ts
3 |
4 | /*global jasmine */
5 | var SpecReporter = require('jasmine-spec-reporter').SpecReporter;
6 |
7 | exports.config = {
8 | allScriptsTimeout: 11000,
9 | specs: [
10 | '../e2e/**/*.e2e-spec.ts'
11 | ],
12 | capabilities: {
13 | 'browserName': 'chrome'
14 | },
15 | directConnect: true,
16 | baseUrl: 'http://localhost:8100/',
17 | framework: 'jasmine',
18 | jasmineNodeOpts: {
19 | showColors: true,
20 | defaultTimeoutInterval: 30000,
21 | print: function() {}
22 | },
23 | useAllAngular2AppRoots: true,
24 | beforeLaunch: function() {
25 | require('ts-node').register({
26 | project: 'e2e'
27 | });
28 | },
29 | onPrepare: function() {
30 | jasmine.getEnv().addReporter(new SpecReporter());
31 | }
32 | };
--------------------------------------------------------------------------------
/config/uglifyjs.conf.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 |
3 |
4 |
5 | /**
6 | * mangle: uglify 2's mangle option
7 | */
8 | mangle: {
9 | reserved : ['BigInteger','ECPair','Point']
10 | },
11 |
12 | /**
13 | * compress: uglify 2's compress option
14 | */
15 | compress: true,
16 |
17 | };
--------------------------------------------------------------------------------
/config/webpack.test.js:
--------------------------------------------------------------------------------
1 | var webpack = require('webpack');
2 | var path = require('path');
3 |
4 | module.exports = {
5 | devtool: 'inline-source-map',
6 |
7 | resolve: {
8 | extensions: ['.ts', '.js']
9 | },
10 |
11 | module: {
12 | rules: [
13 | {
14 | test: /\.ts$/,
15 | loaders: [
16 | {
17 | loader: 'ts-loader'
18 | } , 'angular2-template-loader'
19 | ]
20 | },
21 | {
22 | test: /\.html$/,
23 | loader: 'html-loader'
24 | },
25 | {
26 | test: /\.(png|jpe?g|gif|svg|woff|woff2|ttf|eot|ico)$/,
27 | loader: 'null-loader'
28 | }
29 | ]
30 | },
31 |
32 | plugins: [
33 | new webpack.ContextReplacementPlugin(
34 | // The (\\|\/) piece accounts for path separators in *nix and Windows
35 | /angular(\\|\/)core(\\|\/)(esm(\\|\/)src|src)(\\|\/)linker/,
36 | root('./src'), // location of your src
37 | {} // a map of your routes
38 | )
39 | ]
40 | };
41 |
42 | function root(localPath) {
43 | return path.resolve(__dirname, localPath);
44 | }
--------------------------------------------------------------------------------
/e2e/account-creation/account-creation.e2e-spec.ts:
--------------------------------------------------------------------------------
1 | import { OnboardingPage } from './account-creation.po';
2 | import { browser, element, by, promise } from 'protractor';
3 |
4 | describe('Account Creation', function() {
5 | let page: OnboardingPage;
6 |
7 | beforeEach(() => {
8 | browser.restartSync();
9 | page = new OnboardingPage();
10 | });
11 |
12 | describe('Create Account', () => {
13 |
14 | beforeEach(() => {
15 | browser.ignoreSynchronization = true;
16 | browser.get('/#/account-creation');
17 | });
18 |
19 | it('should create account and navigate to amount page', () => {
20 | page.setAccountCreationInput('n1RGwdbNTgNL868cRFCcimdCAQMft8HZKo')
21 | .then(() => {
22 | return page.submitAccountCreation();
23 | }).then(() => {
24 | browser.sleep(4000);
25 | return page.isAmountPage();
26 | }).then(amountPage => {
27 | expect(amountPage).toBeTruthy();
28 | });
29 | });
30 |
31 | it('should create a dialog error message', () => {
32 | page.setAccountCreationInput('no-valid-input')
33 | .then(() => {
34 | return page.submitAccountCreation();
35 | }).then(() => {
36 | return page.hasAlert();
37 | }).then(() => {
38 | expect(true).toBeTruthy();
39 | });
40 | });
41 |
42 | });
43 |
44 | });
--------------------------------------------------------------------------------
/e2e/account-creation/account-creation.po.ts:
--------------------------------------------------------------------------------
1 | import { browser, element, by } from 'protractor';
2 | import { MainPage } from "./../main.po";
3 |
4 | export class OnboardingPage extends MainPage {
5 |
6 | isAmountPage() {
7 | return browser.getCurrentUrl().then((url:string) => {
8 | return /\/#\/amount$/.test(url);
9 | });
10 | }
11 |
12 | hasAlert() {
13 | browser.sleep(1000)
14 | .then(() => {
15 | return element(by.css('button[ion-button="alert-button"]')).click();
16 | });
17 | }
18 |
19 | }
--------------------------------------------------------------------------------
/e2e/account/account.e2e-spec.ts:
--------------------------------------------------------------------------------
1 | import { AccountPage } from './account.po';
2 | import { browser, element, by, promise } from 'protractor';
3 |
4 | describe('Account', function() {
5 | let page: AccountPage;
6 |
7 | beforeEach(() => {
8 | browser.restartSync();
9 | page = new AccountPage();
10 | });
11 |
12 | describe('Account Management', () => {
13 |
14 | beforeEach(() => {
15 | browser.ignoreSynchronization = true;
16 | page.createAccount('n1RGwdbNTgNL868cRFCcimdCAQMft8HZKo')
17 | .then(() => {
18 | browser.get('/#/account');
19 | });
20 | });
21 |
22 | it('should create additional account', () => {
23 | page.navigateNewAccountPage()
24 | .then(() => {
25 | return page.setAccountData('testnet 2', 'mqdquKXQPfyn4Qzu2HHPgULTX5ay8g4wPg');
26 | }).then(() => {
27 | return page.submitAccountData();
28 | }).then(() => {
29 | return page.countAccounts();
30 | }).then(count => {
31 | expect(count).toEqual(2);
32 | });
33 | });
34 |
35 | });
36 |
37 | });
--------------------------------------------------------------------------------
/e2e/account/account.po.ts:
--------------------------------------------------------------------------------
1 | import { browser, element, by } from 'protractor';
2 | import { MainPage } from './../main.po';
3 |
4 | export class AccountPage extends MainPage {
5 |
6 | navigateNewAccountPage() {
7 | return browser.sleep(1000)
8 | .then(() => {
9 | return element(by.css('ion-fab button')).click()
10 | }).then(() => {
11 | return browser.sleep(500);
12 | });
13 | }
14 |
15 | countAccounts() {
16 | return browser.sleep(1000)
17 | .then(() : any => {
18 | return element.all(by.css('.scroll-content ion-card'));
19 | }).then(items => {
20 | return items.length;
21 | });
22 | }
23 |
24 | submitAccountData() {
25 | return element.all(by.css('ion-navbar .back-button'))
26 | .then(items => {
27 | return items[items.length - 1].click();
28 | });
29 | }
30 |
31 | setAccountData(name:string, account:string) {
32 | return element.all(by.css('ion-list ion-item input[type="text"]'))
33 | .then(items => {
34 | browser.sleep(100);
35 |
36 | items[0].sendKeys(name);
37 | items[1].sendKeys(account);
38 |
39 | return browser.sleep(300);
40 | });
41 | }
42 |
43 | }
--------------------------------------------------------------------------------
/e2e/amount/amount.e2e-spec.ts:
--------------------------------------------------------------------------------
1 | import { AmountPage } from './amount.po';
2 | import { browser, element, by, promise } from 'protractor';
3 |
4 | describe('Amount', function() {
5 | let page: AmountPage;
6 |
7 | beforeEach(() => {
8 | browser.restartSync();
9 | page = new AmountPage();
10 | });
11 |
12 | describe('Amount', () => {
13 |
14 | beforeEach(() => {
15 | browser.ignoreSynchronization = true;
16 | page.createAccount('n1RGwdbNTgNL868cRFCcimdCAQMft8HZKo')
17 | .then(() => {
18 | browser.get('/#/amount');
19 | });
20 | });
21 |
22 | it('should set amount correctly', () => {
23 | browser.sleep(1000);
24 |
25 | page.clickButton(0)
26 | .then(() => {
27 | return page.clickButton(1);
28 | }).then(() => {
29 | return page.clickButton(2);
30 | }).then(() => {
31 | return page.clickButton(9);
32 | }).then(() => {
33 | return page.clickButton(6);
34 | }).then(() => {
35 | return page.getActiveAmount();
36 | }).then(amount => {
37 | expect(amount).toMatch(/123(,|\.)70/);
38 | });
39 | });
40 |
41 | });
42 |
43 | });
--------------------------------------------------------------------------------
/e2e/amount/amount.po.ts:
--------------------------------------------------------------------------------
1 | import { browser, element, by } from 'protractor';
2 | import { MainPage } from './../main.po';
3 |
4 | export class AmountPage extends MainPage {
5 |
6 | /**
7 | *
8 | * Click on of the 14 buttons
9 | * index: 0 - 13
10 | * @param buttonIndex
11 | */
12 | clickButton(buttonIndex:number = 0) {
13 | return element.all(by.css('.numpad button'))
14 | .then(items => {
15 | if (items.length > buttonIndex) {
16 | return items[buttonIndex].click();
17 | }
18 | })
19 | }
20 |
21 | getActiveAmount() {
22 | return element(by.css('.payment-amount .active [dynamicfontsize]')).getText();
23 | }
24 |
25 | }
--------------------------------------------------------------------------------
/e2e/main.po.ts:
--------------------------------------------------------------------------------
1 | import { browser, element, by } from 'protractor';
2 |
3 | export class MainPage {
4 |
5 | navigateTo(destination) {
6 | return browser.get(destination);
7 | }
8 |
9 | getTitle() {
10 | return browser.getTitle();
11 | }
12 |
13 | createAccount(input:string) {
14 | browser.ignoreSynchronization = true;
15 |
16 | return browser.sleep(2000)
17 | .then(() => {
18 | return browser.get('/#/account');
19 | }).then(() => {
20 | return browser.sleep(2000);
21 | }).then(() => {
22 | return this.setAccountCreationInput(input);
23 | }).then(() => {
24 | return this.submitAccountCreation();
25 | }).then(() => {
26 | return browser.sleep(5000);
27 | });
28 |
29 | }
30 |
31 | setAccountCreationInput(input:string) {
32 | let el = element(by.css('#account-creation-input input[type="text"]'));
33 | browser.sleep(500);
34 | return el.sendKeys(input);
35 | }
36 |
37 | submitAccountCreation() {
38 | return element(by.css('button#account-creation-button')).click();
39 | }
40 |
41 | }
--------------------------------------------------------------------------------
/e2e/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compileOnSave": false,
3 | "compilerOptions": {
4 | "declaration": false,
5 | "emitDecoratorMetadata": true,
6 | "experimentalDecorators": true,
7 | "module": "commonjs",
8 | "moduleResolution": "node",
9 | "outDir": "../dist/out-tsc-e2e",
10 | "sourceMap": true,
11 | "target": "es5",
12 | "typeRoots": [
13 | "../node_modules/@types"
14 | ]
15 | }
16 | }
--------------------------------------------------------------------------------
/ionic.config.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "bitpocket-mobile-app",
3 | "app_id": "3cd21cf2",
4 | "type": "ionic-angular",
5 | "integrations": {
6 | "cordova": {}
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "bitpocket-mobile-app",
3 | "author": "Stefan Huber",
4 | "version": "1.1.8",
5 | "license": "BSD-2-Clause",
6 | "repository": {
7 | "type": "git",
8 | "url": "https://github.com/getbitpocket/bitpocket-mobile-app.git"
9 | },
10 | "bugs": "https://github.com/getbitpocket/bitpocket-mobile-app/issues",
11 | "description": "Bitpocket mobile app",
12 | "homepage": "http://www.bitpocket.at/",
13 | "scripts": {
14 | "ionic:build": "ionic-app-scripts build",
15 | "ionic:serve": "ionic-app-scripts serve",
16 | "test": "npm run test:unit && npm run test:e2e",
17 | "test:e2e": "webdriver-manager update --standalone false --gecko false; protractor ./config/protractor.conf.js",
18 | "test:unit": "karma start ./config/karma.conf.js"
19 | },
20 | "config": {
21 | "ionic_uglifyjs": "./config/uglifyjs.conf.js"
22 | },
23 | "dependencies": {
24 | "@angular/common": "^5.0.2",
25 | "@angular/compiler": "^5.0.2",
26 | "@angular/compiler-cli": "^5.0.2",
27 | "@angular/core": "^5.0.2",
28 | "@angular/forms": "^5.0.2",
29 | "@angular/http": "^5.0.2",
30 | "@angular/platform-browser": "^5.0.2",
31 | "@angular/platform-browser-dynamic": "^5.0.2",
32 | "@ionic-native/core": "^4.4.2",
33 | "@ionic-native/file": "^4.4.2",
34 | "@ionic-native/file-opener": "^4.4.2",
35 | "@ionic-native/network": "^4.4.2",
36 | "@ionic-native/splash-screen": "^4.4.2",
37 | "@ionic-native/status-bar": "^4.4.2",
38 | "@ionic/storage": "^2.1.3",
39 | "@ngx-translate/core": "^8.0.0",
40 | "@ngx-translate/http-loader": "^2.0.0",
41 | "bip21": "^2.0.1",
42 | "bitcoinjs-lib": "^3.3.0",
43 | "cordova-android": "6.4.0",
44 | "cordova-plugin-file": "^5.0.0",
45 | "cordova-plugin-file-opener2": "^2.0.19",
46 | "cordova-plugin-inappbrowser": "^1.7.2",
47 | "cordova-plugin-ionic-webview": "^1.1.16",
48 | "cordova-plugin-network-information": "^1.3.4",
49 | "cordova-plugin-qrscanner": "~2.5.0",
50 | "cordova-plugin-splashscreen": "~4.1.0",
51 | "cordova-plugin-whitelist": "^1.3.3",
52 | "crypto-regex": "^0.3.0",
53 | "ionic-angular": "^3.9.2",
54 | "ionic-plugin-keyboard": "^2.2.1",
55 | "ionicons": "3.0.0",
56 | "pouchdb": "^6.3.4",
57 | "pouchdb-find": "^6.3.4",
58 | "pouchdb-upsert": "^2.1.0",
59 | "qrcode-generator": "^1.3.1",
60 | "rxjs": "^5.5.2",
61 | "socket.io": "^2.0.4",
62 | "sw-toolbox": "^3.6.0",
63 | "zone.js": "^0.8.18"
64 | },
65 | "devDependencies": {
66 | "@ionic/app-scripts": "3.1.0",
67 | "@types/bitcoinjs-lib": "^3.0.5",
68 | "@types/cordova-plugin-qrscanner": "^1.0.31",
69 | "@types/jasmine": "^2.8.2",
70 | "@types/node": "^8.0.53",
71 | "@types/socket.io": "^1.4.31",
72 | "android-versions": "^1.2.1",
73 | "angular2-template-loader": "^0.6.2",
74 | "html-loader": "^0.5.1",
75 | "jasmine": "^2.8.0",
76 | "jasmine-spec-reporter": "^4.2.1",
77 | "karma": "^1.7.1",
78 | "karma-chrome-launcher": "^2.2.0",
79 | "karma-jasmine": "^1.1.0",
80 | "karma-jasmine-html-reporter": "^0.2.2",
81 | "karma-sourcemap-loader": "^0.3.7",
82 | "karma-webpack": "^2.0.6",
83 | "null-loader": "^0.1.1",
84 | "protractor": "^5.2.0",
85 | "ts-loader": "^2.3.7",
86 | "ts-node": "^3.3.0",
87 | "typescript": "2.4.2"
88 | },
89 | "cordova": {
90 | "plugins": {
91 | "ionic-plugin-keyboard": {},
92 | "cordova-plugin-wkwebview-engine": {},
93 | "cordova-plugin-network-information": {},
94 | "cordova-plugin-whitelist": {},
95 | "cordova-plugin-splashscreen": {},
96 | "cordova-plugin-inappbrowser": {},
97 | "cordova-plugin-qrscanner": {},
98 | "cordova-plugin-ionic-webview": {},
99 | "cordova-plugin-file": {},
100 | "cordova-plugin-file-opener2": {}
101 | },
102 | "platforms": [
103 | "android"
104 | ]
105 | }
106 | }
107 |
--------------------------------------------------------------------------------
/res/drawable-port-hdpi/screen.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getbitpocket/bitpocket-mobile-app/907cf005fcd2c33bd9c2aa2d2a9a2281bc1a6376/res/drawable-port-hdpi/screen.png
--------------------------------------------------------------------------------
/res/drawable-port-ldpi/screen.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getbitpocket/bitpocket-mobile-app/907cf005fcd2c33bd9c2aa2d2a9a2281bc1a6376/res/drawable-port-ldpi/screen.png
--------------------------------------------------------------------------------
/res/drawable-port-mdpi/screen.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getbitpocket/bitpocket-mobile-app/907cf005fcd2c33bd9c2aa2d2a9a2281bc1a6376/res/drawable-port-mdpi/screen.png
--------------------------------------------------------------------------------
/res/drawable-port-xhdpi/screen.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getbitpocket/bitpocket-mobile-app/907cf005fcd2c33bd9c2aa2d2a9a2281bc1a6376/res/drawable-port-xhdpi/screen.png
--------------------------------------------------------------------------------
/res/drawable-port-xxhdpi/screen.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getbitpocket/bitpocket-mobile-app/907cf005fcd2c33bd9c2aa2d2a9a2281bc1a6376/res/drawable-port-xxhdpi/screen.png
--------------------------------------------------------------------------------
/res/drawable-port-xxxhdpi/screen.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getbitpocket/bitpocket-mobile-app/907cf005fcd2c33bd9c2aa2d2a9a2281bc1a6376/res/drawable-port-xxxhdpi/screen.png
--------------------------------------------------------------------------------
/res/mipmap-hdpi/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getbitpocket/bitpocket-mobile-app/907cf005fcd2c33bd9c2aa2d2a9a2281bc1a6376/res/mipmap-hdpi/icon.png
--------------------------------------------------------------------------------
/res/mipmap-ldpi/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getbitpocket/bitpocket-mobile-app/907cf005fcd2c33bd9c2aa2d2a9a2281bc1a6376/res/mipmap-ldpi/icon.png
--------------------------------------------------------------------------------
/res/mipmap-mdpi/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getbitpocket/bitpocket-mobile-app/907cf005fcd2c33bd9c2aa2d2a9a2281bc1a6376/res/mipmap-mdpi/icon.png
--------------------------------------------------------------------------------
/res/mipmap-xhdpi/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getbitpocket/bitpocket-mobile-app/907cf005fcd2c33bd9c2aa2d2a9a2281bc1a6376/res/mipmap-xhdpi/icon.png
--------------------------------------------------------------------------------
/res/mipmap-xxhdpi/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getbitpocket/bitpocket-mobile-app/907cf005fcd2c33bd9c2aa2d2a9a2281bc1a6376/res/mipmap-xxhdpi/icon.png
--------------------------------------------------------------------------------
/res/mipmap-xxxhdpi/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getbitpocket/bitpocket-mobile-app/907cf005fcd2c33bd9c2aa2d2a9a2281bc1a6376/res/mipmap-xxxhdpi/icon.png
--------------------------------------------------------------------------------
/resources/android/icon/drawable-hdpi-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getbitpocket/bitpocket-mobile-app/907cf005fcd2c33bd9c2aa2d2a9a2281bc1a6376/resources/android/icon/drawable-hdpi-icon.png
--------------------------------------------------------------------------------
/resources/android/icon/drawable-ldpi-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getbitpocket/bitpocket-mobile-app/907cf005fcd2c33bd9c2aa2d2a9a2281bc1a6376/resources/android/icon/drawable-ldpi-icon.png
--------------------------------------------------------------------------------
/resources/android/icon/drawable-mdpi-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getbitpocket/bitpocket-mobile-app/907cf005fcd2c33bd9c2aa2d2a9a2281bc1a6376/resources/android/icon/drawable-mdpi-icon.png
--------------------------------------------------------------------------------
/resources/android/icon/drawable-xhdpi-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getbitpocket/bitpocket-mobile-app/907cf005fcd2c33bd9c2aa2d2a9a2281bc1a6376/resources/android/icon/drawable-xhdpi-icon.png
--------------------------------------------------------------------------------
/resources/android/icon/drawable-xxhdpi-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getbitpocket/bitpocket-mobile-app/907cf005fcd2c33bd9c2aa2d2a9a2281bc1a6376/resources/android/icon/drawable-xxhdpi-icon.png
--------------------------------------------------------------------------------
/resources/android/icon/drawable-xxxhdpi-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getbitpocket/bitpocket-mobile-app/907cf005fcd2c33bd9c2aa2d2a9a2281bc1a6376/resources/android/icon/drawable-xxxhdpi-icon.png
--------------------------------------------------------------------------------
/resources/android/splash/drawable-land-hdpi-screen.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getbitpocket/bitpocket-mobile-app/907cf005fcd2c33bd9c2aa2d2a9a2281bc1a6376/resources/android/splash/drawable-land-hdpi-screen.png
--------------------------------------------------------------------------------
/resources/android/splash/drawable-land-ldpi-screen.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getbitpocket/bitpocket-mobile-app/907cf005fcd2c33bd9c2aa2d2a9a2281bc1a6376/resources/android/splash/drawable-land-ldpi-screen.png
--------------------------------------------------------------------------------
/resources/android/splash/drawable-land-mdpi-screen.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getbitpocket/bitpocket-mobile-app/907cf005fcd2c33bd9c2aa2d2a9a2281bc1a6376/resources/android/splash/drawable-land-mdpi-screen.png
--------------------------------------------------------------------------------
/resources/android/splash/drawable-land-xhdpi-screen.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getbitpocket/bitpocket-mobile-app/907cf005fcd2c33bd9c2aa2d2a9a2281bc1a6376/resources/android/splash/drawable-land-xhdpi-screen.png
--------------------------------------------------------------------------------
/resources/android/splash/drawable-land-xxhdpi-screen.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getbitpocket/bitpocket-mobile-app/907cf005fcd2c33bd9c2aa2d2a9a2281bc1a6376/resources/android/splash/drawable-land-xxhdpi-screen.png
--------------------------------------------------------------------------------
/resources/android/splash/drawable-land-xxxhdpi-screen.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getbitpocket/bitpocket-mobile-app/907cf005fcd2c33bd9c2aa2d2a9a2281bc1a6376/resources/android/splash/drawable-land-xxxhdpi-screen.png
--------------------------------------------------------------------------------
/resources/android/splash/drawable-port-hdpi-screen.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getbitpocket/bitpocket-mobile-app/907cf005fcd2c33bd9c2aa2d2a9a2281bc1a6376/resources/android/splash/drawable-port-hdpi-screen.png
--------------------------------------------------------------------------------
/resources/android/splash/drawable-port-ldpi-screen.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getbitpocket/bitpocket-mobile-app/907cf005fcd2c33bd9c2aa2d2a9a2281bc1a6376/resources/android/splash/drawable-port-ldpi-screen.png
--------------------------------------------------------------------------------
/resources/android/splash/drawable-port-mdpi-screen.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getbitpocket/bitpocket-mobile-app/907cf005fcd2c33bd9c2aa2d2a9a2281bc1a6376/resources/android/splash/drawable-port-mdpi-screen.png
--------------------------------------------------------------------------------
/resources/android/splash/drawable-port-xhdpi-screen.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getbitpocket/bitpocket-mobile-app/907cf005fcd2c33bd9c2aa2d2a9a2281bc1a6376/resources/android/splash/drawable-port-xhdpi-screen.png
--------------------------------------------------------------------------------
/resources/android/splash/drawable-port-xxhdpi-screen.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getbitpocket/bitpocket-mobile-app/907cf005fcd2c33bd9c2aa2d2a9a2281bc1a6376/resources/android/splash/drawable-port-xxhdpi-screen.png
--------------------------------------------------------------------------------
/resources/android/splash/drawable-port-xxxhdpi-screen.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getbitpocket/bitpocket-mobile-app/907cf005fcd2c33bd9c2aa2d2a9a2281bc1a6376/resources/android/splash/drawable-port-xxxhdpi-screen.png
--------------------------------------------------------------------------------
/resources/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getbitpocket/bitpocket-mobile-app/907cf005fcd2c33bd9c2aa2d2a9a2281bc1a6376/resources/icon.png
--------------------------------------------------------------------------------
/resources/icon.png.md5:
--------------------------------------------------------------------------------
1 | 172454b609fcc58c82c7e908d1276ad4
--------------------------------------------------------------------------------
/resources/ios/icon/icon-40.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getbitpocket/bitpocket-mobile-app/907cf005fcd2c33bd9c2aa2d2a9a2281bc1a6376/resources/ios/icon/icon-40.png
--------------------------------------------------------------------------------
/resources/ios/icon/icon-40@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getbitpocket/bitpocket-mobile-app/907cf005fcd2c33bd9c2aa2d2a9a2281bc1a6376/resources/ios/icon/icon-40@2x.png
--------------------------------------------------------------------------------
/resources/ios/icon/icon-50.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getbitpocket/bitpocket-mobile-app/907cf005fcd2c33bd9c2aa2d2a9a2281bc1a6376/resources/ios/icon/icon-50.png
--------------------------------------------------------------------------------
/resources/ios/icon/icon-50@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getbitpocket/bitpocket-mobile-app/907cf005fcd2c33bd9c2aa2d2a9a2281bc1a6376/resources/ios/icon/icon-50@2x.png
--------------------------------------------------------------------------------
/resources/ios/icon/icon-60.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getbitpocket/bitpocket-mobile-app/907cf005fcd2c33bd9c2aa2d2a9a2281bc1a6376/resources/ios/icon/icon-60.png
--------------------------------------------------------------------------------
/resources/ios/icon/icon-60@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getbitpocket/bitpocket-mobile-app/907cf005fcd2c33bd9c2aa2d2a9a2281bc1a6376/resources/ios/icon/icon-60@2x.png
--------------------------------------------------------------------------------
/resources/ios/icon/icon-60@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getbitpocket/bitpocket-mobile-app/907cf005fcd2c33bd9c2aa2d2a9a2281bc1a6376/resources/ios/icon/icon-60@3x.png
--------------------------------------------------------------------------------
/resources/ios/icon/icon-72.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getbitpocket/bitpocket-mobile-app/907cf005fcd2c33bd9c2aa2d2a9a2281bc1a6376/resources/ios/icon/icon-72.png
--------------------------------------------------------------------------------
/resources/ios/icon/icon-72@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getbitpocket/bitpocket-mobile-app/907cf005fcd2c33bd9c2aa2d2a9a2281bc1a6376/resources/ios/icon/icon-72@2x.png
--------------------------------------------------------------------------------
/resources/ios/icon/icon-76.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getbitpocket/bitpocket-mobile-app/907cf005fcd2c33bd9c2aa2d2a9a2281bc1a6376/resources/ios/icon/icon-76.png
--------------------------------------------------------------------------------
/resources/ios/icon/icon-76@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getbitpocket/bitpocket-mobile-app/907cf005fcd2c33bd9c2aa2d2a9a2281bc1a6376/resources/ios/icon/icon-76@2x.png
--------------------------------------------------------------------------------
/resources/ios/icon/icon-small.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getbitpocket/bitpocket-mobile-app/907cf005fcd2c33bd9c2aa2d2a9a2281bc1a6376/resources/ios/icon/icon-small.png
--------------------------------------------------------------------------------
/resources/ios/icon/icon-small@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getbitpocket/bitpocket-mobile-app/907cf005fcd2c33bd9c2aa2d2a9a2281bc1a6376/resources/ios/icon/icon-small@2x.png
--------------------------------------------------------------------------------
/resources/ios/icon/icon-small@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getbitpocket/bitpocket-mobile-app/907cf005fcd2c33bd9c2aa2d2a9a2281bc1a6376/resources/ios/icon/icon-small@3x.png
--------------------------------------------------------------------------------
/resources/ios/icon/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getbitpocket/bitpocket-mobile-app/907cf005fcd2c33bd9c2aa2d2a9a2281bc1a6376/resources/ios/icon/icon.png
--------------------------------------------------------------------------------
/resources/ios/icon/icon@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getbitpocket/bitpocket-mobile-app/907cf005fcd2c33bd9c2aa2d2a9a2281bc1a6376/resources/ios/icon/icon@2x.png
--------------------------------------------------------------------------------
/resources/ios/splash/Default-568h@2x~iphone.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getbitpocket/bitpocket-mobile-app/907cf005fcd2c33bd9c2aa2d2a9a2281bc1a6376/resources/ios/splash/Default-568h@2x~iphone.png
--------------------------------------------------------------------------------
/resources/ios/splash/Default-667h.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getbitpocket/bitpocket-mobile-app/907cf005fcd2c33bd9c2aa2d2a9a2281bc1a6376/resources/ios/splash/Default-667h.png
--------------------------------------------------------------------------------
/resources/ios/splash/Default-736h.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getbitpocket/bitpocket-mobile-app/907cf005fcd2c33bd9c2aa2d2a9a2281bc1a6376/resources/ios/splash/Default-736h.png
--------------------------------------------------------------------------------
/resources/ios/splash/Default-Landscape-736h.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getbitpocket/bitpocket-mobile-app/907cf005fcd2c33bd9c2aa2d2a9a2281bc1a6376/resources/ios/splash/Default-Landscape-736h.png
--------------------------------------------------------------------------------
/resources/ios/splash/Default-Landscape@2x~ipad.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getbitpocket/bitpocket-mobile-app/907cf005fcd2c33bd9c2aa2d2a9a2281bc1a6376/resources/ios/splash/Default-Landscape@2x~ipad.png
--------------------------------------------------------------------------------
/resources/ios/splash/Default-Landscape~ipad.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getbitpocket/bitpocket-mobile-app/907cf005fcd2c33bd9c2aa2d2a9a2281bc1a6376/resources/ios/splash/Default-Landscape~ipad.png
--------------------------------------------------------------------------------
/resources/ios/splash/Default-Portrait@2x~ipad.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getbitpocket/bitpocket-mobile-app/907cf005fcd2c33bd9c2aa2d2a9a2281bc1a6376/resources/ios/splash/Default-Portrait@2x~ipad.png
--------------------------------------------------------------------------------
/resources/ios/splash/Default-Portrait~ipad.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getbitpocket/bitpocket-mobile-app/907cf005fcd2c33bd9c2aa2d2a9a2281bc1a6376/resources/ios/splash/Default-Portrait~ipad.png
--------------------------------------------------------------------------------
/resources/ios/splash/Default@2x~iphone.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getbitpocket/bitpocket-mobile-app/907cf005fcd2c33bd9c2aa2d2a9a2281bc1a6376/resources/ios/splash/Default@2x~iphone.png
--------------------------------------------------------------------------------
/resources/ios/splash/Default~iphone.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getbitpocket/bitpocket-mobile-app/907cf005fcd2c33bd9c2aa2d2a9a2281bc1a6376/resources/ios/splash/Default~iphone.png
--------------------------------------------------------------------------------
/resources/splash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getbitpocket/bitpocket-mobile-app/907cf005fcd2c33bd9c2aa2d2a9a2281bc1a6376/resources/splash.png
--------------------------------------------------------------------------------
/resources/splash.png.md5:
--------------------------------------------------------------------------------
1 | 793b26f03f71ba46b3ba5dc0dba80162
--------------------------------------------------------------------------------
/src/api/account.ts:
--------------------------------------------------------------------------------
1 | export interface Account {
2 | _id?:string;
3 | name?:string;
4 | type:string;
5 | data:any;
6 | default?:boolean;
7 | index?:number;
8 | lastConfirmedIndex?:number;
9 | }
--------------------------------------------------------------------------------
/src/api/currency-exchange-rate.ts:
--------------------------------------------------------------------------------
1 | export interface CurrencyExchangeRate {
2 | code : string;
3 | rate? : number;
4 | }
--------------------------------------------------------------------------------
/src/api/currency-exchange-service.ts:
--------------------------------------------------------------------------------
1 | import {CurrencyExchangeRate} from './currency-exchange-rate';
2 |
3 | export interface CurrencyExchangeService {
4 | getAvailableCurrencies() : Promise>;
5 | getExchangeRates() : Promise>;
6 | getExchangeRate(code:string) : Promise;
7 | }
--------------------------------------------------------------------------------
/src/api/payment-request-handler.ts:
--------------------------------------------------------------------------------
1 | export interface PaymentRequestHandler {
2 |
3 | cancel() : void;
4 |
5 | on(event:string, handler: (data:any) => void) : PaymentRequestHandler;
6 |
7 | once(event:string, handler: (data:any) => void) : PaymentRequestHandler;
8 |
9 | }
--------------------------------------------------------------------------------
/src/api/payment-request.ts:
--------------------------------------------------------------------------------
1 | export const PAYMENT_STATUS_RECEIVED = 'received';
2 | export const PAYMENT_STATUS_OVERPAID = 'overpaid';
3 | export const PAYMENT_STATUS_PARTIAL_PAID = 'partial';
4 | export const PAYMENT_STATUS_SERVICE_ERROR = 'service_error';
5 |
6 | export interface PaymentRequest {
7 |
8 | currency: string;
9 | address: string;
10 | amount: number;
11 |
12 | txAmount?: number; // partial / over payment
13 | txid?:string;
14 | status?: string;
15 | startTime?: number;
16 |
17 | referenceAmount?: number;
18 | referenceCurrency?: string;
19 | }
--------------------------------------------------------------------------------
/src/api/payment-service.ts:
--------------------------------------------------------------------------------
1 | import { PaymentRequestHandler } from './payment-request-handler';
2 | import { PaymentRequest } from './payment-request';
3 |
4 | export interface PaymentService {
5 |
6 | createPaymentRequestHandler(paymentRequest: PaymentRequest) : PaymentRequestHandler;
7 |
8 | }
--------------------------------------------------------------------------------
/src/api/transaction-filter.ts:
--------------------------------------------------------------------------------
1 | import { Account } from './account';
2 |
3 | export interface TransactionFilter {
4 | txid?: string ,
5 | addresses?: string[] ,
6 | account?:Account ,
7 | from? : number ,
8 | to? : number ,
9 | startTime? : number ,
10 | endTime? : number
11 | }
--------------------------------------------------------------------------------
/src/api/transaction-service.ts:
--------------------------------------------------------------------------------
1 | import { TransactionFilter } from './transaction-filter';
2 | import { Transaction } from './transaction';
3 |
4 | export interface TransactionService {
5 | findTransactions(filter:TransactionFilter) : Promise;
6 | }
--------------------------------------------------------------------------------
/src/api/transaction.ts:
--------------------------------------------------------------------------------
1 | export interface Transaction {
2 | _id: string; // txid
3 | currency: string;
4 | address: string;
5 | amount: number;
6 | incomming: boolean;
7 | timestamp?: number;
8 | confirmations?: number;
9 | account?: string;
10 |
11 | paymentReferenceAmount?: number;
12 | paymentReferenceCurrency?: string;
13 | paymentStatus?: string;
14 | }
15 |
--------------------------------------------------------------------------------
/src/app/app.component.ts:
--------------------------------------------------------------------------------
1 | import * as ionic from 'ionic-angular';
2 | import { Component, ViewChild } from '@angular/core';
3 | import { StatusBar } from '@ionic-native/status-bar';
4 | import { SplashScreen } from '@ionic-native/splash-screen';
5 | import { Network } from '@ionic-native/network';
6 |
7 | // Providers
8 | import { Repository, Config, CurrencyService, AccountService } from '../providers';
9 | import { TranslateService } from '@ngx-translate/core';
10 |
11 | @Component({
12 | templateUrl: 'app.html'
13 | })
14 | export class BitPocketApp {
15 | @ViewChild(ionic.Nav) nav: ionic.Nav;
16 | @ViewChild(ionic.Menu) menu: ionic.Menu;
17 |
18 | menuItems:Array<{name:string,icon:string,page:any}> = [];
19 |
20 | constructor(
21 | protected platform: ionic.Platform,
22 | protected statusBar: StatusBar,
23 | protected splashScreen: SplashScreen,
24 | protected network: Network,
25 | protected app:ionic.App,
26 | protected config:Config,
27 | protected ionicConfig:ionic.Config,
28 | protected currency:CurrencyService,
29 | protected repository:Repository,
30 | protected accountService:AccountService,
31 | protected translate: TranslateService) {
32 |
33 | platform.ready().then(() => {
34 | this.statusBar.styleDefault();
35 | this.initLanguage();
36 | this.initApp();
37 |
38 | // watch network for a disconnect
39 | this.network.onDisconnect().subscribe(() => {
40 | this.nav.setRoot('offline');
41 | });
42 |
43 | // watch network for a connection
44 | this.network.onConnect().subscribe(() => {
45 | this.initNavState();
46 | });
47 | });
48 | }
49 |
50 | isOnline() {
51 | if (this.network.type != 'none') {
52 | return true;
53 | } else {
54 | return false;
55 | }
56 | }
57 |
58 | triggerUpdateTask() {
59 | if (this.isOnline()) {
60 | this.currency.updateCurrencyRate();
61 | }
62 |
63 | setTimeout(() => {
64 | this.triggerUpdateTask();
65 | },1000 * 60 * 10);
66 | }
67 |
68 | hideSplashscreen() {
69 | setTimeout(() => {
70 | this.splashScreen.hide();
71 | }, 2000);
72 | }
73 |
74 | initNavState() {
75 | if (this.isOnline()) {
76 | this.accountService.getAccounts()
77 | .then((accounts) => {
78 | if (accounts.length > 0) {
79 | this.nav.setRoot('amount');
80 | } else {
81 | this.nav.setRoot('account-creation');
82 | }
83 | this.hideSplashscreen();
84 | }).catch(() => {
85 | this.nav.setRoot('account-creation');
86 | this.hideSplashscreen();
87 | });
88 | } else {
89 | this.nav.setRoot('offline');
90 | this.hideSplashscreen();
91 | }
92 | }
93 |
94 | initLanguage() {
95 | this.translate.setDefaultLang('en');
96 | let langs = ['de','en','pl'];
97 | let langIndex = langs.indexOf(this.translate.getBrowserLang());
98 |
99 | if (langIndex == -1) {
100 | this.translate.use('en');
101 | } else {
102 | this.translate.use(langs[langIndex]);
103 | }
104 |
105 | let languageIdentifiers = ['MENU.PAYMENT', 'MENU.ACCOUNTS', 'MENU.SETTINGS', 'BUTTON.BACK'];
106 | this.translate.get(languageIdentifiers)
107 | .subscribe((res:Array) => {
108 | this.menuItems[0] = { name:res[languageIdentifiers[0]], icon:'keypad' , page:'amount' };
109 | this.menuItems[1] = { name:res[languageIdentifiers[1]], icon:'list', page:'account' };
110 | this.menuItems[2] = { name:res[languageIdentifiers[2]], icon:'options', page:'settings' };
111 | this.ionicConfig.set('ios', 'backButtonText', res[languageIdentifiers[3]]);
112 | });
113 | }
114 |
115 | initApp() {
116 | Promise.all([
117 | this.repository.init() ,
118 | this.config.initConfig()
119 | ]).then(() => {
120 | this.triggerUpdateTask();
121 | this.initNavState();
122 | });
123 | }
124 |
125 | openPage(page:any) {
126 | this.menu.close();
127 | this.nav.setRoot(page);
128 | }
129 | }
130 |
--------------------------------------------------------------------------------
/src/app/app.html:
--------------------------------------------------------------------------------
1 |
2 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/src/app/app.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule, ErrorHandler } from '@angular/core';
2 | import { BrowserModule } from '@angular/platform-browser';
3 | import { HttpClientModule, HttpClient } from '@angular/common/http';
4 | import { IonicStorageModule, Storage } from '@ionic/storage';
5 | import { IonicApp, IonicModule, IonicErrorHandler, ModalController } from 'ionic-angular';
6 |
7 | // Pouch DB
8 | import PouchDB from 'pouchdb';
9 | import pouchdbUpsert from 'pouchdb-upsert';
10 | import pouchFind from 'pouchdb-find';
11 | PouchDB.plugin(pouchdbUpsert);
12 | PouchDB.plugin(pouchFind);
13 | // PouchDB.debug.enable('pouchdb:find');
14 | window['PouchDB'] = PouchDB;
15 |
16 | // Main Component
17 | import { BitPocketApp } from './app.component';
18 |
19 | // Providers
20 | import {
21 | Config,
22 | Repository,
23 | QRScanner,
24 | CurrencyService,
25 | AccountService,
26 | TransactionServiceWrapper,
27 | CryptocurrencyService,
28 | PaymentService,
29 | TransactionStorageService,
30 | AccountSyncService,
31 | provideCurrencyService,
32 | provideAccountService,
33 | provideAccountSyncService,
34 | provideTransactionService,
35 | provideTransactionStorageService,
36 | providePaymentService,
37 | provideCryptocurrencyService,
38 | provideRepository,
39 | provideConfig,
40 | provideQRScanner,
41 | provideBitcoinAverageExchangeService
42 | } from '../providers/index';
43 |
44 | // Ionic Native
45 | import { StatusBar } from '@ionic-native/status-bar';
46 | import { Network } from '@ionic-native/network';
47 | import { SplashScreen } from '@ionic-native/splash-screen';
48 | import { File } from '@ionic-native/file';
49 | import { FileOpener } from '@ionic-native/file-opener';
50 |
51 | // Translations
52 | import { TranslateModule, TranslateLoader } from '@ngx-translate/core';
53 | import { TranslateHttpLoader } from '@ngx-translate/http-loader';
54 |
55 | // Exchange Services
56 | import { BitcoinAverageExchangeService } from '../providers/currency/bitcoinaverage-service';
57 |
58 | // Pipes
59 | import { BitpocketCurrencyPipe } from '../pipes/currency';
60 | import { BitpocketUnitPipe } from './../pipes/unit';
61 | import { BitpocketFiatPipe } from './../pipes/fiat';
62 |
63 | export function createTranslateLoader(http: HttpClient) {
64 | return new TranslateHttpLoader(http, './assets/i18n/', '.json');
65 | }
66 |
67 | @NgModule({
68 | declarations: [
69 | BitPocketApp
70 | ],
71 | imports: [
72 | BrowserModule,
73 | HttpClientModule,
74 | IonicModule.forRoot(BitPocketApp) ,
75 | IonicStorageModule.forRoot(),
76 | TranslateModule.forRoot({
77 | loader : {
78 | provide: TranslateLoader,
79 | useFactory: (createTranslateLoader),
80 | deps: [HttpClient]
81 | }
82 | })
83 | ],
84 | bootstrap: [IonicApp],
85 | entryComponents: [
86 | BitPocketApp
87 | ],
88 | providers: [
89 | { provide: ErrorHandler, useClass: IonicErrorHandler } ,
90 | { provide: CurrencyService, useFactory: provideCurrencyService, deps: [Config, BitcoinAverageExchangeService] } ,
91 | { provide: AccountService, useFactory: provideAccountService, deps: [CryptocurrencyService, Config, Repository] } ,
92 | { provide: AccountSyncService, useFactory: provideAccountSyncService, deps: [TransactionServiceWrapper, TransactionStorageService, AccountService, CryptocurrencyService] } ,
93 | { provide: TransactionServiceWrapper, useFactory: provideTransactionService, deps: [HttpClient, CryptocurrencyService] } ,
94 | { provide: TransactionStorageService, useFactory: provideTransactionStorageService, deps: [Repository] } ,
95 | { provide: PaymentService, useFactory: providePaymentService, deps: [TransactionServiceWrapper, CryptocurrencyService] } ,
96 | { provide: CryptocurrencyService, useFactory: provideCryptocurrencyService } ,
97 | { provide: Repository, useFactory:provideRepository } ,
98 | { provide:Config, useFactory:provideConfig, deps:[Storage] } ,
99 | { provide:QRScanner, useFactory:provideQRScanner, deps:[ModalController] },
100 | { provide:BitcoinAverageExchangeService, useFactory:provideBitcoinAverageExchangeService, deps:[HttpClient, Config] },
101 | SplashScreen,
102 | Network,
103 | StatusBar,
104 | File,
105 | FileOpener
106 | ]
107 | })
108 | export class AppModule {}
109 |
--------------------------------------------------------------------------------
/src/app/app.scss:
--------------------------------------------------------------------------------
1 | @import "scss/buttons";
2 | @import "scss/content";
3 | @import "scss/logo";
4 | @import "scss/md";
5 | @import "scss/menu";
6 | @import "scss/payment";
7 | @import "scss/utilities";
8 | @import "scss/numpad";
9 |
10 | .toolbar-title-md {
11 | text-align:left !important;
12 | }
13 |
14 | .toolbar-title-ios {
15 | padding-top:12px;
16 | }
17 |
18 | ion-app {
19 | &.md,
20 | &.ios {
21 | background-color: transparent !important;
22 | .ion-page.show-page ~ .nav-decor {
23 | background-color: transparent !important;
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/app/main.prod.ts:
--------------------------------------------------------------------------------
1 | import { platformBrowser } from '@angular/platform-browser';
2 | import { enableProdMode } from '@angular/core';
3 |
4 | import { AppModuleNgFactory } from './app.module.ngfactory';
5 |
6 | enableProdMode();
7 | platformBrowser().bootstrapModuleFactory(AppModuleNgFactory);
8 |
--------------------------------------------------------------------------------
/src/app/main.ts:
--------------------------------------------------------------------------------
1 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
2 |
3 | import { AppModule } from './app.module';
4 |
5 | platformBrowserDynamic().bootstrapModule(AppModule);
6 |
--------------------------------------------------------------------------------
/src/app/scss/_buttons.scss:
--------------------------------------------------------------------------------
1 | .button-qr-scanner {
2 | &.button-md {
3 | margin:0 !important;
4 | padding: 20px 9px !important;
5 | }
6 | &.button-ios {
7 | margin:0 !important;
8 | padding: 20px 9px !important;
9 | }
10 | }
11 |
12 | .button-large {
13 | width:100%;
14 | line-height: $font-size-x-large;
15 | height: $font-size-x-large;
16 | border-radius: $button-border-radius;
17 | }
18 |
19 | .button-bottom {
20 | width:80%;
21 | position: absolute;
22 | bottom: 8vh;
23 | left: 50%;
24 | transform: translateX(-50%);
25 | text-align:center;
26 |
27 | .fab {
28 | display:inline-block;
29 | margin:0 10px;
30 | }
31 |
32 | }
33 |
34 | button {
35 | ion-spinner {
36 | margin-right:10px;
37 | width:20px;
38 | height:20px;
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/app/scss/_content.scss:
--------------------------------------------------------------------------------
1 | ion-nav ion-content, ion-modal ion-content {
2 | background: url(../assets/img/background.png) no-repeat center center;
3 | background-size: cover;
4 | }
5 |
6 | [no-scroll] {
7 | .scroll-content {
8 | overflow: hidden;
9 | }
10 | ::-webkit-scrollbar,
11 | *::-webkit-scrollbar {
12 | display: none;
13 | }
14 | }
--------------------------------------------------------------------------------
/src/app/scss/_logo.scss:
--------------------------------------------------------------------------------
1 | bitpocket-logo {
2 | display:block;
3 | position:absolute;
4 | bottom:10px;
5 | right:0px;
6 |
7 | img {
8 | max-width:2rem;
9 | max-height:2rem;
10 |
11 | &.ios {
12 | margin-right:16px;
13 | }
14 |
15 | &.md {
16 | margin-bottom:6px;
17 | margin-right:18px;
18 | }
19 | }
20 |
21 | &[loading] > img {
22 | -webkit-animation:spin 2s linear infinite;
23 | -moz-animation:spin 2s linear infinite;
24 | animation:spin 2s linear infinite;
25 | @-moz-keyframes spin { 100% { -moz-transform: rotate(360deg); } }
26 | @-webkit-keyframes spin { 100% { -webkit-transform: rotate(360deg); } }
27 | @keyframes spin { 100% { -webkit-transform: rotate(360deg); transform:rotate(360deg); } }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/app/scss/_md.scss:
--------------------------------------------------------------------------------
1 | .list-header-md {
2 | padding-top:13px;
3 | background:none;
4 | border:none;
5 | }
--------------------------------------------------------------------------------
/src/app/scss/_menu.scss:
--------------------------------------------------------------------------------
1 | .menu-header {
2 | background-color: #434954;
3 | padding: 16px;
4 | width: 100%;
5 | min-height: 150px;
6 | text-align: center;
7 | }
8 |
9 | .menu-header img {
10 | max-width: 200px;
11 | min-height: 115px;
12 | margin-left: -15px;
13 | padding: 25px 0 20px 0;
14 | }
15 |
16 | .bar-button-close ion-icon {
17 | padding: 0 14px;
18 | font-size: 2.4rem;
19 | }
20 |
--------------------------------------------------------------------------------
/src/app/scss/_numpad.scss:
--------------------------------------------------------------------------------
1 |
2 | .numpad {
3 | display: flex;
4 | flex-flow: column;
5 | justify-content: center;
6 |
7 | .button-number-backspaceinput {
8 | font-size: $font-size-x-small;
9 | }
10 | }
11 |
12 | .numpad-row {
13 | display:flex;
14 | flex-flow: row nowrap;
15 | justify-content: center;
16 | align-content: center;
17 | align-items: center;
18 | }
19 |
20 | .numpad-button-row {
21 | display:flex;
22 | justify-content: space-between;
23 | margin-top:20px;
24 | flex: 0 1 232px;
25 | @media(min-width: $bitpocket-xs-min) {
26 | flex: 0 1 244px;
27 | }
28 | @media(min-width: $bitpocket-sm-min) {
29 | flex: 0 1 280px;
30 | }
31 | @media(min-width: $bitpocket-md-min) {
32 | flex: 0 1 300px;
33 | }
34 | }
35 |
36 | .numpad-primary-button {
37 | font-size: $font-size-base;
38 | border-radius: $button-border-radius;
39 | flex: 1 1 45%;
40 | }
41 |
42 | .numpad-primary-button + .numpad-primary-button {
43 | margin-left:15px;
44 | }
45 |
46 | .numpad-button {
47 | flex: 0 1 60px;
48 | height: 60px;
49 | margin: 5px 13px;
50 | padding:0;
51 | font-size: $font-size-x-medium;
52 | border-radius: 100%;
53 |
54 | @media(min-width: $bitpocket-xs-min) {
55 | flex: 0 1 64px;
56 | height: 64px;
57 | }
58 | @media(min-width: $bitpocket-sm-min) {
59 | flex: 0 1 70px;
60 | height: 70px;
61 | margin:10px 15px;
62 | }
63 | @media(min-width: $bitpocket-md-min) {
64 | flex: 0 1 80px;
65 | height: 80px;
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/src/app/scss/_payment.scss:
--------------------------------------------------------------------------------
1 | .payment-specification {
2 | display:flex;
3 | flex-flow:row wrap;
4 | color: white;
5 | margin: 2vh 8vw 0 8vw;
6 |
7 | .payment-amount {
8 | line-height: 0;
9 | display: flex;
10 | flex-direction: column;
11 | justify-content: center;
12 | flex-basis:65%;
13 | text-align: right;
14 | font-size: $font-size-x-small;
15 | overflow: hidden;
16 | white-space: nowrap;
17 |
18 | .active {
19 | font-size: $font-size-x-large;
20 | }
21 | }
22 |
23 | .payment-currency {
24 | flex-basis:35%;
25 | font-size: $font-size-x-small;
26 | overflow: hidden;
27 | min-height: 50px;
28 |
29 | .checkbox-disabled {
30 | opacity: 1 !important;
31 | }
32 |
33 | &.vertical-align {
34 | line-height:50px;
35 | }
36 |
37 | ion-label {
38 | font-size: $font-size-small;
39 | margin-left:3px;
40 | }
41 |
42 | ion-checkbox, ion-label {
43 | display: inline-block;
44 | vertical-align: middle;
45 | }
46 | }
47 | }
48 |
49 | .payment-qr {
50 | padding:0 6%;
51 | text-align: center;
52 | height: 35%;
53 |
54 | div {
55 | display:inline-block;
56 | background:white;
57 | img {
58 | margin:20px;
59 | }
60 | }
61 | }
62 |
63 | .payment-info {
64 | color: white;
65 | margin-top:95px;
66 | text-align: center;
67 | font-size: 1.2em;
68 |
69 | p {
70 | font-size:12px;
71 | }
72 |
73 | .payment-text {
74 | margin:0;
75 | }
76 | }
77 |
78 | .payment-result-box {
79 | color:white;
80 | height: 70%;
81 | margin:0 10%;
82 | text-align: center;
83 |
84 | .payment-result-icon {
85 | padding: 20% 10% 5% 10%;
86 | font-size: 8.0rem;
87 | }
88 |
89 | .payment-result-text {
90 | font-size: $font-size-small;
91 | }
92 |
93 | }
94 |
95 | .payment-result-history {
96 | position: absolute;
97 | bottom: 5%;
98 | left: 50%;
99 | transform: translateX(-50%);
100 | .button-show-history {
101 | border-radius: $button-border-radius;
102 | }
103 | }
104 |
105 | .transaction-failed .payment-result-history {
106 | display:none;
107 | }
108 |
--------------------------------------------------------------------------------
/src/app/scss/_utilities.scss:
--------------------------------------------------------------------------------
1 | .divider-box {
2 | margin: 1.6vh 0 4vh 0;
3 | position: relative;
4 | .divider {
5 | position: relative;
6 | top: 50%;
7 | transform: translateY(-50%);
8 | border: 0;
9 | height: 2px;
10 | background-image: linear-gradient(to right, rgba(255,255,255, 0), rgba(255,255,255, 0.75), rgba(255,255,255, 0));
11 | &.black {
12 | background-image: linear-gradient(to right, rgba(0,0,0, 0), rgba(0,0,0, 0.75), rgba(0,0,0, 0))
13 | }
14 | }
15 | }
16 |
17 | .invisible {
18 | visibility: hidden;
19 | }
20 |
21 | [dynamicFontSize] {
22 | display:inline-block;
23 | }
24 |
25 |
26 |
--------------------------------------------------------------------------------
/src/assets/fonts/Ubuntu-B.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getbitpocket/bitpocket-mobile-app/907cf005fcd2c33bd9c2aa2d2a9a2281bc1a6376/src/assets/fonts/Ubuntu-B.ttf
--------------------------------------------------------------------------------
/src/assets/fonts/Ubuntu-C.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getbitpocket/bitpocket-mobile-app/907cf005fcd2c33bd9c2aa2d2a9a2281bc1a6376/src/assets/fonts/Ubuntu-C.ttf
--------------------------------------------------------------------------------
/src/assets/fonts/Ubuntu-L.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getbitpocket/bitpocket-mobile-app/907cf005fcd2c33bd9c2aa2d2a9a2281bc1a6376/src/assets/fonts/Ubuntu-L.ttf
--------------------------------------------------------------------------------
/src/assets/fonts/Ubuntu-M.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getbitpocket/bitpocket-mobile-app/907cf005fcd2c33bd9c2aa2d2a9a2281bc1a6376/src/assets/fonts/Ubuntu-M.ttf
--------------------------------------------------------------------------------
/src/assets/fonts/Ubuntu-R.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getbitpocket/bitpocket-mobile-app/907cf005fcd2c33bd9c2aa2d2a9a2281bc1a6376/src/assets/fonts/Ubuntu-R.ttf
--------------------------------------------------------------------------------
/src/assets/icon/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getbitpocket/bitpocket-mobile-app/907cf005fcd2c33bd9c2aa2d2a9a2281bc1a6376/src/assets/icon/favicon.ico
--------------------------------------------------------------------------------
/src/assets/img/background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getbitpocket/bitpocket-mobile-app/907cf005fcd2c33bd9c2aa2d2a9a2281bc1a6376/src/assets/img/background.png
--------------------------------------------------------------------------------
/src/assets/img/bitcoin-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getbitpocket/bitpocket-mobile-app/907cf005fcd2c33bd9c2aa2d2a9a2281bc1a6376/src/assets/img/bitcoin-logo.png
--------------------------------------------------------------------------------
/src/assets/img/bitpocket_appicon_1024x1024.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getbitpocket/bitpocket-mobile-app/907cf005fcd2c33bd9c2aa2d2a9a2281bc1a6376/src/assets/img/bitpocket_appicon_1024x1024.png
--------------------------------------------------------------------------------
/src/assets/img/bitpocket_appicon_white1024x1024.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getbitpocket/bitpocket-mobile-app/907cf005fcd2c33bd9c2aa2d2a9a2281bc1a6376/src/assets/img/bitpocket_appicon_white1024x1024.png
--------------------------------------------------------------------------------
/src/assets/img/bitpocket_menu_header.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getbitpocket/bitpocket-mobile-app/907cf005fcd2c33bd9c2aa2d2a9a2281bc1a6376/src/assets/img/bitpocket_menu_header.jpg
--------------------------------------------------------------------------------
/src/assets/img/noconnection.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getbitpocket/bitpocket-mobile-app/907cf005fcd2c33bd9c2aa2d2a9a2281bc1a6376/src/assets/img/noconnection.png
--------------------------------------------------------------------------------
/src/assets/img/scanner.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
78 |
--------------------------------------------------------------------------------
/src/assets/img/testnet-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getbitpocket/bitpocket-mobile-app/907cf005fcd2c33bd9c2aa2d2a9a2281bc1a6376/src/assets/img/testnet-logo.png
--------------------------------------------------------------------------------
/src/assets/sound/paid.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getbitpocket/bitpocket-mobile-app/907cf005fcd2c33bd9c2aa2d2a9a2281bc1a6376/src/assets/sound/paid.mp3
--------------------------------------------------------------------------------
/src/components/dynamic-font-size/dynamic-font-size.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { IonicModule } from 'ionic-angular';
3 | import { DynamicFontSize } from './dynamic-font-size';
4 |
5 | @NgModule({
6 | declarations: [
7 | DynamicFontSize,
8 | ],
9 | imports: [
10 | IonicModule ,
11 |
12 | ],
13 | exports: [
14 | DynamicFontSize
15 | ]
16 | })
17 | export class DynamicFontSizeModule {}
--------------------------------------------------------------------------------
/src/components/dynamic-font-size/dynamic-font-size.ts:
--------------------------------------------------------------------------------
1 | import { Directive, ElementRef, Input, Renderer } from '@angular/core';
2 |
3 | @Directive({ selector: '[dynamicFontSize]' })
4 | export class DynamicFontSize {
5 |
6 | @Input() maxFontSize:any;
7 | @Input() minFontSize:any;
8 | @Input() content:string = "";
9 |
10 | constructor(private el: ElementRef, private renderer: Renderer) {
11 | this.el.nativeElement.style.fontSize = this.maxFontSize + "px";
12 | }
13 |
14 | calculateFontSize() {
15 | let childWidth = this.el.nativeElement.clientWidth;
16 | let parentWidth = this.el.nativeElement.parentElement.clientWidth;
17 | let fontSize = parseInt(this.el.nativeElement.style.fontSize) || parseInt(this.maxFontSize);
18 | let calculatedSize = (fontSize * ((parentWidth/childWidth)-0.1));
19 |
20 | if (calculatedSize < parseInt(this.maxFontSize) && calculatedSize > parseInt(this.minFontSize)) {
21 | this.el.nativeElement.style.fontSize = calculatedSize + "px";
22 | childWidth = this.el.nativeElement.clientWidth + 20;
23 |
24 | if (childWidth >= parentWidth) {
25 | setTimeout(() => {
26 | this.calculateFontSize();
27 | },0);
28 | }
29 | }
30 | }
31 |
32 | ngAfterViewInit() {
33 | setTimeout(() => {
34 | this.calculateFontSize();
35 | },50);
36 | }
37 |
38 | ngOnChanges() {
39 | this.calculateFontSize();
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/components/logo/logo.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { IonicModule } from 'ionic-angular';
3 | import { Logo } from './logo';
4 |
5 | @NgModule({
6 | declarations: [
7 | Logo
8 | ],
9 | imports: [
10 | IonicModule
11 | ],
12 | exports: [
13 | Logo
14 | ]
15 | })
16 | export class LogoModule {}
--------------------------------------------------------------------------------
/src/components/logo/logo.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 | import { NavController, Config } from 'ionic-angular';
3 |
4 | @Component({
5 | selector: 'bitpocket-logo' ,
6 | template: '
'
7 | })
8 | export class Logo {
9 |
10 | modeClass:string = "";
11 |
12 | constructor(
13 | protected config:Config,
14 | protected navigation:NavController) {
15 | this.modeClass = config.get('mode', 'md');
16 | }
17 |
18 | click(ev: UIEvent) {
19 | this.navigation.setRoot('amount');
20 | }
21 |
22 | }
--------------------------------------------------------------------------------
/src/declarations.d.ts:
--------------------------------------------------------------------------------
1 | /*
2 | Declaration files are how the Typescript compiler knows about the type information(or shape) of an object.
3 | They're what make intellisense work and make Typescript know all about your code.
4 |
5 | A wildcard module is declared below to allow third party libraries to be used in an app even if they don't
6 | provide their own type declarations.
7 |
8 | To learn more about using third party libraries in an Ionic app, check out the docs here:
9 | http://ionicframework.com/docs/v2/resources/third-party-libs/
10 |
11 | For more info on type definition files, check out the Typescript docs here:
12 | https://www.typescriptlang.org/docs/handbook/declaration-files/introduction.html
13 | */
14 | declare module '*';
--------------------------------------------------------------------------------
/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | BitPocket
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/src/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Ionic",
3 | "short_name": "Ionic",
4 | "start_url": "index.html",
5 | "display": "standalone",
6 | "icons": [{
7 | "src": "assets/imgs/logo.png",
8 | "sizes": "512x512",
9 | "type": "image/png"
10 | }],
11 | "background_color": "#4e8ef7",
12 | "theme_color": "#4e8ef7"
13 | }
--------------------------------------------------------------------------------
/src/pages/account-creation/account-creation.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |

7 |
8 | {{ 'TITLE.ONBOARDING' | translate }}
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
35 |
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/src/pages/account-creation/account-creation.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { IonicPageModule } from 'ionic-angular';
3 | import { LogoModule } from '../../components/logo/logo.module';
4 | import { TranslateModule } from '@ngx-translate/core';
5 | import { AccountCreationPage } from './account-creation';
6 |
7 | @NgModule({
8 | declarations: [
9 | AccountCreationPage,
10 | ],
11 | imports: [
12 | IonicPageModule.forChild(AccountCreationPage),
13 | LogoModule ,
14 | TranslateModule
15 | ],
16 | exports: [
17 | AccountCreationPage
18 | ]
19 | })
20 | export class AccountCreationPageModule {}
--------------------------------------------------------------------------------
/src/pages/account-creation/account-creation.scss:
--------------------------------------------------------------------------------
1 | .logo {
2 | margin-top:10vh;
3 | text-align:center;
4 | img {
5 | display: inline-block;
6 | height: 5rem;
7 | }
8 | }
9 |
10 | .title {
11 | color: white;
12 | font-size: $font-size-x-medium;
13 | font-weight: 300;
14 | display: block;
15 | text-align:center;
16 | }
17 |
--------------------------------------------------------------------------------
/src/pages/account-creation/account-creation.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 | import { NavController, AlertController, MenuController, IonicPage } from 'ionic-angular';
3 | import { TranslateService } from '@ngx-translate/core'
4 | import { Config, AccountService, QRScanner, AccountSyncService } from '../../providers/index';
5 | import 'rxjs/add/operator/toPromise';
6 |
7 | @IonicPage({
8 | name : 'account-creation'
9 | })
10 | @Component({
11 | templateUrl : 'account-creation.html'
12 | })
13 | export class AccountCreationPage {
14 |
15 | accountInput:string = "";
16 | account:any = null;
17 | loading:boolean = false;
18 |
19 | constructor(
20 | private accountSyncService: AccountSyncService ,
21 | private qrscanner:QRScanner,
22 | private config: Config,
23 | private accountService: AccountService,
24 | private nav:NavController,
25 | private alertController: AlertController,
26 | private menuController: MenuController,
27 | private translate: TranslateService) {
28 | }
29 |
30 | ionViewWillEnter() {
31 | this.loading = false;
32 | this.menuController.enable(false);
33 | }
34 |
35 | ionViewWillLeave() {
36 | this.menuController.enable(true);
37 | }
38 |
39 | parseAccount() {
40 | let account = this.accountService.parseAccountInput(this.accountInput);
41 | account.name = "Bitcoin";
42 | return account;
43 | }
44 |
45 | triggerAlert() {
46 | Promise.all([
47 | this.translate.get('TITLE.INVALID_INPUT').toPromise() ,
48 | this.translate.get('TEXT.INVALID_ACCOUNT_INPUT').toPromise() ,
49 | this.translate.get('BUTTON.OK').toPromise()
50 | ]).then((texts) => {
51 | let alert = this.alertController.create({
52 | title: texts[0],
53 | subTitle: texts[1],
54 | buttons: [texts[2]]
55 | });
56 | alert.present();
57 | this.loading = false;
58 | });
59 | }
60 |
61 | start() {
62 | try {
63 | this.loading = true;
64 |
65 | if (!this.account) {
66 | this.account = this.parseAccount();
67 | }
68 |
69 | console.log(this.account);
70 | this.accountService.addAccount(this.account)
71 | .then(account => {
72 | this.account = account;
73 | return this.config.set(Config.CONFIG_KEY_DEFAULT_ACCOUNT, account._id);
74 | }).then(() => {
75 | return this.accountSyncService.syncAccount(this.account);
76 | }).then(() => {
77 | this.nav.setRoot('amount');
78 | }).catch(e => {
79 | this.triggerAlert();
80 | console.error(e);
81 | });
82 | } catch (e) {
83 | this.account = '';
84 | this.triggerAlert();
85 | }
86 | }
87 |
88 | scan() {
89 | this.qrscanner.scan((text) => {
90 | this.accountInput = text;
91 | let account = this.parseAccount();
92 |
93 | if (account) {
94 | this.account = account;
95 | return true;
96 | } else {
97 | return false;
98 | }
99 | });
100 | }
101 |
102 | }
--------------------------------------------------------------------------------
/src/pages/account-form/account-form.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {{ 'TITLE.ACCOUNTS' | translate }}
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 | {{ 'TEXT.NAME' | translate }}
13 |
14 |
15 |
16 |
17 | {{ 'TEXT.' + account.type | translate }}
18 |
19 |
20 |
21 |
22 |
23 |
24 | {{ 'TEXT.DEFAULT_ACCOUNT' | translate }}
25 |
26 |
27 |
28 |
29 |
30 |
31 | {{ 'TEXT.REMOVE_ACCOUNT' | translate }}
32 |
33 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/src/pages/account-form/account-form.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { IonicPageModule } from 'ionic-angular';
3 | import { LogoModule } from '../../components/logo/logo.module';
4 | import { TranslateModule } from '@ngx-translate/core';
5 | import { AccountFormPage } from './account-form';
6 |
7 | @NgModule({
8 | declarations: [
9 | AccountFormPage,
10 | ],
11 | imports: [
12 | IonicPageModule.forChild(AccountFormPage),
13 | LogoModule ,
14 | TranslateModule
15 | ],
16 | exports: [
17 | AccountFormPage
18 | ]
19 | })
20 | export class AccountFormPageModule {}
--------------------------------------------------------------------------------
/src/pages/account/account.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {{ 'TITLE.ACCOUNTS' | translate }}
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 | {{ account.name }}
17 | {{ 'TEXT.' + account.type | translate }}
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/src/pages/account/account.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { IonicPageModule } from 'ionic-angular';
3 | import { LogoModule } from '../../components/logo/logo.module';
4 | import { TranslateModule } from '@ngx-translate/core';
5 | import { AccountPage } from './account';
6 |
7 | @NgModule({
8 | declarations: [
9 | AccountPage,
10 | ],
11 | imports: [
12 | IonicPageModule.forChild(AccountPage),
13 | LogoModule ,
14 | TranslateModule
15 | ],
16 | exports: [
17 | AccountPage
18 | ]
19 | })
20 | export class AccountPageModule {}
--------------------------------------------------------------------------------
/src/pages/account/account.ts:
--------------------------------------------------------------------------------
1 | import { NavController, IonicPage } from 'ionic-angular';
2 | import { Component } from '@angular/core';
3 | import { Config, AccountService } from './../../providers/index';
4 |
5 | @IonicPage({
6 | name : 'account' ,
7 | defaultHistory: ['amount']
8 | })
9 | @Component({
10 | templateUrl : 'account.html'
11 | })
12 | export class AccountPage {
13 |
14 | accounts:any[];
15 | defaultAccount:string = "";
16 |
17 | constructor(
18 | protected config:Config,
19 | protected navController:NavController,
20 | protected accountService:AccountService) {}
21 |
22 | openAccountEditForm(i:number) {
23 | this.navController.push('account-form', { id : this.accounts[i]._id });
24 | }
25 |
26 | openAccountCreateForm() {
27 | this.navController.push('account-form');
28 | }
29 |
30 | openAccountHistory(i:number) {
31 | this.navController.push('history', { accountId : this.accounts[i]._id });
32 | }
33 |
34 | ionViewWillEnter() {
35 | this.accounts = [];
36 |
37 | Promise.all([
38 | this.accountService.getAccounts() ,
39 | this.config.get(Config.CONFIG_KEY_DEFAULT_ACCOUNT)
40 | ]).then(promised => {
41 | for (let account of promised[0]) {
42 | if (/bitcoin/.test(account.type)) {
43 | account['icon'] = 'bitcoin';
44 | }
45 | if (/testnet/.test(account.type)) {
46 | account['icon'] = 'testnet';
47 | }
48 | this.defaultAccount = promised[1];
49 | this.accounts.push(account);
50 | }
51 | });
52 | }
53 |
54 | }
--------------------------------------------------------------------------------
/src/pages/amount/amount.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {{ 'TITLE.REQUEST_AMOUNT' | translate }}
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 | {{ currency }}
13 |
14 |
15 |
16 |
17 | {{ digits }}{{ separator }}{{ decimals }}
18 |
19 | {{ exchangedAmount }}
20 |
21 |
22 |
23 |
24 | {{ bitcoinUnit }}
25 |
26 |
27 |
28 |
29 | {{ digits }}{{ separator }}{{ decimals }}
30 |
31 | {{ exchangedAmount }}
32 |
33 |
34 |
35 |
36 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
--------------------------------------------------------------------------------
/src/pages/amount/amount.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { IonicPageModule } from 'ionic-angular';
3 | import { LogoModule } from '../../components/logo/logo.module';
4 | import { DynamicFontSizeModule } from '../../components/dynamic-font-size/dynamic-font-size.module';
5 | import { AmountPage } from './amount';
6 | import { TranslateModule } from '@ngx-translate/core';
7 |
8 | @NgModule({
9 | declarations: [
10 | AmountPage,
11 | ],
12 | imports: [
13 | IonicPageModule.forChild(AmountPage),
14 | TranslateModule ,
15 | DynamicFontSizeModule ,
16 | LogoModule
17 | ],
18 | exports: [
19 | AmountPage
20 | ]
21 | })
22 | export class AmountPageModule {}
--------------------------------------------------------------------------------
/src/pages/export/export.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | {{ 'TITLE.EXPORT' | translate }}
7 |
8 |
9 |
10 |
11 |
12 |
13 | {{ 'TEXT.START_DATE' | translate }}
14 |
15 |
16 |
17 | {{ 'TEXT.END_DATE' | translate }}
18 |
19 |
20 |
21 |
22 | {{ 'TEXT.ONLY_INCOMMING' | translate }}
23 |
24 |
25 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/src/pages/export/export.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { IonicPageModule } from 'ionic-angular';
3 | import { ExportPage } from './export';
4 | import { TranslateModule } from '@ngx-translate/core';
5 |
6 | @NgModule({
7 | declarations: [
8 | ExportPage,
9 | ],
10 | imports: [
11 | IonicPageModule.forChild(ExportPage),
12 | TranslateModule
13 | ],
14 | })
15 | export class ExportPageModule {}
16 |
--------------------------------------------------------------------------------
/src/pages/history/history.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {{ 'TITLE.HISTORY' | translate }}
5 |
6 |
7 |
8 |
9 | 0">
10 |
11 |
12 |
13 |
14 | {{ currencySymbol }} {{ transaction.amount | bitpocketUnit:'BTC':currencySymbol | bitpocketCurrency:currencySeparator:currencyThousandsPoint:currencyPrecision }} ({{ referenceCurrencySymbol }} {{ transaction.amount | bitpocketFiat:'BTC':referenceCurrencyRate | bitpocketCurrency:currencySeparator:currencyThousandsPoint }})
15 | = 6" name="checkmark-circle"> {{ (transaction.timestamp * 1000) | date:dateTimeFormat }}
16 |
17 |
18 |
21 |
24 |
25 |
26 |
27 |
28 | {{ 'TEXT.NO_TRANSACTIONS' | translate }}
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/src/pages/history/history.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { IonicPageModule } from 'ionic-angular';
3 | import { LogoModule } from '../../components/logo/logo.module';
4 | import { BitpocketCurrencyPipeModule } from '../../pipes/bitpocket-currency/bitpocket-currency.module';
5 | import { BitpocketUnitPipeModule } from '../../pipes/bitpocket-unit/bitpocket-unit.module';
6 | import { BitpocketFiatPipeModule } from '../../pipes/bitpocket-fiat/bitpocket-fiat.module';
7 | import { TranslateModule } from '@ngx-translate/core';
8 | import { HistoryPage } from './history';
9 |
10 | @NgModule({
11 | declarations: [
12 | HistoryPage,
13 | ],
14 | imports: [
15 | IonicPageModule.forChild(HistoryPage),
16 | LogoModule ,
17 | TranslateModule ,
18 | BitpocketCurrencyPipeModule ,
19 | BitpocketUnitPipeModule ,
20 | BitpocketFiatPipeModule
21 | ],
22 | exports: [
23 | HistoryPage
24 | ]
25 | })
26 | export class HistoryPageModule {}
--------------------------------------------------------------------------------
/src/pages/history/history.scss:
--------------------------------------------------------------------------------
1 | .history {
2 | .no-transactions {
3 | color: white;
4 | margin-top:15px;
5 | text-align: center;
6 | font-size: 1.2em;
7 | }
8 | h2 span {
9 | color: lightgrey;
10 | }
11 | }
12 |
13 | .icon-unconfirmed {
14 | color: yellow;
15 | }
16 | .icon-confirmed {
17 | color: green;
18 | }
19 |
--------------------------------------------------------------------------------
/src/pages/offline/offline.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |

4 |
5 |
6 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/src/pages/offline/offline.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { IonicPageModule } from 'ionic-angular';
3 | import { LogoModule } from '../../components/logo/logo.module';
4 | import { TranslateModule } from '@ngx-translate/core';
5 | import { OfflinePage } from './offline';
6 |
7 | @NgModule({
8 | declarations: [
9 | OfflinePage,
10 | ],
11 | imports: [
12 | IonicPageModule.forChild(OfflinePage),
13 | LogoModule ,
14 | TranslateModule
15 | ],
16 | exports: [
17 | OfflinePage
18 | ]
19 | })
20 | export class OfflinePageModule {}
--------------------------------------------------------------------------------
/src/pages/offline/offline.scss:
--------------------------------------------------------------------------------
1 |
2 | .no-internet {
3 |
4 | div {
5 | text-align:center;
6 | }
7 |
8 | img {
9 | display:inline-block;
10 | width:50vw;
11 | margin-top:10vh;
12 | }
13 |
14 | p {
15 | color: white;
16 | font-size: $font-size-small;
17 | text-transform: uppercase;
18 | }
19 | }
20 |
21 |
--------------------------------------------------------------------------------
/src/pages/offline/offline.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 | import { MenuController, IonicPage } from 'ionic-angular';
3 | import { SplashScreen } from '@ionic-native/splash-screen';
4 |
5 | declare var document:any;
6 | declare var navigator:any;
7 |
8 | @IonicPage({
9 | name : 'offline'
10 | })
11 | @Component({
12 | templateUrl: 'offline.html',
13 | })
14 | export class OfflinePage {
15 |
16 | constructor(
17 | protected splashscreen: SplashScreen,
18 | protected menuController:MenuController) {
19 | }
20 |
21 | ionViewWillEnter() {
22 | this.menuController.enable(false);
23 | }
24 |
25 | ionViewWillLeave() {
26 | this.menuController.enable(true);
27 | }
28 |
29 | retry() {
30 | this.splashscreen.show();
31 | window.location.reload();
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/pages/payment-result/payment-result.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | {{ 'TITLE.PAYMENT_RESULT' | translate }}
4 |
5 |
6 |
7 |
8 |
9 |
10 |
{{ referenceCurrency }}
11 |
12 |
13 | {{ referenceAmount }}
14 |
15 |
16 |
{{ currency }}
17 |
18 |
19 | {{ amount }}
20 |
21 |
22 |
23 |
24 |
27 |
28 |
29 |
30 |
31 |
32 |
{{ resultText }}
33 |
34 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/src/pages/payment-result/payment-result.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { IonicPageModule } from 'ionic-angular';
3 | import { LogoModule } from '../../components/logo/logo.module';
4 | import { DynamicFontSizeModule } from '../../components/dynamic-font-size/dynamic-font-size.module';
5 | import { PaymentResultPage } from './payment-result';
6 | import { TranslateModule } from '@ngx-translate/core';
7 |
8 | @NgModule({
9 | declarations: [
10 | PaymentResultPage
11 | ],
12 | imports: [
13 | IonicPageModule.forChild(PaymentResultPage),
14 | TranslateModule ,
15 | DynamicFontSizeModule ,
16 | LogoModule
17 | ],
18 | exports: [
19 | PaymentResultPage
20 | ]
21 | })
22 | export class PaymentResultPageModule {}
--------------------------------------------------------------------------------
/src/pages/payment-result/payment-result.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 | import { NavParams, NavController, IonicPage } from 'ionic-angular';
3 | import { CryptocurrencyService, CurrencyService, Config, AccountService, BitcoinUnit, AccountSyncService } from '../../providers/index';
4 | import { TranslateService } from '@ngx-translate/core';
5 | import { PaymentRequest, PAYMENT_STATUS_RECEIVED, PAYMENT_STATUS_OVERPAID, PAYMENT_STATUS_PARTIAL_PAID } from './../../api/payment-request';
6 | import { Account } from './../../api/account';
7 | import 'rxjs/add/operator/toPromise';
8 |
9 | @IonicPage({
10 | name : 'payment-result' ,
11 | defaultHistory : ['amount']
12 | })
13 | @Component({
14 | templateUrl : 'payment-result.html'
15 | })
16 | export class PaymentResultPage {
17 |
18 | resultSuccess : boolean = false;
19 | resultIcon : string = "";
20 | resultText : string = "";
21 | paymentRequest:PaymentRequest;
22 | account:Account = null;
23 |
24 | currency:string = "";
25 | amount:string = "";
26 | referenceCurrency = "";
27 | referenceAmount = "";
28 |
29 | waiting:boolean = true;
30 |
31 | getResultClasses() {
32 | if (this.resultSuccess) {
33 | return { "transaction-success" : true , "transaction-failed" : false };
34 | } else {
35 | return { "transaction-success" : false , "transaction-failed" : true };
36 | }
37 | }
38 |
39 | ionViewWillLeave() {
40 | this.waiting = false;
41 | }
42 |
43 | ionViewWillEnter() {
44 | this.paymentRequest = this.params.data.paymentRequest;
45 |
46 | if (this.paymentRequest.status == PAYMENT_STATUS_RECEIVED) {
47 | this.resultSuccess = true;
48 | this.resultIcon = "checkmark-circle";
49 | } else if (this.paymentRequest.status == PAYMENT_STATUS_OVERPAID ||
50 | this.paymentRequest.status == PAYMENT_STATUS_PARTIAL_PAID) {
51 | // change referenceAmount in accordance
52 | this.paymentRequest.referenceAmount = this.paymentRequest.referenceAmount * (this.paymentRequest.txAmount / this.paymentRequest.amount);
53 | this.resultSuccess = true;
54 | this.resultIcon = "alert";
55 | } else {
56 | this.resultIcon = "close-circle";
57 | }
58 |
59 | Promise.all([
60 | this.config.get(Config.CONFIG_KEY_BITCOIN_UNIT) ,
61 | this.translate.get('FORMAT.CURRENCY_S').toPromise() ,
62 | this.translate.get('PAYMENT_STATUS.' + this.paymentRequest.status).toPromise() ,
63 | this.accountService.getDefaultAccount()
64 | ]).then(promised => {
65 | this.account = promised[3];
66 | this.currency = promised[0];
67 | this.referenceCurrency = this.paymentRequest.referenceCurrency;
68 | this.amount = this.currencyService.formatNumber(BitcoinUnit.from(this.paymentRequest.txAmount,'BTC').to(this.currency), promised[1], BitcoinUnit.decimalsCount(this.currency));
69 | this.referenceAmount = this.currencyService.formatNumber(this.paymentRequest.referenceAmount, promised[1]);
70 | this.resultText = promised[2];
71 |
72 | if (this.resultSuccess) {
73 | this.accountSyncService.storeTransaction({
74 | _id : this.paymentRequest.txid ,
75 | amount : this.paymentRequest.txAmount ,
76 | currency : this.cryptocurrencyService.parseInput(this.account.data).currency ,
77 | address : this.paymentRequest.address ,
78 | incomming : true ,
79 | confirmations : 0 ,
80 | paymentReferenceAmount : this.paymentRequest.referenceAmount ,
81 | paymentReferenceCurrency : this.paymentRequest.referenceCurrency ,
82 | paymentStatus : this.paymentRequest.status
83 | }, this.account._id).then(() => {
84 | // TODO: is a sync required at this moment?
85 | // index needs to be updated
86 | this.accountSyncService.syncAccount(this.account);
87 | });
88 | }
89 | });
90 | }
91 |
92 | showHistory() {
93 | this.nav.setRoot('history', { accountId : this.account._id });
94 | }
95 |
96 | constructor(
97 | protected cryptocurrencyService: CryptocurrencyService ,
98 | protected accountSyncService: AccountSyncService,
99 | protected accountService: AccountService,
100 | protected translate: TranslateService,
101 | protected currencyService: CurrencyService,
102 | protected config: Config,
103 | protected params: NavParams,
104 | protected nav: NavController) {}
105 |
106 | }
--------------------------------------------------------------------------------
/src/pages/payment/payment.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | {{ 'TITLE.PAYMENT_REQUEST' | translate }}
4 |
5 |
6 |
7 |
8 |
9 |
10 |
{{ currency }}
11 |
12 |
13 | {{ fiatAmount }}
14 |
15 |
16 |
{{ bitcoinUnit }}
17 |
18 |
19 | {{ bitcoinAmount }}
20 |
21 |
22 |
23 |
24 |
27 |
28 |
31 |
32 |
33 |
{{ address }}
34 | {{ 'TEXT.WAITING_FOR_PAYMENT' | translate }}
35 |
36 |
37 |
--------------------------------------------------------------------------------
/src/pages/payment/payment.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { IonicPageModule } from 'ionic-angular';
3 | import { LogoModule } from '../../components/logo/logo.module';
4 | import { DynamicFontSizeModule } from '../../components/dynamic-font-size/dynamic-font-size.module';
5 | import { PaymentPage } from './payment';
6 | import { TranslateModule } from '@ngx-translate/core';
7 |
8 | @NgModule({
9 | declarations: [
10 | PaymentPage,
11 | ],
12 | imports: [
13 | IonicPageModule.forChild(PaymentPage),
14 | TranslateModule ,
15 | DynamicFontSizeModule ,
16 | LogoModule
17 | ],
18 | exports: [
19 | PaymentPage
20 | ]
21 | })
22 | export class PaymentPageModule {}
--------------------------------------------------------------------------------
/src/pages/pincode/pincode.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | {{ title }}
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
15 |
16 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/src/pages/pincode/pincode.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { IonicPageModule } from 'ionic-angular';
3 | import { LogoModule } from './../../components/logo/logo.module';
4 | import { TranslateModule } from '@ngx-translate/core';
5 | import { PincodePage } from './pincode';
6 |
7 | @NgModule({
8 | declarations: [
9 | PincodePage
10 | ],
11 | imports: [
12 | IonicPageModule.forChild(PincodePage),
13 | LogoModule ,
14 | TranslateModule
15 | ],
16 | exports: [
17 | PincodePage
18 | ]
19 | })
20 | export class PincodePageModule {}
--------------------------------------------------------------------------------
/src/pages/pincode/pincode.scss:
--------------------------------------------------------------------------------
1 | .pin {
2 | padding:4vh 0 3vh 0;
3 | text-align:center;
4 |
5 | .pin-position {
6 | margin:3vw;
7 | display:inline-block;
8 | width:10vw;
9 | height:10vw;
10 | border:1px solid white;
11 | border-radius: 100%;
12 | }
13 |
14 | .filled {
15 | background: $basic-color-transparency;
16 | }
17 |
18 | }
--------------------------------------------------------------------------------
/src/pages/pincode/pincode.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 | import { ToastController, ViewController, NavParams, MenuController, IonicPage } from 'ionic-angular';
3 | import * as bitcoin from 'bitcoinjs-lib';
4 | import { TranslateService } from '@ngx-translate/core';
5 |
6 | @IonicPage({
7 | name : 'pincode'
8 | })
9 | @Component({
10 | templateUrl : 'pincode.html'
11 | })
12 | export class PincodePage {
13 |
14 | pin:Array = ["","","",""];
15 | position:number = 0;
16 | length:number = 4;
17 | title:string = "";
18 | token:string = "";
19 | closeable: boolean = false;
20 | confirm: boolean = false;
21 | texts:Array = [];
22 | textIdentifiers = ['TITLE.VERIFY_PIN','TITLE.ENTER_PIN','TITLE.CONFIRM_PIN','TEXT.PIN_MISMATCH'];
23 |
24 | constructor(private toastController: ToastController, private viewController: ViewController, private navParams: NavParams, private menuController: MenuController, private translate: TranslateService) {
25 | translate.get(this.textIdentifiers)
26 | .subscribe((res:Array) => {
27 | this.texts = res;
28 | this.title = this.texts[this.textIdentifiers[0]];
29 |
30 | if (navParams.get('closable') === true) {
31 | this.closeable = true;
32 | }
33 | if (navParams.get('token')) {
34 | this.token = navParams.get('token');
35 | }
36 | if (navParams.get('change') === true) {
37 | this.confirm = true;
38 | this.title = this.texts[this.textIdentifiers[1]];
39 | }
40 | });
41 |
42 | }
43 |
44 | ionViewWillEnter() {
45 | this.position = 0;
46 | this.menuController.enable(false);
47 | }
48 |
49 | ionViewWillLeave() {
50 | this.menuController.enable(true);
51 | }
52 |
53 | close(success:boolean = false) {
54 | this.viewController.dismiss({
55 | success : success ,
56 | pin : this.hashedPin()
57 | });
58 | }
59 |
60 | numberInput(input:string) {
61 | if (this.position >= 0 && this.position < this.length) {
62 | this.pin[this.position] = input;
63 | this.position++;
64 | }
65 |
66 | let fullLength = this.position >= this.length;
67 |
68 | if (fullLength && this.confirm) {
69 | this.token = this.hashedPin();
70 | this.title = this.texts[this.textIdentifiers[2]];
71 | this.confirm = false;
72 | this.reset();
73 | } else if (fullLength && this.verify(this.token)) {
74 | this.close(true);
75 | } else if (fullLength) {
76 | this.toastController.create({
77 | message : this.texts[this.textIdentifiers[3]] ,
78 | duration : 3000
79 | }).present();
80 | this.reset();
81 | }
82 | }
83 |
84 | backspaceInput() {
85 | if (this.position > 0) {
86 | this.pin[this.position-1] = "";
87 | this.position--;
88 | }
89 | }
90 |
91 | reset() {
92 | this.position = 0;
93 | this.pin = ["","","",""];
94 | }
95 |
96 | hashedPin() : string {
97 | return bitcoin.crypto.sha256(new Buffer(this.pin.join(''))).toString('hex');
98 | }
99 |
100 | verify(token:string) : boolean {
101 | return this.hashedPin() === token;
102 | }
103 |
104 | }
--------------------------------------------------------------------------------
/src/pages/qrscanner/qrscanner.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | {{ 'TITLE.SCANNING' | translate }}
7 |
8 |
9 |
10 |
11 |
12 |
13 |
16 |
19 |
20 |
--------------------------------------------------------------------------------
/src/pages/qrscanner/qrscanner.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { IonicPageModule } from 'ionic-angular';
3 | import { LogoModule } from '../../components/logo/logo.module';
4 | import { TranslateModule } from '@ngx-translate/core';
5 | import { QRScannerPage } from './qrscanner';
6 |
7 | @NgModule({
8 | declarations: [
9 | QRScannerPage
10 | ],
11 | imports: [
12 | IonicPageModule.forChild(QRScannerPage),
13 | LogoModule ,
14 | TranslateModule
15 | ],
16 | exports: [
17 | QRScannerPage
18 | ]
19 | })
20 | export class QRScannerPageModule {}
--------------------------------------------------------------------------------
/src/pages/qrscanner/qrscanner.scss:
--------------------------------------------------------------------------------
1 | .qrscanner {
2 | background:none;
3 |
4 | &-area {
5 | width:100%;
6 | height:85%;
7 | background: url(../assets/img/scanner.svg) no-repeat center center;
8 | background-size: contain;
9 | }
10 | }
--------------------------------------------------------------------------------
/src/pages/qrscanner/qrscanner.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 | import { ToastController, Toast, ViewController, NavParams, IonicPage } from 'ionic-angular';
3 |
4 | @IonicPage({
5 | name : 'qrscanner'
6 | })
7 | @Component({
8 | templateUrl : 'qrscanner.html'
9 | })
10 | export class QRScannerPage {
11 |
12 | protected light: boolean = false;
13 | protected frontCamera: boolean = false;
14 | protected scanningError: Toast;
15 | protected validate:any;
16 |
17 | constructor(private toastController: ToastController, private viewController: ViewController, navParams: NavParams) {
18 | this.scanningError = toastController.create({
19 | message : 'There was a scanning error, please try again!' ,
20 | duration: 2000
21 | });
22 |
23 | this.validate = navParams.get('validate');
24 | }
25 |
26 | close(data:any = null) {
27 | this.viewController.dismiss(data);
28 | }
29 |
30 | toggleCamera() {
31 | this.frontCamera = !this.frontCamera;
32 |
33 | if (this.frontCamera) {
34 | window['QRScanner'].useFrontCamera();
35 | } else {
36 | window['QRScanner'].useBackCamera();
37 | }
38 | }
39 |
40 | toggleLight() {
41 | this.light = !this.light;
42 |
43 | if (this.light) {
44 | window['QRScanner'].enableLight();
45 | } else {
46 | window['QRScanner'].disableLight();
47 | }
48 | }
49 |
50 | setupScanner() {
51 | window['QRScanner'].scan((error, text) => {
52 | if (error || !this.validate(text)) {
53 | this.scanningError.present();
54 | this.close();
55 | } else {
56 | this.close({
57 | 'text' : this.validate(text)
58 | });
59 | }
60 | });
61 | }
62 |
63 | ionViewWillEnter() {
64 | this.setupScanner();
65 | window['QRScanner'].show();
66 | window.document.querySelector('ion-app > .app-root').classList.add('hide');
67 | }
68 |
69 | ionViewWillLeave() {
70 | window.document.querySelector('ion-app > .app-root').classList.remove('hide');
71 | window['QRScanner'].destroy();
72 | }
73 |
74 | ionViewCanEnter() {
75 | if (window['QRScanner']) {
76 | return true;
77 | } else {
78 | return false;
79 | }
80 | }
81 |
82 | }
--------------------------------------------------------------------------------
/src/pages/settings/currency/currency.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | {{ 'TITLE.CURRENCY' | translate }}
4 |
5 |
6 |
7 |
8 |
9 | {{ 'LIST_HEADING.EXCHANGE_RATE_FEE' | translate }}
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | {{ 'LIST_TEXT.EXCHANGE_RATE_FEE' | translate }}
18 | {{ (rateFee / 100) | bitpocketCurrency:currencySeparator }} %
19 |
20 | {{ 'LIST_HEADING.AVAILABLE_CURRENCIES' | translate }}
21 |
22 | {{ currency.code }}
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/src/pages/settings/currency/currency.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { IonicPageModule } from 'ionic-angular';
3 | import { LogoModule } from './../../../components/logo/logo.module';
4 | import { BitpocketCurrencyPipeModule } from '../../../pipes/bitpocket-currency/bitpocket-currency.module';
5 | import { TranslateModule } from '@ngx-translate/core';
6 | import { CurrencyPage } from './currency';
7 |
8 | @NgModule({
9 | declarations: [
10 | CurrencyPage
11 | ],
12 | imports: [
13 | IonicPageModule.forChild(CurrencyPage),
14 | LogoModule ,
15 | BitpocketCurrencyPipeModule ,
16 | TranslateModule
17 | ],
18 | exports: [
19 | CurrencyPage
20 | ]
21 | })
22 | export class CurrencyPageModule {}
--------------------------------------------------------------------------------
/src/pages/settings/currency/currency.scss:
--------------------------------------------------------------------------------
1 | .text-small {
2 | font-size:12px;
3 | }
--------------------------------------------------------------------------------
/src/pages/settings/currency/currency.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 | import { Loading, NavController, LoadingController, IonicPage } from 'ionic-angular';
3 | import { CurrencyService } from '../../../providers/index';
4 | import { TranslateService } from '@ngx-translate/core'
5 | import 'rxjs/add/operator/toPromise';
6 |
7 | @IonicPage({
8 | name : 'currency' ,
9 | defaultHistory : ['settings']
10 | })
11 | @Component({
12 | templateUrl : 'currency.html'
13 | })
14 | export class CurrencyPage {
15 |
16 | currencies:Array<{code:string}>;
17 | selectedCurrency:string;
18 | availableCurrencies:Array;
19 | loader: Loading;
20 | rateFee: number = 0;
21 | currencySeparator: string = "";
22 |
23 | startLoading() {
24 | this.translateService.get("TEXT.LOADING_CURRENCIES").toPromise()
25 | .then((text) => {
26 | this.loader = this.loadingController.create({
27 | content : text
28 | });
29 | this.loader.present();
30 | });
31 | }
32 |
33 | stopLoading() {
34 | this.loader.dismiss();
35 | }
36 |
37 | constructor(
38 | protected translateService:TranslateService,
39 | protected currencyService:CurrencyService,
40 | protected nav:NavController,
41 | protected loadingController:LoadingController) {
42 | }
43 |
44 | increaseFee() {
45 | this.rateFee += 5;
46 | }
47 |
48 | decreaseFee() {
49 | this.rateFee -= 5;
50 | }
51 |
52 | ionViewWillEnter() {
53 | this.startLoading();
54 |
55 | Promise.all([
56 | this.currencyService.getSelectedCurrency() ,
57 | this.currencyService.getAvailableCurrencies() ,
58 | this.translateService.get('FORMAT.CURRENCY_S').toPromise() ,
59 | this.currencyService.getFeePercentage()
60 | ]).then(selections => {
61 | this.selectedCurrency = selections[0];
62 | this.availableCurrencies = selections[1];
63 | this.currencySeparator = selections[2];
64 | this.rateFee = selections[3] * 100;
65 | this.stopLoading();
66 | });
67 | }
68 |
69 | ionViewWillLeave() {
70 | this.currencyService.setFeePercentage(this.rateFee / 100);
71 | this.currencyService.setSelectedCurrency(this.selectedCurrency);
72 | }
73 |
74 | }
75 |
--------------------------------------------------------------------------------
/src/pages/settings/general/general.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | {{ 'TITLE.GENERAL' | translate }}
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 | {{ 'LIST_HEADING.MERCHANT_INFORMATION' | translate }}
12 |
13 | {{ 'LIST_TEXT.MERCHANT_NAME' | translate }}
14 |
15 |
16 |
17 |
18 |
19 | {{ 'LIST_HEADING.BITCOIN_UNIT' | translate }}
20 |
21 | {{ 'LIST_TEXT.BITCOIN' | translate }}
22 |
23 |
24 |
25 | {{ 'LIST_TEXT.MILLI_BITCOIN' | translate }}
26 |
27 |
28 |
29 | {{ 'LIST_TEXT.BITS' | translate }}
30 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/src/pages/settings/general/general.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { IonicPageModule } from 'ionic-angular';
3 | import { LogoModule } from './../../../components/logo/logo.module';
4 | import { TranslateModule } from '@ngx-translate/core';
5 | import { GeneralPage } from './general';
6 |
7 | @NgModule({
8 | declarations: [
9 | GeneralPage
10 | ],
11 | imports: [
12 | IonicPageModule.forChild(GeneralPage),
13 | LogoModule ,
14 | TranslateModule
15 | ],
16 | exports: [
17 | GeneralPage
18 | ]
19 | })
20 | export class GeneralPageModule {}
--------------------------------------------------------------------------------
/src/pages/settings/general/general.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 | import { IonicPage } from 'ionic-angular';
3 | import { Config } from '../../../providers/index';
4 |
5 | @IonicPage({
6 | name : 'general' ,
7 | defaultHistory : ['settings']
8 | })
9 | @Component({
10 | templateUrl : 'general.html'
11 | })
12 | export class GeneralPage {
13 |
14 | selectedUnit: string = 'mBTC';
15 | selectedExplorer: string = 'blockchaininfo';
16 | paymentRequestLabel: string = "";
17 |
18 | constructor(private config: Config) {
19 | }
20 |
21 | ionViewWillEnter() {
22 | Promise.all([
23 | this.config.get(Config.CONFIG_KEY_BITCOIN_UNIT) ,
24 | this.config.get(Config.CONFIG_KEY_PAYMENT_REQUEST_LABEL)
25 | ]).then(promised => {
26 | this.selectedUnit = promised[0];
27 | this.paymentRequestLabel = promised[1];
28 | });
29 | }
30 |
31 | ionViewWillLeave() {
32 | this.config.set(Config.CONFIG_KEY_BITCOIN_UNIT, this.selectedUnit);
33 | this.config.set(Config.CONFIG_KEY_PAYMENT_REQUEST_LABEL, this.paymentRequestLabel);
34 | }
35 |
36 | }
--------------------------------------------------------------------------------
/src/pages/settings/settings.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {{ 'TITLE.SETTINGS' | translate }}
5 |
6 |
7 |
8 |
9 |
10 |
11 |
15 |
19 |
20 |
21 | {{ 'LIST_HEADING.PIN' | translate }}
22 | {{ 'LIST_TEXT.PIN' | translate }}
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/src/pages/settings/settings.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { IonicPageModule } from 'ionic-angular';
3 | import { LogoModule } from '../../components/logo/logo.module';
4 | import { TranslateModule } from '@ngx-translate/core';
5 | import { SettingsPage } from './settings';
6 |
7 | @NgModule({
8 | declarations: [
9 | SettingsPage
10 | ],
11 | imports: [
12 | IonicPageModule.forChild(SettingsPage),
13 | LogoModule,
14 | TranslateModule
15 | ],
16 | exports: [
17 | SettingsPage
18 | ]
19 | })
20 | export class SettingsPageModule {}
--------------------------------------------------------------------------------
/src/pages/settings/settings.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 | import { NavController, ModalController, Modal, IonicPage } from 'ionic-angular';
3 | import { Config } from '../../providers/index';
4 |
5 | @IonicPage({
6 | name : 'settings'
7 | })
8 | @Component({
9 | templateUrl : 'settings.html' ,
10 | })
11 | export class SettingsPage {
12 |
13 | pages = ['general', 'currency'];
14 |
15 | constructor(
16 | private config:Config,
17 | private modalController:
18 | ModalController,
19 | private navigation:NavController) {
20 | }
21 |
22 | ionViewCanEnter() : Promise {
23 | return new Promise((resolve, reject) => {
24 |
25 | // if the SettingsPage was already navigated, don't ask again for PIN
26 | for (let i = 0; i < this.navigation.length(); i++) {
27 | if (SettingsPage === this.navigation.getByIndex(i).component) {
28 | resolve();
29 | return;
30 | }
31 | }
32 |
33 | this.config.get(Config.CONFIG_KEY_PIN).then(value => {
34 | if (value === '') {
35 | resolve();
36 | } else {
37 | let modal:Modal = this.modalController.create('pincode', { token : value, closable : true });
38 |
39 | modal.present();
40 | modal.onDidDismiss(data => {
41 | if (data && data.success) {
42 | resolve();
43 | } else {
44 | reject();
45 | }
46 | })
47 | }
48 | }).catch(e => {console.error(e);});
49 | });
50 | }
51 |
52 | changePin() {
53 | let modal:Modal = this.modalController.create('pincode', { change : true });
54 | modal.present();
55 |
56 | modal.onDidDismiss(data => {
57 | if (data && data.success) {
58 | this.config.set(Config.CONFIG_KEY_PIN, data.pin);
59 | }
60 | });
61 |
62 | }
63 |
64 | openPage(index:number) {
65 | this.navigation.push(this.pages[index]);
66 | }
67 |
68 | }
69 |
--------------------------------------------------------------------------------
/src/pages/transaction/transaction.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | {{ 'TITLE.TRANSACTION' | translate }}
7 |
8 |
9 |
10 |
11 | 0">
12 |
13 | {{ 'LIST_HEADING.PAYMENT' | translate }}
14 |
15 | {{ transaction.paymentReferenceCurrency }} {{ transaction.paymentReferenceAmount | bitpocketCurrency:currencySeparator:currencyThousandsPoint:2 }}
16 | {{ transaction.paymentStatus | translate }}
17 |
18 |
19 | {{ 'LIST_HEADING.TRANSACTION' | translate }}
20 |
21 | {{ currencySymbol }} {{ transaction.amount | bitpocketUnit:'BTC':currencySymbol | bitpocketCurrency:currencySeparator:currencyThousandsPoint:currencyPrecision }}
22 | {{ referenceCurrencySymbol }} {{ transaction.amount | bitpocketFiat:'BTC':referenceCurrencyRate | bitpocketCurrency:currencySeparator:currencyThousandsPoint }}
23 | {{ (transaction.timestamp * 1000) | date:dateTimeFormat }}
24 |
25 |
26 | {{ 'LIST_TEXT.UNCONFIRMED' | translate }}
27 |
28 | = 6">
29 | {{ 'LIST_TEXT.CONFIRMED' | translate }}
30 |
31 |
32 |
33 |
34 | {{ 'LIST_TEXT.INCOMMING' | translate }}
35 |
36 |
37 | {{ 'LIST_TEXT.OUTOING' | translate }}
38 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/src/pages/transaction/transaction.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { IonicPageModule } from 'ionic-angular';
3 | import { BitpocketCurrencyPipeModule } from '../../pipes/bitpocket-currency/bitpocket-currency.module';
4 | import { BitpocketUnitPipeModule } from '../../pipes/bitpocket-unit/bitpocket-unit.module';
5 | import { BitpocketFiatPipeModule } from '../../pipes/bitpocket-fiat/bitpocket-fiat.module';
6 | import { TranslateModule } from '@ngx-translate/core';
7 | import { TransactionPage } from './transaction';
8 |
9 | @NgModule({
10 | declarations: [
11 | TransactionPage,
12 | ],
13 | imports: [
14 | IonicPageModule.forChild(TransactionPage),
15 | TranslateModule ,
16 | BitpocketCurrencyPipeModule ,
17 | BitpocketUnitPipeModule ,
18 | BitpocketFiatPipeModule
19 | ],
20 | })
21 | export class TransactionPageModule {}
22 |
--------------------------------------------------------------------------------
/src/pages/transaction/transaction.scss:
--------------------------------------------------------------------------------
1 | page-transaction {
2 |
3 | }
4 |
--------------------------------------------------------------------------------
/src/pages/transaction/transaction.ts:
--------------------------------------------------------------------------------
1 | import { TransactionStorageService, Config, CurrencyService, BitcoinUnit } from './../../providers/index';
2 | import { Transaction } from './../../api/transaction';
3 | import { Component } from '@angular/core';
4 | import { IonicPage, NavController, NavParams, ViewController } from 'ionic-angular';
5 | import { TranslateService } from '@ngx-translate/core'
6 | import 'rxjs/add/operator/toPromise';
7 |
8 | @IonicPage({
9 | name : 'transaction' ,
10 | segment : 'transaction/:txid'
11 | })
12 | @Component({
13 | templateUrl: 'transaction.html',
14 | })
15 | export class TransactionPage {
16 |
17 | txid:string;
18 | transaction:any = {};
19 |
20 | currencyThousandsPoint: string = "";
21 | currencySeparator: string = "";
22 | currencyPrecision: number = 2;
23 | currencySymbol:string = "BTC";
24 | dateTimeFormat: any;
25 | referenceCurrencySymbol:string = "";
26 | referenceCurrencyRate:number = 0;
27 |
28 | constructor(
29 | protected currencyService: CurrencyService,
30 | protected config: Config,
31 | protected translation: TranslateService ,
32 | protected transactionStorageService: TransactionStorageService ,
33 | protected viewController: ViewController ,
34 | protected navParams: NavParams) {
35 | this.txid = navParams.get('txid');
36 | }
37 |
38 | ionViewDidEnter() {
39 | Promise.all([
40 | this.translation.get('FORMAT.CURRENCY_T').toPromise() ,
41 | this.translation.get('FORMAT.CURRENCY_S').toPromise() ,
42 | this.translation.get('FORMAT.DATETIME').toPromise() ,
43 | this.translation.get('TEXT.LOADING_TRANSACTIONS').toPromise() ,
44 | this.config.get(Config.CONFIG_KEY_BITCOIN_UNIT) ,
45 | this.currencyService.getSelectedCurrency() ,
46 | this.currencyService.getSelectedCurrencyRate() ,
47 | this.transactionStorageService.retrieveTransaction(this.txid)
48 | ]).then(promised => {
49 | this.currencyThousandsPoint = promised[0];
50 | this.currencySeparator = promised[1];
51 | this.dateTimeFormat = promised[2];
52 | this.currencySymbol = promised[4];
53 | this.currencyPrecision = BitcoinUnit.decimalsCount(promised[4]);
54 | this.referenceCurrencySymbol = promised[5];
55 | this.referenceCurrencyRate = promised[6];
56 | this.transaction = promised[7];
57 | });
58 | }
59 |
60 | close() {
61 | this.viewController.dismiss();
62 | }
63 |
64 | }
65 |
--------------------------------------------------------------------------------
/src/pipes/bitpocket-currency/bitpocket-currency.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { BitpocketCurrencyPipe } from './bitpocket-currency';
3 |
4 | @NgModule({
5 | declarations: [BitpocketCurrencyPipe],
6 | exports: [BitpocketCurrencyPipe]
7 | })
8 | export class BitpocketCurrencyPipeModule {}
--------------------------------------------------------------------------------
/src/pipes/bitpocket-currency/bitpocket-currency.ts:
--------------------------------------------------------------------------------
1 | import { Pipe, PipeTransform } from '@angular/core';
2 |
3 | @Pipe({
4 | name: 'bitpocketCurrency'
5 | })
6 | export class BitpocketCurrencyPipe implements PipeTransform {
7 | transform(value, separator=".", thousandsDelimiter=",", precision=2, symbol="") : string {
8 |
9 | symbol = symbol === "" ? "" : (symbol + " ");
10 |
11 | let n:any = value,
12 | c = isNaN(precision = Math.abs(precision)) ? 2 : precision,
13 | d = separator == undefined ? "." : separator,
14 | t = thousandsDelimiter == undefined ? "," : thousandsDelimiter,
15 | s = value < 0 ? "-" : "",
16 | i:any = parseInt(n = Math.abs(+n || 0).toFixed(c)) + "",
17 | j = (i.length) > 3 ? i.length % 3 : 0;
18 |
19 | return symbol + s + (j ? i.substr(0, j) + t : "") + i.substr(j).replace(/(\d{3})(?=\d)/g, "$1" + t) + (c ? d + Math.abs(n - i).toFixed(c).slice(2) : "");
20 |
21 | }
22 | }
--------------------------------------------------------------------------------
/src/pipes/bitpocket-fiat/bitpocket-fiat.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { BitpocketFiatPipe } from './bitpocket-fiat';
3 |
4 | @NgModule({
5 | declarations: [BitpocketFiatPipe],
6 | exports: [BitpocketFiatPipe]
7 | })
8 | export class BitpocketFiatPipeModule {}
--------------------------------------------------------------------------------
/src/pipes/bitpocket-fiat/bitpocket-fiat.ts:
--------------------------------------------------------------------------------
1 | import { BitcoinUnit } from './../../providers/index';
2 | import { Pipe, PipeTransform } from '@angular/core';
3 |
4 | @Pipe({
5 | name: 'bitpocketFiat'
6 | })
7 | export class BitpocketFiatPipe implements PipeTransform {
8 | transform(value:any, from="BTC", rate:number=0) : number {
9 | return BitcoinUnit.from(parseFloat(value), from).toFiat(rate);
10 | }
11 | }
--------------------------------------------------------------------------------
/src/pipes/bitpocket-unit/bitpocket-unit.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { BitpocketUnitPipe } from './bitpocket-unit';
3 |
4 | @NgModule({
5 | declarations: [BitpocketUnitPipe],
6 | exports: [BitpocketUnitPipe]
7 | })
8 | export class BitpocketUnitPipeModule {}
--------------------------------------------------------------------------------
/src/pipes/bitpocket-unit/bitpocket-unit.ts:
--------------------------------------------------------------------------------
1 | import { BitcoinUnit } from './../../providers/index';
2 | import { Pipe, PipeTransform } from '@angular/core';
3 |
4 | @Pipe({
5 | name: 'bitpocketUnit'
6 | })
7 | export class BitpocketUnitPipe implements PipeTransform {
8 | transform(value:any, from="BTC", to="BTC") : number {
9 | return BitcoinUnit.from(parseFloat(value), from).to(to);
10 | }
11 | }
--------------------------------------------------------------------------------
/src/providers/config.ts:
--------------------------------------------------------------------------------
1 | import {Injectable} from '@angular/core';
2 | import {Storage} from '@ionic/storage';
3 |
4 | @Injectable()
5 | export class Config {
6 |
7 | constructor(
8 | protected storage: Storage
9 | ) {}
10 |
11 | static CONFIG_KEY_PAYMENT_REQUEST_LABEL = 'payment-request-label';
12 | static CONFIG_KEY_CURRENCY = 'currency';
13 | static CONFIG_KEY_BITCOIN_UNIT = 'bitcoin-unit';
14 | static CONFIG_KEY_EXCHANGE_RATE = 'rate';
15 | static CONFIG_KEY_PIN = 'pin';
16 | static CONFIG_KEY_DEFAULT_ACCOUNT = 'default-account';
17 | static CONFIG_KEY_CURRENCY_CACHE = 'currency-cache';
18 | static CONFIG_KEY_FEE_PERCENTAGE = 'fee-percentage';
19 |
20 | initConfig() : Promise {
21 | return new Promise((resolve,reject) => {
22 | Promise.all([
23 | this.initialize(Config.CONFIG_KEY_EXCHANGE_RATE,-1),
24 | this.initialize(Config.CONFIG_KEY_CURRENCY,'EUR'),
25 | this.initialize(Config.CONFIG_KEY_BITCOIN_UNIT,'mBTC'),
26 | this.initialize(Config.CONFIG_KEY_PIN,''),
27 | this.initialize(Config.CONFIG_KEY_FEE_PERCENTAGE,0),
28 | this.initialize(Config.CONFIG_KEY_CURRENCY_CACHE, [{
29 | code : 'EUR', rate : 0
30 | }, {
31 | code : 'USD' , rate : 0
32 | }])
33 | ]).then(() => {
34 | resolve(true);
35 | }).catch(() => {
36 | resolve(false);
37 | });
38 | });
39 | }
40 |
41 | isSet(key:string) : Promise {
42 | return new Promise((resolve,reject) => {
43 | this.storage.get(key).then(storedValue => {
44 | if (storedValue === null || storedValue === undefined) {
45 | resolve(false);
46 | }
47 | resolve(true);
48 | });
49 | });
50 | }
51 |
52 | /**
53 | * init the key with the given value, only!
54 | * if there is no value set already
55 | */
56 | initialize(key:string, value:any) : Promise {
57 | return new Promise((resolve,reject) => {
58 | this.isSet(key).then(status => {
59 | if (!status) {
60 | this.storage.set(key,value);
61 | }
62 | resolve(true);
63 | }).catch(() => {
64 | resolve(false);
65 | });
66 | });
67 | }
68 |
69 | set(key:string, value:any) : Promise {
70 | return this.storage.set(key, value);
71 | }
72 |
73 | get(key:string) : Promise {
74 | return this.storage.get(key);
75 | }
76 |
77 | }
--------------------------------------------------------------------------------
/src/providers/currency/bitcoin-unit.spec.ts:
--------------------------------------------------------------------------------
1 | import {BitcoinUnit} from './bitcoin-unit'
2 |
3 | describe('Bitcoin Unit', () => {
4 |
5 | it('from btc input with many decimals', () => {
6 | let bU = BitcoinUnit.from(12.12345678, 'BTC');
7 | expect(bU.to('satoshis')).toEqual(1212345678);
8 | });
9 |
10 | it('from satoshi input to different units', () => {
11 | // 100.000.000 satoshi is 1 BTC
12 | let bU = BitcoinUnit.from(100000000);
13 |
14 | expect(bU.to('BTC')).toEqual(1);
15 | expect(bU.to('mBTC')).toEqual(1000);
16 | expect(bU.to('bits')).toEqual(1000000);
17 | expect(bU.to('satoshis')).toEqual(100000000);
18 | });
19 |
20 | it('from fiat to mBTC', () => {
21 | // 400 Fiat is 1 BTC
22 | let bU = BitcoinUnit.fromFiat(100,400);
23 | expect(bU.to('mBTC')).toEqual(250);
24 | });
25 |
26 | it('from mBTC to Fiat', () => {
27 | // 420.50 Fiat is 1 BTC
28 | let bU = BitcoinUnit.from(350,'mBTC');
29 | expect(bU.toFiat(420.5,3)).toEqual(147.175);
30 | });
31 |
32 | });
--------------------------------------------------------------------------------
/src/providers/currency/bitcoin-unit.ts:
--------------------------------------------------------------------------------
1 | const BITCOIN_UNITS = {
2 | 'BTC' : [1e8,8] ,
3 | 'mBTC' : [1e5,5] ,
4 | 'bits' : [1e2,2] ,
5 | 'satoshis' : [1,0]
6 | };
7 |
8 | export class BitcoinUnit {
9 | // internal representation is in satoshis
10 | private satoshis: number = 0;
11 |
12 | set value(value: number) {
13 | this.satoshis = value;
14 | }
15 |
16 | constructor(value: number) {
17 | this.satoshis = value;
18 | }
19 |
20 | static decimalsCount(bitcoinUnit) : number {
21 | return BitcoinUnit.getUnitSpecification(bitcoinUnit)[1];
22 | }
23 |
24 | static getUnitSpecification(bitcoinUnit: string) : Array {
25 | if (!BITCOIN_UNITS.hasOwnProperty(bitcoinUnit)) { // fallback to satoshis...
26 | bitcoinUnit = 'satoshis';
27 | }
28 | return BITCOIN_UNITS[bitcoinUnit];
29 | }
30 |
31 | static from(value: number, bitcoinUnit: string = 'satoshis') : BitcoinUnit {
32 | let unitSpec = BitcoinUnit.getUnitSpecification(bitcoinUnit);
33 | return new BitcoinUnit( parseInt((value * unitSpec[0]).toFixed(0)) );
34 | }
35 |
36 | static fromFiat(fiatValue: number, exchangeRate: number) : BitcoinUnit {
37 | let valueInBTC:number = fiatValue / exchangeRate;
38 | return BitcoinUnit.from(valueInBTC, 'BTC');
39 | }
40 |
41 | to(bitcoinUnit: string = 'satoshis') : number {
42 | let unitSpec = BitcoinUnit.getUnitSpecification(bitcoinUnit);
43 | return parseFloat( (this.satoshis / unitSpec[0]).toFixed(unitSpec[1]) );
44 | }
45 |
46 | toFiat(exchangeRate: number, decimals: number = 2) : number {
47 | let btcValue = this.to('BTC');
48 | return parseFloat((btcValue * exchangeRate).toFixed(decimals));
49 | }
50 |
51 | toString() {
52 | return this.satoshis.toFixed(0);
53 | }
54 |
55 | }
--------------------------------------------------------------------------------
/src/providers/currency/bitcoinaverage-service.ts:
--------------------------------------------------------------------------------
1 | import { HttpClient } from '@angular/common/http';
2 | import { Injectable } from '@angular/core';
3 | import { CurrencyExchangeRate } from '../../api/currency-exchange-rate';
4 | import { CurrencyExchangeService } from '../../api/currency-exchange-service';
5 | import { Config } from '../../providers/index';
6 |
7 | @Injectable()
8 | export class BitcoinAverageExchangeService implements CurrencyExchangeService {
9 |
10 | constructor(protected http:HttpClient, protected config:Config) {
11 | }
12 |
13 | prepareOne(code, json:any) : CurrencyExchangeRate {
14 | return {
15 | code : code ,
16 | rate : json.last
17 | };
18 | }
19 |
20 | prepareAll(json:any) : Array {
21 | let output = [];
22 |
23 | for (let code in json) {
24 | if (json.hasOwnProperty(code)) {
25 | output.push(this.prepareOne(code.slice(-3),json[code]))
26 | }
27 | }
28 |
29 | return output;
30 | }
31 |
32 | getAvailableCurrencies() : Promise> {
33 | return this.getExchangeRates();
34 | }
35 |
36 | getExchangeRates() : Promise> {
37 | return new Promise((resolve, reject) => {
38 | try {
39 | this.http.get('https://apiv2.bitcoinaverage.com/indices/global/ticker/short?crypto=BTC')
40 | .subscribe(
41 | response => {
42 | let currencies = this.prepareAll(response);
43 | this.config.set(Config.CONFIG_KEY_CURRENCY_CACHE, currencies);
44 | resolve(currencies);
45 | } ,
46 | error => {
47 | resolve(this.config.get(Config.CONFIG_KEY_CURRENCY_CACHE));
48 | }
49 | );
50 | } catch(e) {
51 | console.error(e);
52 | resolve(this.config.get(Config.CONFIG_KEY_CURRENCY_CACHE));
53 | }
54 | });
55 | }
56 |
57 | getExchangeRate(code:string) : Promise {
58 | return new Promise((resolve, reject) => {
59 | try {
60 | this.http.get('https://apiv2.bitcoinaverage.com/indices/global/ticker/short?crypto=BTC&fiats=' + code)
61 | .subscribe(
62 | response => {
63 | resolve(this.prepareOne(code, response['BTC' + code]));
64 | } ,
65 | error => {
66 | reject();
67 | }
68 | );
69 | } catch(e) {
70 | console.error(e);
71 | reject();
72 | }
73 | });
74 | }
75 |
76 | }
--------------------------------------------------------------------------------
/src/providers/currency/cryptocurrency-service.spec.ts:
--------------------------------------------------------------------------------
1 | import {
2 | CryptocurrencyService,
3 | BITCOIN_ADDRESS,
4 | BITCOIN_XPUB_KEY,
5 | TESTNET_ADDRESS,
6 | TESTNET_TPUB_KEY
7 | } from './cryptocurrency-service';
8 |
9 | describe('Bitcoin', () => {
10 |
11 | let service = new CryptocurrencyService();
12 |
13 | it('should correctly parse account address input', () => {
14 | let address = "15mKKb2eos1hWa6tisdPwwDC1a5J1y9nma";
15 | let result = service.parseInput(" " + address + " ");
16 | expect(result.data).toEqual(address);
17 | expect(result.type).toEqual(BITCOIN_ADDRESS);
18 | });
19 |
20 | it('should correctly parse account uri input', () => {
21 | let address = "15mKKb2eos1hWa6tisdPwwDC1a5J1y9nma";
22 | let result = service.parseInput("bitcoin:" + address + "?amount=100.0000&text=something");
23 | expect(result.data).toEqual(address);
24 | expect(result.type).toEqual(BITCOIN_ADDRESS);
25 | });
26 |
27 | it('should correctly parse account xpub input', () => {
28 | let key = "xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8";
29 | let result = service.parseInput(" " + key + " ");
30 | expect(result.data).toEqual(key);
31 | expect(result.type).toEqual(BITCOIN_XPUB_KEY);
32 | });
33 |
34 | });
35 |
36 | describe('Testnet', () => {
37 |
38 | let service = new CryptocurrencyService();
39 |
40 | it('should correctly parse account address input', () => {
41 | let address = "mipcBbFg9gMiCh81Kj8tqqdgoZub1ZJRfn";
42 | let result = service.parseInput(" " + address + " ");
43 | expect(result.data).toEqual(address);
44 | expect(result.type).toEqual(TESTNET_ADDRESS);
45 | });
46 |
47 | it('should correctly parse account uri input', () => {
48 | let address = "mipcBbFg9gMiCh81Kj8tqqdgoZub1ZJRfn";
49 | let result = service.parseInput("bitcoin:" + address + "?amount=100.0000&text=something");
50 | expect(result.data).toEqual(address);
51 | expect(result.type).toEqual(TESTNET_ADDRESS);
52 | });
53 |
54 | it('should correctly parse account tpub input', () => {
55 | let key = "tpubD6NzVbkrYhZ4WUbpsyRwqJ8C2f5T7vXZVaiZkgsBCeNAT3gQxKAaFnqdM3jUzVJDAQsnnMBHvcwKUqt6NesoZRuW1ED8xEGoV5HDR3vad1j";
56 | let result = service.parseInput(" " + key + " ");
57 | expect(result.data).toEqual(key);
58 | expect(result.type).toEqual(TESTNET_TPUB_KEY);
59 | });
60 |
61 | it('should parse tpub input from android wallet', () => {
62 | let input = "tpubD8dWq6wHd9sXLeDZ8hiYixYvqApcRBgiYhtKVhEozbhMZBA4qgYtTXtfPWqWKdFXB2uKxptSZiR9LQ4kjNSBNmWfxPFhEByzJx3X9ry4mWw?c=1490794392&h=bip32";
63 | let key = "tpubD8dWq6wHd9sXLeDZ8hiYixYvqApcRBgiYhtKVhEozbhMZBA4qgYtTXtfPWqWKdFXB2uKxptSZiR9LQ4kjNSBNmWfxPFhEByzJx3X9ry4mWw";
64 | let result = service.parseInput(" " + input + " ");
65 | expect(result.data).toEqual(key);
66 | expect(result.type).toEqual(TESTNET_TPUB_KEY);
67 | });
68 |
69 | });
--------------------------------------------------------------------------------
/src/providers/currency/cryptocurrency-service.ts:
--------------------------------------------------------------------------------
1 | import { Transaction } from './../../api/transaction';
2 | import {Injectable} from '@angular/core';
3 | import * as regex from 'crypto-regex';
4 | import * as bitcoin from 'bitcoinjs-lib';
5 |
6 | export const BITCOIN = "bitcoin";
7 | export const TESTNET = "testnet";
8 |
9 | export const BITCOIN_ADDRESS = "bitcoin-static-address";
10 | export const BITCOIN_XPUB_KEY = "bitcoin-xpub-key";
11 | export const TESTNET_ADDRESS = "testnet-static-address";
12 | export const TESTNET_TPUB_KEY = "testnet-tpub-key";
13 |
14 | export const REGEX_BITCOIN_ADDRESS = new RegExp("^(" + regex['bitcoin-address'] + ")$");
15 | export const REGEX_BITCOIN_URI = new RegExp("^" + regex['bitcoin-uri']);
16 | export const REGEX_XPUB_KEY = new RegExp("^(" + regex['bitcoin-xpub-key'] + ")$");
17 | export const REGEX_TESTNET_ADDRESS = new RegExp("^(" + regex['testnet-address'] + ")$");
18 | export const REGEX_TESTNET_URI = new RegExp("^" + regex['testnet-uri']);
19 | export const REGEX_TPUB_KEY = new RegExp("^(" + regex['testnet-tpub-key'] + ")$");
20 |
21 | @Injectable()
22 | export class CryptocurrencyService {
23 |
24 | deriveAddress(key:string, index:number, change:boolean = false) : string {
25 | try {
26 | let data = this.parseXpubKeyInput(key);
27 | let path = this.getDerivationPath(index, change);
28 |
29 | if (data.currency == BITCOIN) {
30 | return bitcoin.HDNode.fromBase58(key).derivePath(path).getAddress();
31 | } else {
32 | return bitcoin.HDNode.fromBase58(key, [bitcoin.networks.testnet]).derivePath(path).getAddress();
33 | }
34 | } catch (e) {
35 | throw new Error('Could not derive index from key' + e);
36 | }
37 | }
38 |
39 | getDerivationPath(index:number = 0, change:boolean = false) {
40 | return (change ? 1 : 0) + '/' + index;
41 | }
42 |
43 | parseXpubKeyInput(input: string) {
44 | try {
45 | if (REGEX_XPUB_KEY.test(input)) {
46 | input = input.match(REGEX_XPUB_KEY)[2];
47 | if (bitcoin.HDNode.fromBase58(input).toBase58() === input) {
48 | return {
49 | type : BITCOIN_XPUB_KEY ,
50 | currency : BITCOIN ,
51 | data : input.match(REGEX_XPUB_KEY)[1]
52 | };
53 | }
54 | }
55 | if (REGEX_TPUB_KEY.test(input)) {
56 | input = input.match(REGEX_TPUB_KEY)[2];
57 | if ((bitcoin.HDNode.fromBase58(input, [bitcoin.networks.testnet]).toBase58() === input)) {
58 | return {
59 | type : TESTNET_TPUB_KEY ,
60 | currency : TESTNET ,
61 | data : input.match(REGEX_TPUB_KEY)[1]
62 | }
63 | }
64 | }
65 | } catch(e) {
66 | console.error("error", e);
67 | }
68 |
69 | return null;
70 | }
71 |
72 | parseAddressInput(input: string) {
73 | let addressRegexs = [REGEX_BITCOIN_ADDRESS, REGEX_BITCOIN_URI, REGEX_TESTNET_ADDRESS, REGEX_TESTNET_URI];
74 | for(let i = 0; i < addressRegexs.length; i++) {
75 | if (addressRegexs[i].test(input)) {
76 | return {
77 | type : i > 1 ? TESTNET_ADDRESS : BITCOIN_ADDRESS ,
78 | currency : i > 1 ? TESTNET : BITCOIN ,
79 | data : input.match(addressRegexs[i])[1]
80 | };
81 | }
82 | }
83 | return null;
84 | }
85 |
86 | /**
87 | * Parses account input information
88 | *
89 | * @param input account information
90 | * @return Account
91 | * @throws Error
92 | *
93 | */
94 | parseInput(input:string) {
95 | input = input.trim();
96 | let address = this.parseAddressInput(input);
97 | if (address) {
98 | return address;
99 | }
100 |
101 | let key = this.parseXpubKeyInput(input);
102 | if (key) {
103 | return key;
104 | }
105 |
106 | throw new Error('Could not parse input information');
107 | }
108 |
109 | isConfirmed(transaction:Transaction) : boolean {
110 | if (transaction.confirmations >= 6) {
111 | return true;
112 | }
113 | return false;
114 | }
115 |
116 | }
--------------------------------------------------------------------------------
/src/providers/currency/currency-service.spec.ts:
--------------------------------------------------------------------------------
1 | import {CurrencyService} from './currency-service';
2 |
3 | describe('Currency', () => {
4 |
5 | let currencyService: CurrencyService = new CurrencyService(null,null);
6 |
7 | it('format Currency', () => {
8 | expect(currencyService.formatNumber(10,',')).toEqual('10,00');
9 | expect(currencyService.formatNumber(10.2501,',',6,2)).toEqual('10,2501');
10 | expect(currencyService.formatNumber(10.250,',',3,3)).toEqual('10,250');
11 | });
12 |
13 | });
14 |
--------------------------------------------------------------------------------
/src/providers/currency/currency-service.ts:
--------------------------------------------------------------------------------
1 | import {Injectable} from '@angular/core';
2 | import {CurrencyExchangeService} from '../../api/currency-exchange-service';
3 | import {CurrencyExchangeRate} from '../../api/currency-exchange-rate';
4 | import {Config, BitcoinAverageExchangeService} from './../index';
5 |
6 | @Injectable()
7 | export class CurrencyService {
8 |
9 | constructor(protected config: Config, protected exchangeService: BitcoinAverageExchangeService) {
10 | }
11 |
12 | getFeePercentage() : Promise {
13 | return this.config.get(Config.CONFIG_KEY_FEE_PERCENTAGE);
14 | }
15 |
16 | getExchangeService() : CurrencyExchangeService {
17 | return this.exchangeService;
18 | }
19 |
20 | getSelectedCurrency() : Promise {
21 | return this.config.get(Config.CONFIG_KEY_CURRENCY);
22 | }
23 |
24 | getSelectedCurrencyRate() : Promise {
25 | return this.config.get(Config.CONFIG_KEY_EXCHANGE_RATE).then(rate => {
26 | return parseFloat(rate);
27 | });
28 | }
29 |
30 | getCalculatedCurrencyRate() : Promise {
31 | return new Promise ((resolve, reject) => {
32 | Promise.all([
33 | this.config.get(Config.CONFIG_KEY_EXCHANGE_RATE) ,
34 | this.config.get(Config.CONFIG_KEY_FEE_PERCENTAGE)
35 | ]).then(values => {
36 | resolve(parseFloat(values[0]) * ((100.0 - parseFloat(values[1]))/100.0));
37 | });
38 | });
39 | }
40 |
41 | getAvailableCurrencies() : Promise {
42 | return new Promise((resolve, reject) => {
43 | resolve(this.getExchangeService().getAvailableCurrencies());
44 | });
45 | }
46 |
47 | setSelectedCurrency(code:string) : CurrencyService {
48 | this.config.set(Config.CONFIG_KEY_CURRENCY, code).then(() => {
49 | this.updateCurrencyRate();
50 | });
51 | return this;
52 | }
53 |
54 | setFeePercentage(feePercentage:number) : CurrencyService {
55 | this.config.set(Config.CONFIG_KEY_FEE_PERCENTAGE, feePercentage);
56 | return this;
57 | }
58 |
59 | updateCurrencyRate() : Promise {
60 | return new Promise((resolve,reject) => {
61 | Promise.all([
62 | this.getExchangeService() ,
63 | this.getSelectedCurrency()
64 | ]).then(promised => {
65 | promised[0].getExchangeRate(promised[1]).then(data => {
66 | this.config.set(Config.CONFIG_KEY_EXCHANGE_RATE, data.rate);
67 |
68 | resolve({
69 | 'code' : promised[1] ,
70 | 'rate' : data.rate
71 | });
72 | }).catch(reject);
73 | }).catch(reject);
74 | });
75 | }
76 |
77 | /**
78 | * Format a number, fillup with 0 until minDecimals is reached, cut at maxDecimals
79 | */
80 | formatNumber(value: number, separator: string, maxDecimals: number = 2, minDecimals: number = 2) : string {
81 | let formattedNumber = value.toFixed(maxDecimals).replace(/\./,separator);
82 |
83 | if (minDecimals >= maxDecimals) {
84 | return formattedNumber;
85 | } else {
86 | let startLength = (formattedNumber.indexOf(separator) + minDecimals);
87 | let endIndex = formattedNumber.length - 1;
88 | while (startLength < endIndex) {
89 | if (formattedNumber[endIndex] != "0") {
90 | break;
91 | } else {
92 | formattedNumber = formattedNumber.substr(0,formattedNumber.length-1);
93 | }
94 |
95 | endIndex--;
96 | }
97 | }
98 |
99 | return formattedNumber;
100 | }
101 |
102 | }
103 |
104 |
--------------------------------------------------------------------------------
/src/providers/index.ts:
--------------------------------------------------------------------------------
1 | import { TransactionServiceWrapper } from './transaction/transaction-service-wrapper';
2 | import { Storage } from '@ionic/storage';
3 | import { HttpClient } from '@angular/common/http';
4 | import { ModalController } from 'ionic-angular';
5 | import { TransactionStorageService } from './transaction/transaction-storage-service';
6 | import { InsightTransactionService } from './transaction/insight-transaction-service/insight-transaction-service';
7 | import { BlockchainInfoService } from './transaction/blockchain-info-service/blockchain-info-service';
8 | import { QRScanner } from './qrscanner/qrscanner';
9 | import { PaymentService } from './payment/payment-service';
10 | import { InsightPaymentRequestHandler } from './payment/payment-request-handler/insight-payment-request-handler';
11 | import { BitcoinAverageExchangeService } from './currency/bitcoinaverage-service';
12 | import { BitcoinUnit } from './currency/bitcoin-unit';
13 | import { CurrencyService } from './currency/currency-service';
14 | import { AccountService, ACCOUNT_TYPE_BITCOIN_ADDRESS, ACCOUNT_TYPE_BITCOIN_XPUB_KEY, ACCOUNT_TYPE_TESTNET_ADDRESS, ACCOUNT_TYPE_TESTNET_TPUB_KEY } from './account/account-service';
15 | import { AccountSyncService } from './account/account-sync-service';
16 | import { Repository } from './repository';
17 | import { Config } from './config';
18 | import {
19 | CryptocurrencyService ,
20 | BITCOIN,
21 | TESTNET,
22 | BITCOIN_ADDRESS,
23 | BITCOIN_XPUB_KEY,
24 | TESTNET_ADDRESS,
25 | TESTNET_TPUB_KEY,
26 | REGEX_BITCOIN_ADDRESS,
27 | REGEX_BITCOIN_URI,
28 | REGEX_TESTNET_ADDRESS,
29 | REGEX_TESTNET_URI,
30 | REGEX_XPUB_KEY,
31 | REGEX_TPUB_KEY
32 | } from './currency/cryptocurrency-service';
33 |
34 | export function provideCurrencyService(config:Config, exchangeService:BitcoinAverageExchangeService) {
35 | return new CurrencyService(config, exchangeService);
36 | }
37 |
38 | export function provideAccountService(cryptocurrencyService:CryptocurrencyService, config:Config, repository:Repository) {
39 | return new AccountService(cryptocurrencyService, config, repository);
40 | }
41 |
42 | export function provideAccountSyncService(transactionService:TransactionServiceWrapper, transactionStorageService:TransactionStorageService, accountService:AccountService, cryptocurrencyService:CryptocurrencyService) {
43 | return new AccountSyncService(transactionService, transactionStorageService, accountService, cryptocurrencyService);
44 | }
45 |
46 | export function provideTransactionService(http:HttpClient, cryptocurrencyService:CryptocurrencyService) {
47 | return new TransactionServiceWrapper(http, cryptocurrencyService);
48 | }
49 |
50 | export function provideTransactionStorageService(repository:Repository) {
51 | return new TransactionStorageService(repository);
52 | }
53 |
54 | export function providePaymentService(transactionService:TransactionServiceWrapper, cryptocurrencyService:CryptocurrencyService) {
55 | return new PaymentService(transactionService, cryptocurrencyService);
56 | }
57 |
58 | export function provideCryptocurrencyService() {
59 | return new CryptocurrencyService();
60 | }
61 |
62 | export function provideRepository() {
63 | return new Repository();
64 | }
65 |
66 | export function provideConfig(storage:Storage) {
67 | return new Config(storage);
68 | }
69 |
70 | export function provideQRScanner(modalController: ModalController) {
71 | return new QRScanner(modalController);
72 | }
73 |
74 | export function provideBitcoinAverageExchangeService(http:HttpClient, config:Config) {
75 | return new BitcoinAverageExchangeService(http, config);
76 | }
77 |
78 | export {
79 | Config ,
80 | Repository ,
81 | BitcoinUnit ,
82 | AccountService ,
83 | AccountSyncService,
84 | CurrencyService ,
85 | CryptocurrencyService ,
86 | BitcoinAverageExchangeService,
87 | InsightPaymentRequestHandler,
88 | PaymentService,
89 | QRScanner,
90 | TransactionStorageService,
91 | InsightTransactionService,
92 | BlockchainInfoService,
93 | TransactionServiceWrapper,
94 | BITCOIN,
95 | TESTNET,
96 | BITCOIN_ADDRESS,
97 | BITCOIN_XPUB_KEY,
98 | TESTNET_ADDRESS,
99 | TESTNET_TPUB_KEY,
100 | REGEX_BITCOIN_ADDRESS,
101 | REGEX_BITCOIN_URI,
102 | REGEX_TESTNET_ADDRESS,
103 | REGEX_TESTNET_URI,
104 | REGEX_XPUB_KEY,
105 | REGEX_TPUB_KEY,
106 | ACCOUNT_TYPE_BITCOIN_ADDRESS,
107 | ACCOUNT_TYPE_BITCOIN_XPUB_KEY,
108 | ACCOUNT_TYPE_TESTNET_ADDRESS,
109 | ACCOUNT_TYPE_TESTNET_TPUB_KEY
110 | };
111 |
--------------------------------------------------------------------------------
/src/providers/payment/payment-request-handler/insight-payment-request-handler.ts:
--------------------------------------------------------------------------------
1 | import { PaymentRequestHandler } from './../../../api/payment-request-handler';
2 | import { TransactionService } from './../../../api/transaction-service';
3 | import { PaymentRequest, PAYMENT_STATUS_SERVICE_ERROR, PAYMENT_STATUS_RECEIVED, PAYMENT_STATUS_PARTIAL_PAID, PAYMENT_STATUS_OVERPAID } from './../../../api/payment-request';
4 | import { EventEmitter } from 'events';
5 | import * as io from 'socket.io-client';
6 |
7 | export class InsightPaymentRequestHandler extends EventEmitter implements PaymentRequestHandler {
8 |
9 | protected _paymentRequest: PaymentRequest;
10 | protected socket: SocketIO.Socket;
11 | protected transactionService: TransactionService;
12 | protected serviceUrl: string;
13 |
14 | get paymentRequest() : PaymentRequest {
15 | return this._paymentRequest;
16 | }
17 |
18 | constructor() {
19 | super();
20 | }
21 |
22 | static createPaymentRequestHandler(paymentRequest: PaymentRequest, transactionService: TransactionService, serviceUrl:string) {
23 | let handler = new InsightPaymentRequestHandler();
24 | handler._paymentRequest = paymentRequest;
25 | handler.transactionService = transactionService;
26 | handler.serviceUrl = serviceUrl;
27 | handler.init();
28 | return handler;
29 | }
30 |
31 | init() {
32 | try {
33 | this.socket = io(this.serviceUrl);
34 | this.socket.on('error', () => {
35 | this.emit("payment-status:" + PAYMENT_STATUS_SERVICE_ERROR);
36 | }).on('bitcoind/addresstxid', data => {
37 | this.triggerStatusUpdate(data.txid)
38 | .then((close:boolean) => {
39 | if (close && this && this.cancel) {
40 | this.cancel();
41 | }
42 | });
43 | }).on('connect', () => {
44 | this.socket.emit('subscribe', 'bitcoind/addresstxid', [ this.paymentRequest.address ]);
45 | });
46 | } catch (e) {
47 | this.emit("payment-status:" + PAYMENT_STATUS_SERVICE_ERROR);
48 | }
49 | }
50 |
51 | triggerStatusUpdate(txid:string) : Promise {
52 | return new Promise ((resolve, reject) => {
53 | this.transactionService.findTransactions({
54 | addresses : [this.paymentRequest.address] ,
55 | txid : txid
56 | }).then(transactions => {
57 | if (transactions.length > 0) {
58 | let event = {
59 | txid : txid ,
60 | address : this.paymentRequest.address ,
61 | amount : transactions[0].amount
62 | };
63 |
64 | if (transactions[0].amount < this.paymentRequest.amount) {
65 | this.emit("payment-status:" + PAYMENT_STATUS_PARTIAL_PAID, event);
66 | } else if (transactions[0].amount > this.paymentRequest.amount) {
67 | this.emit("payment-status:" + PAYMENT_STATUS_OVERPAID, event);
68 | } else {
69 | this.emit("payment-status:" + PAYMENT_STATUS_RECEIVED, event);
70 | }
71 |
72 | resolve(true);
73 | } else {
74 | resolve(false);
75 | }
76 | });
77 | });
78 | }
79 |
80 | cancel() {
81 | this.socket.disconnect();
82 | }
83 |
84 | }
--------------------------------------------------------------------------------
/src/providers/payment/payment-service.ts:
--------------------------------------------------------------------------------
1 | import {Injectable} from '@angular/core';
2 | import { TransactionServiceWrapper, InsightPaymentRequestHandler, CryptocurrencyService, BITCOIN, TESTNET } from '../index';
3 | import { PaymentRequest } from './../../api/payment-request';
4 | import { PaymentRequestHandler } from './../../api/payment-request-handler';
5 | import * as payment from '../../api/payment-service';
6 |
7 | @Injectable()
8 | export class PaymentService implements payment.PaymentService {
9 |
10 | constructor(
11 | protected transactionService: TransactionServiceWrapper ,
12 | protected cryptocurrencyService: CryptocurrencyService
13 | ) {}
14 |
15 | createPaymentRequestHandler(paymentRequest: PaymentRequest) : PaymentRequestHandler {
16 | let currency = this.cryptocurrencyService.parseInput(paymentRequest.address).currency;
17 |
18 | if (currency == BITCOIN) {
19 | return InsightPaymentRequestHandler.createPaymentRequestHandler(paymentRequest, this.transactionService, 'https://insight.bitpay.com');
20 | } else if (currency == TESTNET) {
21 | return InsightPaymentRequestHandler.createPaymentRequestHandler(paymentRequest, this.transactionService, 'https://test-insight.bitpay.com');
22 | }
23 | }
24 |
25 | }
--------------------------------------------------------------------------------
/src/providers/qrscanner/qrscanner.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { ModalController, Modal } from 'ionic-angular';
3 |
4 | @Injectable()
5 | export class QRScanner {
6 |
7 | protected modal:Modal;
8 |
9 | constructor(protected modalController:ModalController) {
10 | }
11 |
12 | scan(validate: (text:any) => any = (text) => { return text; }) : Promise {
13 | this.modal = this.modalController.create('qrscanner', { validate: validate});
14 | this.modal.present();
15 |
16 | return new Promise((resolve, reject) => {
17 | this.modal.onDidDismiss(data => {
18 | if (data && data.text) {
19 | resolve(data.text);
20 | } else {
21 | reject();
22 | }
23 | });
24 | });
25 | }
26 |
27 | }
--------------------------------------------------------------------------------
/src/providers/repository.ts:
--------------------------------------------------------------------------------
1 | import PouchDB from 'pouchdb';
2 | import {Injectable} from '@angular/core';
3 |
4 | declare var emit:any;
5 |
6 | @Injectable()
7 | export class Repository {
8 |
9 | protected _db:any;
10 | protected _dbname = "bitpocket-pouchdb";
11 |
12 | get db() : any {
13 | return this._db;
14 | }
15 |
16 | addOrEditDocument(doc:any) : Promise {
17 | if (doc._id) {
18 | return this.db.upsert(doc._id, (oldDoc) => {
19 | return doc;
20 | });
21 | } else {
22 | return this.addDocument(doc);
23 | }
24 | }
25 |
26 | addDocumentIfNotExists(doc:any) : Promise {
27 | return this.db.putIfNotExists(doc);
28 | }
29 |
30 | addDocument(doc:any) : Promise {
31 | return this.db.post(doc);
32 | }
33 |
34 | editDocument(doc:any) : Promise {
35 | return this.db.put(doc);
36 | }
37 |
38 | removeDocument(id:string) : Promise {
39 | return this.db.get(id).then((doc) => {
40 | return this.db.remove(doc);
41 | });
42 | }
43 |
44 | findById(id:string) : Promise {
45 | return this.db.get(id);
46 | }
47 |
48 | findByDocumentType(doctype:string) : Promise> {
49 | return new Promise>((resolve, reject) => {
50 | this._db
51 | .find({
52 | selector : {
53 | doctype : doctype
54 | }
55 | })
56 | .then((res) => {
57 | resolve(this.prepareDocuments(res));
58 | }).catch(error => {
59 | reject(error);
60 | });
61 | });
62 | }
63 |
64 | findDocuments(query) : Promise> {
65 | return new Promise> ((resolve, reject) => {
66 | this.db.find(query).then((res) => {
67 | resolve(this.prepareDocuments(res));
68 | }).catch(error => {
69 | reject(error);
70 | });
71 | });
72 | }
73 |
74 | init() : Promise {
75 |
76 | this._db = new PouchDB(this._dbname);
77 |
78 | return Promise.all([
79 | this.db.createIndex({
80 | index : {
81 | fields : ['doctype']
82 | }
83 | }) ,
84 | this.db.createIndex({
85 | index : {
86 | fields : ['timestamp','account']
87 | }
88 | }) ,
89 | this.db.createIndex({
90 | index : {
91 | fields : ['timestamp','address']
92 | }
93 | })
94 | ]);
95 | }
96 |
97 | prepareDocuments(res) : Array {
98 | let docs = [];
99 |
100 | if (res.docs && res.docs.length > 0) {
101 | docs = res.docs;
102 | }
103 |
104 | return docs;
105 | }
106 |
107 | }
--------------------------------------------------------------------------------
/src/providers/transaction/blockchain-info-service/blockchain-info-service.spec.ts:
--------------------------------------------------------------------------------
1 | import { BlockchainInfoService } from './../../index';
2 | import {data} from './blockchain-info-data';
3 |
4 | describe('Transaction Service', () => {
5 |
6 | let transactionService = new BlockchainInfoService(null);
7 |
8 | it('should build correct urls', () => {
9 | expect(transactionService.buildUrl({
10 | addresses : ['152f1muMCNa7goXYhYAQC61hxEgGacmncB','12ni9ddt4WHfEQriVN7DajDBd1JbKL9yUZ','1FS4FF2SYdHf3PGfSbjpdYcUiUYxiVLy73'] ,
11 | to : 15 ,
12 | from : 5
13 | })).toEqual('https://blockchain.info/multiaddr?active=152f1muMCNa7goXYhYAQC61hxEgGacmncB|12ni9ddt4WHfEQriVN7DajDBd1JbKL9yUZ|1FS4FF2SYdHf3PGfSbjpdYcUiUYxiVLy73&limit=10&offset=5&cors=true');
14 | });
15 |
16 | it('should parse transactions correctly', () => {
17 | let txs = transactionService.parseTransactions(['19L9LhCArs9SUNu3ZzaqJ1ys3dGgvJmi5T'], data);
18 | let incomming = [true,false,true,false,true,false,true,false,true,false,false,true,true];
19 |
20 | for (let i = 0; i < incomming.length; i++) {
21 | expect(txs[i].incomming).toBe(incomming[i]);
22 | }
23 | });
24 |
25 | });
--------------------------------------------------------------------------------
/src/providers/transaction/blockchain-info-service/blockchain-info-service.ts:
--------------------------------------------------------------------------------
1 | import { TransactionService } from './../../../api/transaction-service';
2 | import { TransactionFilter } from './../../../api/transaction-filter';
3 | import { BITCOIN, BitcoinUnit } from './../../index';
4 | import { HttpClient } from '@angular/common/http';
5 | import { Transaction } from './../../../api/transaction';
6 |
7 | const BLOCKCHAIN_INFO_BASE_URL = "https://blockchain.info/";
8 |
9 | export class BlockchainInfoService implements TransactionService {
10 |
11 | protected lastKnownBlockchainHeight = 0;
12 |
13 | set blockchainHeight(height:number) {
14 | this.lastKnownBlockchainHeight = height;
15 | }
16 |
17 | constructor(protected http:HttpClient) {}
18 |
19 | findTransactions(filter: TransactionFilter) : Promise {
20 | return new Promise((resolve, reject) => {
21 | this.getBlockHeight()
22 | .then(() => {
23 | this.http.get(this.buildUrl(filter))
24 | .subscribe(response => {
25 | resolve(this.parseTransactions(filter.addresses, response));
26 | }, () => { reject(); })
27 | })
28 | });
29 | }
30 |
31 | getBlockHeight() : Promise {
32 | return new Promise ((resolve, reject) => {
33 | this.http.get(BLOCKCHAIN_INFO_BASE_URL + 'q/getblockcount?cors=true')
34 | .subscribe((response:any) => {
35 | if (response) {
36 | this.lastKnownBlockchainHeight = response;
37 | }
38 | resolve(this.lastKnownBlockchainHeight);
39 | }, () => { resolve(this.lastKnownBlockchainHeight); });
40 | });
41 | }
42 |
43 | parseTransactions(addresses:string[], json:any) {
44 | let txs:Transaction[] = [];
45 | let items = [];
46 |
47 | if (json && json.txs && Array.isArray(json.txs)) {
48 | items = json.txs;
49 | } else if (json && json.inputs && json.out) {
50 | items = [json];
51 | }
52 |
53 | for (let item of items) {
54 | let tx:any = this.parseTransactionInputs(addresses, item.inputs);
55 | if (!tx) {
56 | tx = this.parseTransactionOutputs(addresses, item.out);
57 | }
58 |
59 | if (tx) {
60 | tx.currency = "BTC";
61 | tx.confirmations = this.lastKnownBlockchainHeight - item['block_height'];
62 | tx._id = item.hash;
63 | tx.timestamp = parseInt(item.time);
64 | txs.push(tx);
65 | }
66 | }
67 |
68 | return txs;
69 | }
70 |
71 | parseTransactionOutputs(addresses:string[], outputs:any[]) {
72 | if (outputs && Array.isArray(outputs)) {
73 | for (let output of outputs) {
74 | if (output.addr) {
75 | let index = addresses.indexOf(output.addr);
76 | if (index >= 0) {
77 | return {
78 | address : addresses[index] ,
79 | incomming : true ,
80 | amount : BitcoinUnit.from(output.value).to('BTC')
81 | };
82 | }
83 | }
84 | }
85 | }
86 | return null;
87 | }
88 |
89 | parseTransactionInputs(addresses:string[], inputs:any[]) {
90 | if (inputs && Array.isArray(inputs)) {
91 | for (let input of inputs) {
92 | if (input.prev_out && input.prev_out.addr && input.prev_out.addr) {
93 | let index = addresses.indexOf(input.prev_out.addr);
94 | if (index >= 0) {
95 | return {
96 | address : addresses[index] ,
97 | incomming : false ,
98 | amount : BitcoinUnit.from(input.prev_out.value).to('BTC')
99 | };
100 | }
101 | }
102 | }
103 | }
104 | return null;
105 | }
106 |
107 | buildUrl(filter:TransactionFilter = {}) : string {
108 | let url = "";
109 |
110 | if (filter.txid) {
111 | url = BLOCKCHAIN_INFO_BASE_URL + "rawtx/" + filter.txid + "?";
112 | } else if (filter.addresses && filter.addresses.length > 0) {
113 | url = BLOCKCHAIN_INFO_BASE_URL + "multiaddr?active=" + filter.addresses.join('|') + "&";
114 | }
115 |
116 | if (filter.from >= 0 && filter.to > 0) {
117 | let limit = filter.to - filter.from;
118 | url += 'limit=' + limit + '&offset=' + filter.from;
119 | } else {
120 | url += 'limit=50&offset=0';
121 | }
122 |
123 | return url + '&cors=true';
124 | }
125 |
126 | }
--------------------------------------------------------------------------------
/src/providers/transaction/blockcypher-service/blockcypher-data.ts:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getbitpocket/bitpocket-mobile-app/907cf005fcd2c33bd9c2aa2d2a9a2281bc1a6376/src/providers/transaction/blockcypher-service/blockcypher-data.ts
--------------------------------------------------------------------------------
/src/providers/transaction/blockcypher-service/blockcypher-service.spec.ts:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getbitpocket/bitpocket-mobile-app/907cf005fcd2c33bd9c2aa2d2a9a2281bc1a6376/src/providers/transaction/blockcypher-service/blockcypher-service.spec.ts
--------------------------------------------------------------------------------
/src/providers/transaction/blockcypher-service/blockcypher-service.ts:
--------------------------------------------------------------------------------
1 | import { TransactionService } from './../../../api/transaction-service';
2 | import { TransactionFilter } from './../../../api/transaction-filter';
3 | import { BITCOIN, TESTNET } from './../../index';
4 | import { HttpClient } from '@angular/common/http';
5 | import { Transaction } from './../../../api/transaction';
6 |
7 | // NOT YET IMPLEMENTED
8 |
9 | export class BlockcypherService implements TransactionService {
10 |
11 | constructor(protected http:HttpClient, protected cryptocurrency:string) {}
12 |
13 | findTransactions(filter: TransactionFilter) : Promise {
14 | return new Promise((resolve, reject) => {
15 |
16 | });
17 | }
18 |
19 | parseTransactions(addresses:string[], json:any) {
20 |
21 | }
22 |
23 | buildUrl(filter:TransactionFilter = {}) : string {
24 | let url = "";
25 |
26 | if (this.cryptocurrency == TESTNET) {
27 | url += "https://api.blockcypher.com/v1/btc/test3/";
28 | } else {
29 | url += "https://api.blockcypher.com/v1/btc/main/";
30 | }
31 |
32 | if (filter.txid) {
33 | } else if (filter.addresses && filter.addresses.length > 0) {
34 | }
35 |
36 | if (filter.from >= 0 && filter.to > 0) {
37 | let limit = filter.to - filter.from;
38 | url += 'limit=' + limit + '&offset=' + filter.from;
39 | } else {
40 | url += 'limit=50&offset=0';
41 | }
42 |
43 | return url;
44 | }
45 |
46 | }
--------------------------------------------------------------------------------
/src/providers/transaction/insight-transaction-service/insight-transaction-service.spec.ts:
--------------------------------------------------------------------------------
1 | import { InsightTransactionService } from './../../index';
2 | import {data} from './insight-transaction-data';
3 |
4 | describe('Transaction Service', () => {
5 |
6 | let transactionService = new InsightTransactionService(null, "https://insight.bitpay.com");
7 |
8 | it('should build correct urls', () => {
9 | expect(transactionService.buildUrl({
10 | addresses : ['152f1muMCNa7goXYhYAQC61hxEgGacmncB','12ni9ddt4WHfEQriVN7DajDBd1JbKL9yUZ','1FS4FF2SYdHf3PGfSbjpdYcUiUYxiVLy73'] ,
11 | to : 15 ,
12 | from : 5
13 | })).toEqual('https://insight.bitpay.com/api/addrs/152f1muMCNa7goXYhYAQC61hxEgGacmncB,12ni9ddt4WHfEQriVN7DajDBd1JbKL9yUZ,1FS4FF2SYdHf3PGfSbjpdYcUiUYxiVLy73/txs?from=5&to=15');
14 | });
15 |
16 | it('should find address input', () => {
17 | let index = transactionService.addressIndex(
18 | ['ad1','ad2','ad3','ad4'] ,
19 | ['ad0','ad3']
20 | );
21 | expect(index).toEqual(1);
22 | });
23 |
24 | it('should parse transactions correctly', () => {
25 | let txs = transactionService.parseTransactions(['19L9LhCArs9SUNu3ZzaqJ1ys3dGgvJmi5T'], data);
26 | let incomming = [false, true, false, true, false, false, true, true, false, true];
27 | for (let i = 0; i < incomming.length; i++) {
28 | expect(txs[i].incomming).toBe(incomming[i]);
29 | }
30 | });
31 |
32 | });
--------------------------------------------------------------------------------
/src/providers/transaction/insight-transaction-service/insight-transaction-service.ts:
--------------------------------------------------------------------------------
1 | import { TransactionService } from './../../../api/transaction-service';
2 | import { TransactionFilter } from './../../../api/transaction-filter';
3 | import { CryptocurrencyService, TESTNET, BITCOIN } from './../../index';
4 | import { HttpClient } from '@angular/common/http';
5 | import { Transaction } from './../../../api/transaction';
6 |
7 | export class InsightTransactionService implements TransactionService {
8 |
9 | constructor(
10 | protected http:HttpClient,
11 | protected serviceUrl:string,
12 | protected cryptocurrency:string = BITCOIN) {}
13 |
14 | findTransactions(filter:TransactionFilter) : Promise {
15 | return new Promise((resolve, reject) => {
16 | this.http.get(this.buildUrl(filter))
17 | .subscribe(response => {
18 | resolve(this.parseTransactions(filter.addresses, response));
19 | },() => { reject(); });
20 | });
21 | }
22 |
23 | parseTransactionInputs(vins:any[], addresses:string[]) {
24 | if (vins && vins.length > 0) {
25 | for (let vin of vins) {
26 | let addressIndex = addresses.indexOf(vin.addr);
27 | if (addressIndex >= 0) {
28 | // TODO: same address in vin multiple times?
29 | return {
30 | amount : parseFloat(vin.value) ,
31 | incomming : false ,
32 | address : addresses[addressIndex]
33 | };
34 | }
35 | }
36 | }
37 | return null;
38 | }
39 |
40 | parseTransactionOutputs(vouts:any[], addresses:string[]) {
41 | if (vouts && vouts.length > 0) {
42 | for (let vout of vouts) {
43 | if (vout.scriptPubKey && (vout.scriptPubKey.type == 'pubkeyhash' || vout.scriptPubKey.type == 'scripthash')) {
44 | let index = this.addressIndex(vout.scriptPubKey.addresses, addresses);
45 |
46 | if (index >= 0) {
47 | // TODO: same address in vout multiple times?
48 | // http://bitcoin.stackexchange.com/questions/4475/can-the-same-target-address-appear-more-than-once-on-transaction-output
49 |
50 | return {
51 | amount : parseFloat(vout.value) ,
52 | incomming : true ,
53 | address : addresses[index]
54 | };
55 | }
56 | }
57 | }
58 | }
59 |
60 | return null;
61 | }
62 |
63 | parseTransactions(addresses:string[], json:any) : Transaction[] {
64 | let output = [];
65 | let items = [];
66 |
67 | if (json.items && json.items.length) {
68 | items = json.items;
69 | } else {
70 | items.push(json);
71 | }
72 |
73 | for (let item of items) {
74 |
75 | let tx:any = this.parseTransactionInputs(item.vin, addresses);
76 | if (!tx) {
77 | tx = this.parseTransactionOutputs(item.vout, addresses);
78 | }
79 |
80 | if (tx) {
81 | tx.currency = "BTC";
82 | tx._id = item.txid;
83 | tx.confirmations = parseInt(item.confirmations);
84 | tx.timestamp = parseInt(item.time);
85 | output.push(tx);
86 | }
87 | }
88 |
89 | return output;
90 | }
91 |
92 | /**
93 | * returns index of addressesAsSearchInput array, which is found inside
94 | * addressesToSearchFor array
95 | */
96 | addressIndex(addressesToSearchFor:string[], addressesAsSearchInput:string[]) : number {
97 | for (let i = 0; i < addressesAsSearchInput.length; i++) {
98 | if (addressesToSearchFor.indexOf(addressesAsSearchInput[i]) >= 0) {
99 | return i;
100 | }
101 | }
102 | return -1;
103 | }
104 |
105 | buildUrl(filter:any = {}) : string {
106 | let url = "";
107 |
108 | if (filter.addresses && filter.addresses.length > 0) {
109 | url = this.serviceUrl + "/api";
110 |
111 | if (filter.txid) {
112 | url += '/tx/' + filter.txid;
113 | } else {
114 | url += '/addrs/' + filter.addresses.join(',') + '/txs';
115 | }
116 | }
117 |
118 | if (filter.from >= 0 && filter.to > 0) {
119 | url += '?from=' + filter.from + '&to=' + filter.to;
120 | }
121 |
122 | return url;
123 | }
124 |
125 | }
--------------------------------------------------------------------------------
/src/providers/transaction/transaction-service-wrapper.ts:
--------------------------------------------------------------------------------
1 | import { TransactionService } from './../../api/transaction-service';
2 | import { TransactionFilter } from './../../api/transaction-filter';
3 | import { CryptocurrencyService, BlockchainInfoService, InsightTransactionService, BITCOIN, TESTNET } from './../index';
4 | import { Injectable } from '@angular/core';
5 | import { HttpClient } from '@angular/common/http';
6 | import { Transaction } from './../../api/transaction';
7 |
8 | @Injectable()
9 | export class TransactionServiceWrapper implements TransactionService {
10 |
11 | protected instances:any = {};
12 |
13 | constructor(protected httpClient:HttpClient, protected cryptocurrencyService:CryptocurrencyService) {}
14 |
15 | init() {
16 | if (!this.instances[BITCOIN]) {
17 | this.instances[BITCOIN] = [];
18 | this.instances[TESTNET] = [];
19 | this.instances[BITCOIN].push(new BlockchainInfoService(this.httpClient));
20 | this.instances[BITCOIN].push(new InsightTransactionService(this.httpClient, 'https://insight.bitpay.com'));
21 | this.instances[TESTNET].push(new InsightTransactionService(this.httpClient, 'https://test-insight.bitpay.com', TESTNET));
22 | }
23 | }
24 |
25 | findTransactions(filter: TransactionFilter): Promise {
26 | this.init();
27 | return this.triggerRandomInstance(filter, this.checkCryptocurrency(filter));
28 | }
29 |
30 | triggerRandomInstance(filter:TransactionFilter, cryptocurrency:string) {
31 | return new Promise ((resolve, reject) => {
32 | this.getInstance(cryptocurrency).findTransactions(filter)
33 | .then(result => {
34 | resolve(result);
35 | }).catch(() => {
36 | resolve(this.triggerRandomInstance(filter, cryptocurrency));
37 | });
38 | });
39 | }
40 |
41 | getInstance(cryptocurrency:string) : TransactionService {
42 | let index = Math.floor(Math.random() * this.instances[cryptocurrency].length);
43 | return this.instances[cryptocurrency][index];
44 | }
45 |
46 | checkCryptocurrency(filter: TransactionFilter) : string {
47 | if (filter.addresses && filter.addresses.length > 0) {
48 | return this.cryptocurrencyService.parseInput(filter.addresses[0]).currency;
49 | } else {
50 | return BITCOIN;
51 | }
52 | }
53 |
54 | }
--------------------------------------------------------------------------------
/src/providers/transaction/transaction-storage-service.ts:
--------------------------------------------------------------------------------
1 | import { TransactionFilter } from './../../api/transaction-filter';
2 | import { Repository } from './../repository';
3 | import { Transaction } from './../../api/transaction';
4 | import { Injectable } from '@angular/core';
5 |
6 | @Injectable()
7 | export class TransactionStorageService {
8 |
9 | constructor(
10 | protected repository: Repository
11 | ) {}
12 |
13 | storeTransaction(transaction:Transaction) : Promise {
14 | return new Promise((resolve, reject) => {
15 | this.repository.addOrEditDocument(transaction)
16 | .then(() => { resolve(transaction); })
17 | .catch(() => { reject(); });
18 | });
19 | }
20 |
21 | retrieveTransaction(txid: string) {
22 | return this.repository.findById(txid);
23 | }
24 |
25 | retrieveTransactions(transactionFilter: TransactionFilter) : Promise {
26 | let selector:any = null, limit = true;
27 |
28 | if (transactionFilter.startTime > 0 && transactionFilter.endTime > 0) {
29 | limit = false;
30 | selector = {
31 | '$and' : [
32 | { 'timestamp' : { '$gte':transactionFilter.startTime } } ,
33 | { 'timestamp' : { '$lte':transactionFilter.endTime } }
34 | ]
35 | };
36 | } else {
37 | selector = {
38 | '$and' : [
39 | { 'timestamp' : { '$gt':null } } ,
40 | ]
41 | };
42 | }
43 |
44 | if (transactionFilter.account && /static-address/.test(transactionFilter.account.type)) {
45 | selector.$and.push({ 'address' : { '$eq':transactionFilter.account.data }});
46 | } else if (transactionFilter.account && /pub-key/.test(transactionFilter.account.type)) {
47 | selector.$and.push({ 'account' : { '$eq': transactionFilter.account._id }});
48 | } else if (transactionFilter.addresses) {
49 | selector.$and.push({ 'address' : { '$in':transactionFilter.addresses }});
50 | }
51 |
52 | return new Promise((resolve, reject) => {
53 | let query = {
54 | selector : selector,
55 | sort : [{ timestamp : 'desc' }]
56 | };
57 |
58 | if (limit) {
59 | query['limit'] = transactionFilter.to - transactionFilter.from;
60 | query['skip'] = transactionFilter.from > 0 ? transactionFilter.from : 0;
61 | }
62 |
63 | resolve(this.repository.findDocuments(query));
64 | });
65 | }
66 |
67 | }
--------------------------------------------------------------------------------
/src/service-worker.js:
--------------------------------------------------------------------------------
1 | // tick this to make the cache invalidate and update
2 | const CACHE_VERSION = 1;
3 | const CURRENT_CACHES = {
4 | 'read-through': 'read-through-cache-v' + CACHE_VERSION
5 | };
6 |
7 | self.addEventListener('activate', (event) => {
8 | // Delete all caches that aren't named in CURRENT_CACHES.
9 | // While there is only one cache in this example, the same logic will handle the case where
10 | // there are multiple versioned caches.
11 | const expectedCacheNames = Object.keys(CURRENT_CACHES).map((key) => {
12 | return CURRENT_CACHES[key];
13 | });
14 |
15 | event.waitUntil(
16 | caches.keys().then((cacheNames) => {
17 | return Promise.all(
18 | cacheNames.map((cacheName) => {
19 | if (expectedCacheNames.indexOf(cacheName) === -1) {
20 | // If this cache name isn't present in the array of "expected" cache names, then delete it.
21 | console.log('Deleting out of date cache:', cacheName);
22 | return caches.delete(cacheName);
23 | }
24 | })
25 | );
26 | })
27 | );
28 | });
29 |
30 | // This sample illustrates an aggressive approach to caching, in which every valid response is
31 | // cached and every request is first checked against the cache.
32 | // This may not be an appropriate approach if your web application makes requests for
33 | // arbitrary URLs as part of its normal operation (e.g. a RSS client or a news aggregator),
34 | // as the cache could end up containing large responses that might not end up ever being accessed.
35 | // Other approaches, like selectively caching based on response headers or only caching
36 | // responses served from a specific domain, might be more appropriate for those use cases.
37 | self.addEventListener('fetch', (event) => {
38 |
39 | event.respondWith(
40 | caches.open(CURRENT_CACHES['read-through']).then((cache) => {
41 | return cache.match(event.request).then((response) => {
42 | if (response) {
43 | // If there is an entry in the cache for event.request, then response will be defined
44 | // and we can just return it.
45 |
46 | return response;
47 | }
48 |
49 | // Otherwise, if there is no entry in the cache for event.request, response will be
50 | // undefined, and we need to fetch() the resource.
51 | console.log(' No response for %s found in cache. ' +
52 | 'About to fetch from network...', event.request.url);
53 |
54 | // We call .clone() on the request since we might use it in the call to cache.put() later on.
55 | // Both fetch() and cache.put() "consume" the request, so we need to make a copy.
56 | // (see https://fetch.spec.whatwg.org/#dom-request-clone)
57 | return fetch(event.request.clone()).then((response) => {
58 |
59 | // Optional: add in extra conditions here, e.g. response.type == 'basic' to only cache
60 | // responses from the same domain. See https://fetch.spec.whatwg.org/#concept-response-type
61 | if (response.status < 400 && response.type === 'basic') {
62 | // We need to call .clone() on the response object to save a copy of it to the cache.
63 | // (https://fetch.spec.whatwg.org/#dom-request-clone)
64 | cache.put(event.request, response.clone());
65 | }
66 |
67 | // Return the original response object, which will be used to fulfill the resource request.
68 | return response;
69 | });
70 | }).catch((error) => {
71 | // This catch() will handle exceptions that arise from the match() or fetch() operations.
72 | // Note that a HTTP error response (e.g. 404) will NOT trigger an exception.
73 | // It will return a normal response object that has the appropriate error code set.
74 | console.error(' Read-through caching failed:', error);
75 |
76 | throw error;
77 | });
78 | })
79 | );
80 | });
--------------------------------------------------------------------------------
/src/theme/_font.scss:
--------------------------------------------------------------------------------
1 | @font-face {
2 | font-family: "Ubuntu";
3 | font-style: normal;
4 | font-weight: normal;
5 | src: url("#{$font-path}/Ubuntu-R.ttf") format("truetype");
6 | }
7 | @font-face {
8 | font-family: "Ubuntu";
9 | font-style: normal;
10 | font-weight: 300;
11 | src: url("#{$font-path}/Ubuntu-L.ttf") format("truetype");
12 | }
13 | @font-face {
14 | font-family: "Ubuntu";
15 | font-style: normal;
16 | font-weight: 500;
17 | src: url("#{$font-path}/Ubuntu-M.ttf") format("truetype");
18 | }
19 | @font-face {
20 | font-family: "Ubuntu";
21 | font-style: normal;
22 | font-weight: 700;
23 | src: url("#{$font-path}/Ubuntu-B.ttf") format("truetype");
24 | }
25 | @font-face {
26 | font-family: "Ubuntu Condensed";
27 | font-style: normal;
28 | font-weight: normal;
29 | src: url("#{$font-path}/Ubuntu-C.ttf") format("truetype");
30 | }
--------------------------------------------------------------------------------
/src/theme/variables.scss:
--------------------------------------------------------------------------------
1 | // Ionic Variables and Theming. For more info, please see:
2 | // http://ionicframework.com/docs/v2/theming/
3 | $font-path: "../assets/fonts";
4 |
5 | @import "ionic.globals";
6 |
7 |
8 | // Shared Variables
9 | // --------------------------------------------------
10 | // To customize the look and feel of this app, you can override
11 | // the Sass variables found in Ionic's source scss files.
12 | // To view all the possible Ionic variables, see:
13 | // http://ionicframework.com/docs/v2/theming/overriding-ionic-variables/
14 |
15 | $font-family-base: 'Ubuntu', sans-serif;
16 |
17 | $button-border-radius: 6px;
18 |
19 | // font sizes
20 | $font-size-base: 1.5rem; //
21 | $font-size-small: 2.0rem; //
22 | $font-size-x-small: 2.5rem; //
23 | $font-size-medium: 3.0rem; //
24 | $font-size-x-medium: 3.5rem; //
25 | $font-size-large: 4.0rem; //
26 | $font-size-x-large: 4.5rem; //
27 |
28 | // sizes
29 | $bitpocket-xs-min: 360px;
30 | $bitpocket-sm-min: 576px;
31 | $bitpocket-md-min: 768px;
32 |
33 |
34 | // Named Color Variables
35 | // --------------------------------------------------
36 | // Named colors makes it easy to reuse colors on various components.
37 | // It's highly recommended to change the default colors
38 | // to match your app's branding. Ionic uses a Sass map of
39 | // colors so you can add, rename and remove colors as needed.
40 | // The "primary" color is the only required color in the map.
41 |
42 | $basic-color: rgba(106,109,122,1);
43 | $basic-color-transparency: rgba(255,255,255,0.2);
44 |
45 | $colors: (
46 | primary: $basic-color,
47 | secondary: #32db64,
48 | danger: #f53d3d,
49 | light: #f4f4f4,
50 | dark: #222,
51 | menu-item: (
52 | base: $basic-color,
53 | contrast: $basic-color
54 | ),
55 | menutoggle: (
56 | base: $basic-color,
57 | contrast: $basic-color
58 | ),
59 | number: (
60 | base: $basic-color,
61 | contrast: white
62 | ),
63 | number-decimals: (
64 | base: $basic-color-transparency,
65 | contrast: white
66 | ),
67 | number-backspaceinput: (
68 | base: $basic-color-transparency,
69 | contrast: white
70 | ),
71 | numpad-reset: (
72 | base: $basic-color-transparency,
73 | contrast: white
74 | ),
75 | numpad-request: (
76 | base: white,
77 | contrast: $basic-color
78 | ),
79 | show-history: (
80 | base: white,
81 | contrast: $basic-color
82 | ),
83 | checkbox-style: (
84 | base: white,
85 | contrast: $basic-color
86 | )
87 | );
88 |
89 | $text-color: map-get($colors , primary);
90 |
91 |
92 | // App iOS Variables
93 | // --------------------------------------------------
94 | // iOS only Sass variables can go here
95 |
96 | $toolbar-ios-title-text-color: map-get($colors , primary);
97 |
98 |
99 | // App Material Design Variables
100 | // --------------------------------------------------
101 | // Material Design only Sass variables can go here
102 |
103 | $font-family-md-base: $font-family-base;
104 |
105 | $toolbar-md-button-color: map-get($colors , primary);
106 | $toolbar-md-title-text-color: map-get($colors , primary);
107 |
108 | $list-md-header-color: white;
109 | $list-md-header-font-size: $font-size-small;
110 | $list-md-text-color: map-get($colors , primary);
111 |
112 | $card-md-text-color: map-get($colors , primary);
113 | $card-md-title-text-color: map-get($colors , primary);
114 |
115 | $checkbox-md-icon-background-color-on: rgba(map-get($colors , primary),0.5);
116 | $checkbox-md-icon-background-color-off: rgba(map-get($colors , primary),0.5);
117 | $checkbox-md-icon-border-color-on: map-get($colors , primary);
118 | $checkbox-md-icon-border-color-off: map-get($colors , primary);
119 |
120 | // App Windows Variables
121 | // --------------------------------------------------
122 | // Windows only Sass variables can go here
123 |
124 |
125 |
126 |
127 | // App Theme
128 | // --------------------------------------------------
129 | // Ionic apps can have different themes applied, which can
130 | // then be future customized. This import comes last
131 | // so that the above variables are used and Ionic's
132 | // default are overridden.
133 |
134 | @import "ionic.theme.default";
135 |
136 |
137 | // Ionicons
138 | // --------------------------------------------------
139 | // The premium icon font for Ionic. For more info, please see:
140 | // http://ionicframework.com/docs/v2/ionicons/
141 |
142 | @import "ionic.ionicons";
143 |
144 |
145 | // Fonts
146 | // --------------------------------------------------
147 |
148 | @import "font";
149 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "allowSyntheticDefaultImports": true,
4 | "declaration": false,
5 | "emitDecoratorMetadata": true,
6 | "experimentalDecorators": true,
7 | "lib": [
8 | "dom",
9 | "es2015"
10 | ],
11 | "module": "es2015",
12 | "moduleResolution": "node",
13 | "sourceMap": true,
14 | "target": "es5"
15 | },
16 | "include": [
17 | "src/**/*.ts"
18 | ],
19 | "exclude": [
20 | "node_modules",
21 | "src/**/*.spec.ts"
22 | ],
23 | "compileOnSave": false,
24 | "atom": {
25 | "rewriteTsconfig": false
26 | }
27 | }
--------------------------------------------------------------------------------
/tslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "rules": {
3 | "no-duplicate-variable": true,
4 | "no-unused-variable": [
5 | true
6 | ]
7 | },
8 | "rulesDirectory": [
9 | "node_modules/tslint-eslint-rules/dist/rules"
10 | ]
11 | }
12 |
--------------------------------------------------------------------------------
/www/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
8 | BitPocket
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
--------------------------------------------------------------------------------