diff --git a/package.json b/package.json index 739570e65e265d08348442c614b35e7ab99828d9..cb5d2eeea1dfd488c8b0e011ca9a6b7e572c74d8 100644 --- a/package.json +++ b/package.json @@ -29,24 +29,25 @@ "@angular/platform-browser": "^14.2.0", "@angular/platform-browser-dynamic": "^14.2.0", "@angular/router": "^14.2.0", + "@capacitor-community/sqlite": "^4.0.1", "@capacitor/app": "4.0.1", "@capacitor/clipboard": "^4.0.1", "@capacitor/core": "4.0.1", "@capacitor/haptics": "4.0.1", "@capacitor/keyboard": "4.0.1", "@capacitor/status-bar": "4.0.1", - "@capacitor-community/sqlite": "^4.0.1", "@ionic/angular": "^6.2.4", "@ionic/storage-angular": "^3.0.6", "@ngx-translate/core": "^14.0.0", "@ngx-translate/http-loader": "^7.0.0", "@polkadot/api": "^9.2.4", - "@polkadot/util": "^10.1.6", - "@polkadot/util-crypto": "^10.1.6", "@polkadot/keyring": "^10.1.6", "@polkadot/networks": "^10.1.6", "@polkadot/ui-keyring": "^2.9.7", "@polkadot/ui-settings": "^2.9.7", + "@polkadot/util": "^10.1.6", + "@polkadot/util-crypto": "^10.1.6", + "angular2-qrcode": "^2.0.3", "crypto-browserify": "^3.12.0", "jdenticon": "^3.1.1", "moment": "^2.29.3", diff --git a/src/app/home/home.page.ts b/src/app/home/home.page.ts index 6d53756b942b4e073aa3f2ddf5878baaa683e79e..f11e7e912d4797a6e07ccc04237cdb9d22b0430c 100644 --- a/src/app/home/home.page.ts +++ b/src/app/home/home.page.ts @@ -76,7 +76,7 @@ export class HomePage extends BasePage<Settings> implements OnInit { if (data?.address) { this.defaultAccount = data; - setTimeout(() => this.router.navigateByUrl('/wallet/' + data.address)); + setTimeout(() => this.router.navigate(['/wallet', data.address])); } } diff --git a/src/app/settings/settings.service.ts b/src/app/settings/settings.service.ts index 7c8e40fa2ade5022dcdc8f298cbdf3a39f6d707b..b862e323844f93cd2309524a2adbb9c64c2827b4 100644 --- a/src/app/settings/settings.service.ts +++ b/src/app/settings/settings.service.ts @@ -35,10 +35,15 @@ export class SettingsService extends StartableService<Settings> { protected async ngOnStart(): Promise<Settings> { - this._mobile = this.ionicPlatform.is('mobile'); + this._mobile = this.ionicPlatform.is('mobile') + || this.ionicPlatform.is('iphone') + || this.ionicPlatform.is('android'); const data = await this.restoreLocally(); + console.info('[settings-restore] Mobile: ', this._mobile); + console.info('[settings-restore] Settings ready: ', data); + return data; } diff --git a/src/app/shared/pages/base.page.ts b/src/app/shared/pages/base.page.ts index 3fd1a33429ad69cd434a704b9d2e50cdf39bb35e..1c1b7858580bd6c43dad73cdb1e8708d7bbbecf2 100644 --- a/src/app/shared/pages/base.page.ts +++ b/src/app/shared/pages/base.page.ts @@ -31,6 +31,7 @@ export abstract class BasePage< protected readonly _debug = !environment.production; protected readonly _logPrefix: string; protected readonly _options: O; + protected _presentingElement: Element = null; mobile: boolean = null; error: string = null; @@ -57,11 +58,13 @@ export abstract class BasePage< ...options }; this._logPrefix = `[${this._options.name}] `; - } ngOnInit() { + // Get modal presenting element (iOS only) + this._presentingElement = this.mobile ? document.querySelector('.ion-page') : null; + // Load data setTimeout(() => this.load(), this._options.loadDueTime || 0); } diff --git a/src/app/shared/shared.module.ts b/src/app/shared/shared.module.ts index bcff8e47b0221bcde5c3e12dea7235165288b3a7..9043ba7f671e07ace2ad75627a4c051effec9299 100644 --- a/src/app/shared/shared.module.ts +++ b/src/app/shared/shared.module.ts @@ -6,6 +6,7 @@ import {IonicModule} from '@ionic/angular'; import {TranslateModule} from "@ngx-translate/core"; import {SharedPipesModule} from "@app/shared/pipes/pipes.module"; import {BrowserAnimationsModule} from "@angular/platform-browser/animations"; +import {QRCodeModule} from "angular2-qrcode"; @NgModule({ imports: [ @@ -14,6 +15,7 @@ import {BrowserAnimationsModule} from "@angular/platform-browser/animations"; ReactiveFormsModule, IonicModule, TranslateModule, + QRCodeModule, // App modules SharedPipesModule @@ -24,6 +26,7 @@ import {BrowserAnimationsModule} from "@angular/platform-browser/animations"; ReactiveFormsModule, IonicModule, TranslateModule, + QRCodeModule, // App modules SharedPipesModule diff --git a/src/app/transfer/transfer.page.html b/src/app/transfer/transfer.page.html index 5adabc5b2bea7e6b1a6f26845c82d23a3a46f217..4375d0c70d75c645977dd809eba10258cd08e118 100644 --- a/src/app/transfer/transfer.page.html +++ b/src/app/transfer/transfer.page.html @@ -2,6 +2,7 @@ <ion-toolbar color="primary"> <ion-buttons slot="start"> <ion-menu-button></ion-menu-button> + <ion-back-button></ion-back-button> </ion-buttons> <ion-title translate>TRANSFER.TITLE</ion-title> </ion-toolbar> @@ -27,9 +28,9 @@ <ion-item> <ion-label color="medium" translate>TRANSFER.TO</ion-label> <ion-input *ngIf="data|async|isNotEmptyArray; else inputSkeleton" - [(ngModel)]="recipient.address" (ionFocus)="modal.present()"> + [(ngModel)]="recipient.address" (ionFocus)="!wotModal.isOpen && wotModal.present()"> </ion-input> - <ion-button slot="end" fill="clear" id="open-modal-trigger" + <ion-button slot="end" fill="clear" (click)="!wotModal.isOpen && wotModal.present()" [disabled]="loading" [title]="'COMMON.BTN_SEARCH'|translate"> <ion-icon slot="icon-only" name="search"></ion-icon> @@ -77,9 +78,9 @@ </ion-item> <ion-item lines="none"> - <ion-toggle [(ngModel)]="showComment"> - </ion-toggle> - <ion-label class="ion-text-start" color="medium" translate>TRANSFER.BTN_ADD_COMMENT</ion-label> +<!-- <ion-toggle [(ngModel)]="showComment">--> +<!-- </ion-toggle>--> +<!-- <ion-label class="ion-text-start" color="medium" translate>TRANSFER.BTN_ADD_COMMENT</ion-label>--> <ion-badge slot="end" *ngIf="amount && fee">{{'TRANSFER.FEE'|translate:{fee: fee, currency: currency.symbol} }}</ion-badge> </ion-item> @@ -91,38 +92,37 @@ </ion-list> <div class="ion-text-center" *ngIf="!mobile"> - <ion-button (click)="cancel($event)" color="light" [disabled]="loading"> - <ion-label translate>COMMON.BTN_CANCEL</ion-label> - </ion-button> - <ion-button (click)="submit($event)" [disabled]="loading||invalid"> - <ion-icon slot="start" name="paper-plane"></ion-icon> - <ion-label translate>TRANSFER.BTN_SEND</ion-label> - </ion-button> + <ng-container *ngTemplateOutlet="buttons"></ng-container> </div> </div> + <ion-modal - #modal + #wotModal trigger="open-modal-trigger" - [isOpen]="false" [initialBreakpoint]="0.25" [breakpoints]="[0.25, 0.5, 0.75]" backdropDismiss="true" [backdropBreakpoint]="0.5" + [canDismiss]="!!recipient?.address" > <ng-template> <app-wot-lookup [showToolbar]="false" [showSearchBar]="true" [showItemActions]="false" - (itemClick)="setRecipient($event) || modal.dismiss()" - (searchClick)="modal.setCurrentBreakpoint(0.75)"> + (itemClick)="setRecipient($event) || wotModal.dismiss()" + (searchClick)="wotModal.setCurrentBreakpoint(0.75)"> </app-wot-lookup> </ng-template> </ion-modal> </ion-content> -<ion-footer *ngIf="mobile"> +<ion-footer *ngIf="mobile" class="ion-text-center"> + <ng-container *ngTemplateOutlet="buttons"></ng-container> +</ion-footer> + +<ng-template #buttons> <ion-button (click)="cancel($event)" color="light" [disabled]="loading"> <ion-label translate>COMMON.BTN_CANCEL</ion-label> </ion-button> @@ -130,8 +130,40 @@ <ion-icon slot="start" name="paper-plane"></ion-icon> <ion-label translate>TRANSFER.BTN_SEND</ion-label> </ion-button> -</ion-footer> + <ion-button (click)="qrCodeModal.present()" + [disabled]="loading||invalid"> + <ion-icon slot="start" name="qr-code"></ion-icon> + <ion-label translate>TRANSFER.BTN_QR_CODE</ion-label> + </ion-button> +</ng-template> <ng-template #inputSkeleton> <ion-skeleton-text [animated]="true" ></ion-skeleton-text> </ng-template> + +<ion-modal + #qrCodeModal + [backdropDismiss]="true" + [presentingElement]="_presentingElement" +> + <ng-template> + <ion-content> + <ion-header> + <ion-toolbar *ngIf="mobile"> + <ion-buttons slot="end"> + <ion-button (click)="qrCodeModal.dismiss()" translate>COMMON.BTN_CLOSE</ion-button> + </ion-buttons> + </ion-toolbar> + </ion-header> + <qr-code [value]="paymentData" [size]="250"></qr-code> + <ion-item> + <ion-icon slot="start" name="help-circle" color="tertiary"></ion-icon> + <ion-label class="ion-text-wrap" translate>TRANSFER.QR_CODE_HELP</ion-label> + </ion-item> + <ion-item> + <ion-icon slot="start" name="warning" color="danger" lines="none"></ion-icon> + <ion-label class="ion-text-wrap" [innerHTML]="'INFO.FEATURES_NOT_IMPLEMENTED'|translate"></ion-label> + </ion-item> + </ion-content> + </ng-template> +</ion-modal> diff --git a/src/app/transfer/transfer.page.ts b/src/app/transfer/transfer.page.ts index 36ea83e5afff9519dda63ef95a589e765069f615..c1cff295a301a042f0ee9f3f87ffb05be641bf26 100644 --- a/src/app/transfer/transfer.page.ts +++ b/src/app/transfer/transfer.page.ts @@ -40,7 +40,8 @@ export class TransferPage extends BasePage<Observable<Account[]>> implements OnI reference: 'event' }; - @ViewChild('modal') modal: IonModal; + @ViewChild('wotModal') wotModal: IonModal; + @ViewChild('qrCodeModal') qrCodeModal: IonModal; get balance(): number { if (!this.issuer?.data) return undefined; @@ -61,6 +62,10 @@ export class TransferPage extends BasePage<Observable<Account[]>> implements OnI return !this.valid; } + get paymentData(): string { + return 'TODO'; + } + constructor( injector: Injector, protected accountService: AccountService, @@ -76,8 +81,11 @@ export class TransferPage extends BasePage<Observable<Account[]>> implements OnI ngOnDestroy() { super.ngOnDestroy(); - if (this.modal.isOpen) { - this.modal.dismiss(); + if (this.wotModal?.isOpen) { + this.wotModal.dismiss(); + } + if (this.qrCodeModal?.isOpen) { + this.qrCodeModal.dismiss(); } } @@ -157,7 +165,13 @@ export class TransferPage extends BasePage<Observable<Account[]>> implements OnI this.issuer = null; this.recipient = {address: null, meta: null}; this.amount = null; - this.markAsLoaded(); + if (this.wotModal?.isOpen) { + this.wotModal.dismiss(); + } + if (this.qrCodeModal?.isOpen) { + this.qrCodeModal.dismiss(); + } + this.markAsLoaded({emitEvent: false}); this.markForCheck(); } } diff --git a/src/app/wallet/wallet.page.html b/src/app/wallet/wallet.page.html index 1762ca4cb38c6947bfab17a1fa50e57e8b0052f5..d77da587ef70f22fdd4e377a3d9506e88ea8bbff 100644 --- a/src/app/wallet/wallet.page.html +++ b/src/app/wallet/wallet.page.html @@ -26,16 +26,15 @@ <ion-header> <ion-toolbar class="ion-text-end" color="secondary"> <ion-buttons slot="start"> - <ion-button *ngIf="loaded" (click)="qrCodeModal.present()"> + <ion-button [disabled]="loading" + (click)="qrCodeModal.present()"> <ion-icon slot="icon-only" name="qr-code"></ion-icon> </ion-button> </ion-buttons> - <ion-title size="large" *ngIf="loaded; else loadingText"> - {{ balance | amountFormat }} - </ion-title> - <ng-template #loadingText> - <ion-title translate>COMMON.LOADING</ion-title> - </ng-template> + <ion-label slot="end" + *ngIf="loaded" class="ion-text-end"> + <b>{{ balance | amountFormat }}</b> + </ion-label> </ion-toolbar> </ion-header> @@ -47,7 +46,7 @@ <ion-label color="danger">{{error|translate}}</ion-label> </ion-item> - <ion-item> + <ion-item *ngIf="!mobile"> <ion-icon slot="start" name="key"></ion-icon> <ion-label color="medium" translate>COMMON.PUBKEY</ion-label> <ion-input *ngIf="loaded; else skeleton60" @@ -62,6 +61,10 @@ <ion-icon slot="icon-only" name="copy"></ion-icon> </ion-button> </ion-item> + <ion-item *ngIf="mobile" (click)="copyAddress()" > + <ion-icon slot="start" name="key"></ion-icon> + <ion-label class="ion-text-wrap">{{data?.address}}</ion-label> + </ion-item> <!-- <ion-item detail [routerLink]="['/wot/cert/', data?.address]">--> <!-- <ion-icon slot="start" name="ribbon"></ion-icon>--> @@ -69,7 +72,9 @@ <!-- <ion-badge color="success">{{(data?.data.free || 0) | amountFormat}} {{currency}}</ion-badge>--> <!-- </ion-item>--> - <ion-item detail [routerLink]="['/wot/cert/', data?.address]"> +<!-- <ion-item detail --> +<!-- [routerLink]="['/wot/cert/', data?.address]">--> + <ion-item detail (click)="notImplementedModal.present()"> <ion-icon slot="start" name="ribbon"></ion-icon> <ion-label translate>ACCOUNT.CERTIFICATION_COUNT</ion-label> <ion-badge color="success">0</ion-badge> @@ -77,7 +82,7 @@ </ion-list> - <div class="ion-text-center"> + <div class="ion-text-center ion-padding-top"> <ion-button [routerLink]="['/transfer', 'from', data?.address]" [disabled]="loading"> <ion-icon slot="start" name="paper-plane"></ion-icon> @@ -93,8 +98,7 @@ <ion-modal #authModal - [backdropDismiss]="false" -> + [backdropDismiss]="false"> <ng-template> <ion-content scrollY="false"> <app-auth-modal></app-auth-modal> @@ -106,10 +110,50 @@ <ion-modal #qrCodeModal [backdropDismiss]="true" + [presentingElement]="_presentingElement" > <ng-template> - <ion-content scrollY="false"> - QRCODE + <ion-content [scrollY]="mobile"> + <ion-header> + <ion-toolbar> + <ion-title translate>COMMON.PUBKEY</ion-title> + <ion-buttons slot="end"> + <ion-button (click)="qrCodeModal.dismiss()" translate>COMMON.BTN_CLOSE</ion-button> + </ion-buttons> + </ion-toolbar> + </ion-header> + <qr-code [value]="data.address" [size]="250"></qr-code> + + <!-- public key --> + <ion-list> + <ion-item lines="none" (click)="copyAddress()" tappable> + <ion-icon slot="start" name="key"></ion-icon> + <ion-label class="ion-text-wrap">{{data?.address}}</ion-label> + </ion-item> + </ion-list> + </ion-content> + </ng-template> +</ion-modal> + + + +<ion-modal + #notImplementedModal + [backdropDismiss]="true" +> + <ng-template> + <ion-content> + <ion-header> + <ion-toolbar> + <ion-buttons slot="end"> + <ion-button (click)="notImplementedModal.dismiss()" translate>COMMON.BTN_CLOSE</ion-button> + </ion-buttons> + </ion-toolbar> + </ion-header> + <ion-item> + <ion-icon slot="start" name="warning" color="danger" lines="none"></ion-icon> + <ion-label class="ion-text-wrap" [innerHTML]="'INFO.FEATURES_NOT_IMPLEMENTED'|translate"></ion-label> + </ion-item> </ion-content> </ng-template> </ion-modal> diff --git a/src/app/wallet/wallet.page.scss b/src/app/wallet/wallet.page.scss index 281733f194d1a6cca00e20cad7302c7779ffd76b..a8439d84db8836523224fa7e4e1f51d479721ea9 100644 --- a/src/app/wallet/wallet.page.scss +++ b/src/app/wallet/wallet.page.scss @@ -4,3 +4,4 @@ ion-menu-button { #container { } + diff --git a/src/app/wot/wot-details.page.ts b/src/app/wot/wot-details.page.ts index 9baf6caf84d0090592c4bb67a50c0c28c63903fb..21c56a937933e19ccbe0b5ffb2976a61390e03bc 100644 --- a/src/app/wot/wot-details.page.ts +++ b/src/app/wot/wot-details.page.ts @@ -58,13 +58,4 @@ export class WotDetailsPage extends BasePage<Account> implements OnInit { await this.showToast({message: 'INFO.COPY_TO_CLIPBOARD_DONE'}); } - transfer(event?: UIEvent) { - this.router.navigate(['transfer'], { - queryParams: { - address: this.data.address, - name: this.data.meta?.name - } - }); - } - } diff --git a/src/app/wot/wot-lookup.page.html b/src/app/wot/wot-lookup.page.html index 3250d3b6f3d0dacb0c074c9b59c3e01699629de6..a7fdfe0b4d3ca7df68e6423bad4cf418b1724033 100644 --- a/src/app/wot/wot-lookup.page.html +++ b/src/app/wot/wot-lookup.page.html @@ -59,6 +59,7 @@ <ion-icon slot="icon-only" name="paper-plane"></ion-icon> </ion-button> </ion-item> + <ion-item-options *ngIf="mobile && showItemActions"> <ion-item-option (click)="transfer(item)" [title]="'BTN_SEND_MONEY'|translate"> <ion-icon slot="icon-only" name="paper-plane"></ion-icon> diff --git a/src/app/wot/wot-lookup.page.ts b/src/app/wot/wot-lookup.page.ts index 3e481c1257e46259630c508f959474713607158d..1304a9c8a9fdd942c8f7739c4045ccd9aa6f8829 100644 --- a/src/app/wot/wot-lookup.page.ts +++ b/src/app/wot/wot-lookup.page.ts @@ -61,12 +61,7 @@ export class WotLookupPage extends BasePage<Account[]> implements OnInit { } transfer(item: Account) { - this.router.navigate(['transfer'], { - queryParams: { - name: item.meta?.name, - address: item.address - } - }); + this.router.navigate(['/transfer', 'to', item.address]); } click(item: Account) { diff --git a/src/assets/i18n/fr.json b/src/assets/i18n/fr.json index 0fac1639eb425260c77f64b3fdf2d1ee6e7e527b..24cb1c6f8f60d3dd3a71840c98a0d382d1f47078 100644 --- a/src/assets/i18n/fr.json +++ b/src/assets/i18n/fr.json @@ -361,8 +361,8 @@ "NOT_WRITTEN_EXPIRE_IN": "Date limite<br/>de traitement", "EXPIRED": "Expiré", "PSEUDO": "Pseudonyme", - "SIGNED_ON_BLOCK": "Emise au bloc #{{block}}", - "WRITTEN_ON_BLOCK": "Ecrite au bloc #{{block}}", + "SIGNED_ON_BLOCK": "Émise au bloc #{{block}}", + "WRITTEN_ON_BLOCK": "Écrite au bloc #{{block}}", "GENERAL_DIVIDER": "Informations générales", "NOT_MEMBER_ACCOUNT": "Compte simple (non membre)", "NOT_MEMBER_ACCOUNT_HELP": "Il s'agit d'un simple portefeuille, sans demande d'adhésion en attente.", @@ -684,6 +684,8 @@ "REST": "Reste du compte", "REST_TO": "à", "WARN_COMMENT_IS_PUBLIC": "Veuillez noter que <b>les commentaires sont publics</b> (non chiffrés).", + "BTN_QR_CODE": "QR Code", + "QR_CODE_HELP": "Demander au destinataire de flasher ce code. Il pourra alors déposer le paiement plus tard, quand il le souhaite.", "MODAL": { "TITLE": "Virement" } diff --git a/src/theme/_cesium.scss b/src/theme/_cesium.scss index f2523fd1c75831bd970533ede7dcf80dea8b60e5..c662f501375185581aff64b5e7ad79833968b682 100644 --- a/src/theme/_cesium.scss +++ b/src/theme/_cesium.scss @@ -16,7 +16,15 @@ ion-item.error { } } -/* */ -ion-toolbar { - --ion-toolbar-background: var(--ion-color-base); +/* -- QR code -- */ +qr-code { + --size: min(350px, 80vw); + width: 100%; + height: var(--size); + display: block; + text-align: center; + img { + width: var(--size); + height: var(--size); + } } diff --git a/yarn.lock b/yarn.lock index f1b82ad97b6f24fc396b559501fd5af2dbaf8a1b..dd121728d5fd4649dd39a2f7e70b7a5bec41e3ad 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2992,6 +2992,13 @@ ajv@^6.10.0, ajv@^6.12.3, ajv@^6.12.4, ajv@^6.12.5: json-schema-traverse "^0.4.1" uri-js "^4.2.2" +angular2-qrcode@^2.0.3: + version "2.0.3" + resolved "https://registry.npmjs.org/angular2-qrcode/-/angular2-qrcode-2.0.3.tgz#8a44d8035bc7d5bdc2e53473877d8378912f4db7" + integrity sha512-blXNY7VGSuxxfyAg0aa7urWFkoIT2YrygMzyWNRhzPY7UN1ddM4MWAZSzCEFVTzIIu9x4bZcpYDKG7JnmM9IFg== + dependencies: + qrious "^4.0.2" + ansi-colors@4.1.3: version "4.1.3" resolved "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz#37611340eb2243e70cc604cad35d63270d48781b" @@ -8142,6 +8149,11 @@ qjobs@^1.2.0: resolved "https://registry.npmjs.org/qjobs/-/qjobs-1.2.0.tgz" integrity sha512-8YOJEHtxpySA3fFDyCRxA+UUV+fA+rTWnuWvylOK/NCjhY+b4ocCtmu8TtsWb+mYeU+GCHf/S66KZF/AsteKHg== +qrious@^4.0.2: + version "4.0.2" + resolved "https://registry.npmjs.org/qrious/-/qrious-4.0.2.tgz#09c4d4079d2b961617f62c69cff3b9bb66a39693" + integrity sha512-xWPJIrK1zu5Ypn898fBp8RHkT/9ibquV2Kv24S/JY9VYEhMBMKur1gHVsOiNUh7PHP9uCgejjpZUHUIXXKoU/g== + qs@6.10.3: version "6.10.3" resolved "https://registry.npmjs.org/qs/-/qs-6.10.3.tgz"