diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 404fc41c1..9cf688e12 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -27,6 +27,7 @@ import { CopayApp } from './app.component'; import { TabsPage } from '../pages/tabs/tabs'; import { AddPage } from '../pages/add/add'; import { CreateWalletPage } from '../pages/add/create-wallet/create-wallet'; +import { CopayersPage } from '../pages/copayers/copayers'; import { ImportWalletPage } from '../pages/add/import-wallet/import-wallet'; import { JoinWalletPage } from '../pages/add/join-wallet/join-wallet'; import { BackupRequestPage } from '../pages/onboarding/backup-request/backup-request'; @@ -89,6 +90,7 @@ export function createTranslateLoader(http: Http) { let pages: any = [ AddPage, CreateWalletPage, + CopayersPage, ImportWalletPage, JoinWalletPage, BackupWarningPage, @@ -178,7 +180,7 @@ export function providersComponents() { @NgModule({ declarations: declarationsComponents(), imports: [ - IonicModule.forRoot(CopayApp, { + IonicModule.forRoot(CopayApp, { tabsHideOnSubPages: true, modalEnter: 'modal-slide-in', modalLeave: 'modal-slide-out', diff --git a/src/pages/copayers/copayers.html b/src/pages/copayers/copayers.html new file mode 100644 index 000000000..a449cb91f --- /dev/null +++ b/src/pages/copayers/copayers.html @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/src/pages/copayers/copayers.scss b/src/pages/copayers/copayers.scss new file mode 100644 index 000000000..e69de29bb diff --git a/src/pages/copayers/copayers.ts b/src/pages/copayers/copayers.ts new file mode 100644 index 000000000..7220a8f4a --- /dev/null +++ b/src/pages/copayers/copayers.ts @@ -0,0 +1,13 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'page-copayers', + templateUrl: 'copayers.html', +}) +export class CopayersPage { + + constructor( + ) { + } + +} diff --git a/src/pages/receive/receive.html b/src/pages/receive/receive.html index cb3f6708e..bdfa870cd 100644 --- a/src/pages/receive/receive.html +++ b/src/pages/receive/receive.html @@ -1,27 +1,52 @@ - {{ "Receive" | translate }} + {{'Receive'|translate}} + + + - -
+ +
+
+ + Wallet not backed up + +
+
+
{{ address }}
+ Request Specific amount + +
+
+
+ Incomplete wallet +
+
+ All signing devices must be added to this multisig wallet before bitcoin addresses can be created. +
+ +
+
+ +
+
{{wallet.name}}
+
+
diff --git a/src/pages/receive/receive.scss b/src/pages/receive/receive.scss index 37fac2d95..69b93933e 100644 --- a/src/pages/receive/receive.scss +++ b/src/pages/receive/receive.scss @@ -1,5 +1,27 @@ page-receive { $v-text-accent-color: #1abb9b; + .incomplete-wallet-container { + .title { + padding-top: 10%; + font-size: 25px; + color: #444; + text-align: center; + } + .subtitle { + padding: 20px; + color: #444; + margin-top: 10%; + text-align: center; + } + } + .needs-backup-container { + .backup { + cursor: pointer; + background-color: orange; + color: #fff; + padding: 5px 0; + } + } .qr-container { text-align: center; margin: auto; @@ -22,4 +44,47 @@ page-receive { color: $v-text-accent-color; } } + .wallets-container { + position: absolute; + bottom: 0rem; + width: 100%; + border-top: solid 1px rgb(242, 242, 242); + min-height: 78px; + display: flex; + align-items: center; + cursor: pointer; + img.icon-wallet { + width: 35px !important; + background-color: #647ce8; + } + .wallet-text { + padding-left: 1rem; + } + } + } + + @mixin wallets-list { + height: 4rem; + width: 4rem; + content: " "; + position: absolute; + border-radius: 3px; + box-shadow: 0px 6px 12px 0px rgba(0, 0, 0, 0.3); + } + + .wallets::before { + @include wallets-list; + background: #647ce8 url('../assets/img/icon-wallet.svg') no-repeat 0px 0px; + } + + .action-sheet-container { + .action-sheet-button { + display: flex; + justify-content: baseline; + align-items: center; + padding: 2.5rem; + .button-inner { + padding-left: 5rem; + } + } } diff --git a/src/pages/receive/receive.ts b/src/pages/receive/receive.ts index 7f9e6a6f7..60b297bb1 100644 --- a/src/pages/receive/receive.ts +++ b/src/pages/receive/receive.ts @@ -1,8 +1,18 @@ import { Component } from '@angular/core'; -import { NavController, NavParams } from 'ionic-angular'; +import { NavController, NavParams, Events, ActionSheetController, ModalController } from 'ionic-angular'; + +//native +import { SocialSharing } from '@ionic-native/social-sharing'; + +//pages import { AmountPage } from '../send/amount/amount'; +import { CopayersPage } from '../copayers/copayers'; +import { BackupWarningModalPage } from '../backup/backup-warning-modal/backup-warning-modal'; +//providers import { WalletProvider } from '../../providers/wallet/wallet'; import { ProfileProvider } from '../../providers/profile/profile'; +import { PopupProvider } from '../../providers/popup/popup'; +import { PlatformProvider } from '../../providers/platform/platform'; import * as _ from 'lodash'; @@ -17,12 +27,19 @@ export class ReceivePage { public qrAddress: string; public wallets: any; public wallet: any; + public showShareButton: boolean; constructor( private navCtrl: NavController, private navParams: NavParams, private profileProvider: ProfileProvider, - private walletProvider: WalletProvider + private walletProvider: WalletProvider, + private popupProvider: PopupProvider, + private platformProvider: PlatformProvider, + private events: Events, + private actionSheetCtrl: ActionSheetController, + private socialSharing: SocialSharing, + private modalCtrl: ModalController ) { } @@ -34,6 +51,15 @@ export class ReceivePage { this.wallets = this.profileProvider.getWallets(); this.updateQrAddress(); this.onSelect(this.checkSelectedWallet(this.wallet, this.wallets)); + this.showShareButton = this.platformProvider.isCordova; + this.events.subscribe('bwsEvent', (e, walletId, type, n) => { + // Update current address + if (this.wallet && walletId == this.wallet.id && type == 'NewIncomingTx') this.setAddress(true); + }); + } + + ionViewWillLeave() { + this.events.unsubscribe('bwsEvent'); } private onSelect(wallet: any): any { @@ -45,7 +71,7 @@ export class ReceivePage { } private setProtocolHandler(): void { - this.protocolHandler = this.walletProvider.getProtocolHandler(this.wallet); + this.protocolHandler = this.walletProvider.getProtocolHandler(this.wallet.coin); } private checkSelectedWallet(wallet: any, wallets: any): any { @@ -62,11 +88,14 @@ export class ReceivePage { } private setAddress(newAddr?: boolean): void { + this.walletProvider.getAddress(this.wallet, newAddr).then((addr) => { this.address = addr; this.updateQrAddress(); }).catch((err) => { - console.log(err); + if (err) { + this.popupProvider.ionicAlert(err); + } }); } @@ -74,4 +103,46 @@ export class ReceivePage { this.qrAddress = this.protocolHandler + ":" + this.address; } + public shareAddress(): void { + let protocol = 'bitcoin'; + if (this.wallet.coin == 'bch') protocol += 'cash'; + this.socialSharing.share(protocol + ':' + this.address); + } + + public showWallets(): void { + let buttons: Array = []; + let coinClass: string = "wallets"; + + this.wallets.forEach((wallet, index) => { + + let walletButton: Object = { + text: wallet.credentials.walletName, + cssClass: coinClass, + handler: () => { + this.onSelect(wallet); + } + } + buttons.push(walletButton); + }); + + const actionSheet = this.actionSheetCtrl.create({ + title: 'Select a wallet', + buttons: buttons + }); + + actionSheet.present(); + } + + public goCopayers(): void { + this.navCtrl.push(CopayersPage, { walletId: this.wallet.credentials.walletId }); + }; + + public openBackupNeededModal(): void { + const myModal = this.modalCtrl.create(BackupWarningModalPage, {}, { + showBackdrop: true, + enableBackdropDismiss: true, + }); + myModal.present(); + } + } diff --git a/src/providers/wallet/wallet.spec.ts b/src/providers/wallet/wallet.spec.ts new file mode 100644 index 000000000..f44d029a4 --- /dev/null +++ b/src/providers/wallet/wallet.spec.ts @@ -0,0 +1,117 @@ +import { TestBed, async } from '@angular/core/testing'; +import { HttpModule } from '@angular/http'; +import { ConfigProvider } from '../config/config'; +import { WalletProvider } from './wallet'; +import { Logger, Level as LoggerLevel } from '@nsalaun/ng-logger'; +import { BwcProvider } from '../bwc/bwc'; +import { TxFormatProvider } from '../tx-format/tx-format'; +import { PersistenceProvider } from '../persistence/persistence'; +import { BwcErrorProvider } from '../bwc-error/bwc-error'; +import { RateProvider } from '../rate/rate'; +import { Filter } from '../filter/filter'; +import { PopupProvider } from '../popup/popup'; +import { OnGoingProcess } from '../on-going-process/on-going-process'; +import { TouchIdProvider } from '../touchid/touchid'; + +describe('Provider: Wallet Provider', () => { + let walletProvider: WalletProvider; + + class BwcProviderMock { + constructor() { + }; + getErrors() { + return "error"; + } + } + + class PersistenceProviderMock { + constructor() { + }; + getLastAddress(walletId: any) { + return Promise.resolve('storedAddress'); + } + storeLastAddress(walletId: any, address: any) { + return Promise.resolve(address); + } + } + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [HttpModule], + providers: [ + WalletProvider, + { provide: ConfigProvider }, + { provide: PersistenceProvider, useClass: PersistenceProviderMock }, + { provide: Logger, useValue: new Logger(LoggerLevel.DEBUG) }, + { provide: TxFormatProvider }, + { provide: BwcProvider, useClass: BwcProviderMock }, + { provide: BwcErrorProvider }, + { provide: RateProvider }, + { provide: Filter }, + { provide: PopupProvider }, + { provide: OnGoingProcess }, + { provide: TouchIdProvider }, + ], + }); + walletProvider = TestBed.get(WalletProvider); + }); + + describe('Function: Get Address Function', () => { + + it('should get the last address stored', () => { + let wallet = { + isComplete: function () { + return true; + } + }; + let force = false; + walletProvider.getAddress(wallet, force).then((address) => { + expect(address).toEqual('storedAddress'); + }); + }) + + it('should reject to generate new address if wallet is not complete', () => { + let wallet = { + isComplete: function () { + return false; + } + }; + let force = true; + walletProvider.getAddress(wallet, force).catch((err) => { + expect(err).toEqual('WALLET_NOT_COMPLETE'); + }); + }) + + it('should force to generate new address', () => { + let wallet = { + isComplete: function () { + return true; + }, + createAddress: function ({ }, cb) { + return cb(null, { address: 'newAddress' }); + } + }; + let force = true; + walletProvider.getAddress(wallet, force).then((address) => { + expect(address).toEqual('newAddress'); + }); + }) + + }); + + describe('Function: Get Protocol Handler Function', () => { + + it('should return bitcoincash if coin is bch', () => { + let coin = 'bch'; + let protocol = walletProvider.getProtocolHandler(coin); + expect(protocol).toEqual('bitcoincash'); + }) + + it('should return bitcoin if coin is btc', () => { + let coin = 'btc'; + let protocol = walletProvider.getProtocolHandler(coin); + expect(protocol).toEqual('bitcoin'); + }) + + }); +}); \ No newline at end of file diff --git a/src/providers/wallet/wallet.ts b/src/providers/wallet/wallet.ts index 26794dbc7..ed146b916 100644 --- a/src/providers/wallet/wallet.ts +++ b/src/providers/wallet/wallet.ts @@ -1252,8 +1252,8 @@ export class WalletProvider { }); }; - public getProtocolHandler(wallet: any): string { - if (wallet.coin == 'bch') return 'bitcoincash'; + public getProtocolHandler(coin: string): string { + if (coin == 'bch') return 'bitcoincash'; else return 'bitcoin'; }