From 735e91b7d8063542f2f3598fa64c6c3706a837aa Mon Sep 17 00:00:00 2001 From: sultanmyrza Date: Mon, 6 Jun 2022 20:50:06 +0800 Subject: [PATCH 01/41] fix(share.service.ts): revert to previous functionality --- src/app/features/home/details/details.page.ts | 2 +- src/app/shared/share/share.service.ts | 11 ++--------- 2 files changed, 3 insertions(+), 10 deletions(-) diff --git a/src/app/features/home/details/details.page.ts b/src/app/features/home/details/details.page.ts index 93acb15f0..4da9d097b 100644 --- a/src/app/features/home/details/details.page.ts +++ b/src/app/features/home/details/details.page.ts @@ -302,7 +302,7 @@ export class DetailsPage { const result = await this.confirmAlert.present({ message: this.translocoService.translate( - 'message.assetBecomePublicFor24Hours' + 'message.assetBecomePublicAfterSharing' ) + '!', }); if (result) { diff --git a/src/app/shared/share/share.service.ts b/src/app/shared/share/share.service.ts index b087b744d..8ecbc52d2 100644 --- a/src/app/shared/share/share.service.ts +++ b/src/app/shared/share/share.service.ts @@ -2,10 +2,7 @@ import { Injectable } from '@angular/core'; import { Share } from '@capacitor/share'; import { TranslocoService } from '@ngneat/transloco'; import { catchError } from 'rxjs/operators'; -import { - getAssetProfileUrl, - getAssetProfileUrlWithTmpToken, -} from '../../utils/url'; +import { getAssetProfileUrl } from '../../utils/url'; import { DiaBackendAsset, DiaBackendAssetRepository, @@ -25,13 +22,9 @@ export class ShareService { ) {} async share(asset: DiaBackendAsset) { - const tmpToken = await this.diaBackendAssetRepository - .createTemporaryShareToken$(asset.id) - .toPromise(); - return Share.share({ text: this.defaultShareText, - url: getAssetProfileUrlWithTmpToken(asset.id, tmpToken), + url: await this.setPublicAndGetLink(asset), }); } From 8618d65f72efa93a8c7d6ddcd6f9a6a9f8dbafd1 Mon Sep 17 00:00:00 2001 From: sultanmyrza Date: Mon, 6 Jun 2022 20:52:52 +0800 Subject: [PATCH 02/41] fix(share.service): use existing function to generate asset profile url --- src/app/features/home/details/details.page.ts | 4 ++-- src/app/utils/url.ts | 7 ------- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/src/app/features/home/details/details.page.ts b/src/app/features/home/details/details.page.ts index 4da9d097b..7403f8762 100644 --- a/src/app/features/home/details/details.page.ts +++ b/src/app/features/home/details/details.page.ts @@ -38,7 +38,7 @@ import { switchTap, VOID$, } from '../../../utils/rx-operators/rx-operators'; -import { getAssetProfileUrlWithTmpToken } from '../../../utils/url'; +import { getAssetProfileUrl } from '../../../utils/url'; import { DetailedCapture, InformationSessionService, @@ -484,7 +484,7 @@ export class DetailsPage { concatMap(([detailedCapture, tmpShareToken]) => defer(() => Browser.open({ - url: getAssetProfileUrlWithTmpToken( + url: getAssetProfileUrl( // eslint-disable-next-line @typescript-eslint/no-non-null-assertion detailedCapture.id!, tmpShareToken diff --git a/src/app/utils/url.ts b/src/app/utils/url.ts index b4b7bd9ff..d6ad28bad 100644 --- a/src/app/utils/url.ts +++ b/src/app/utils/url.ts @@ -10,10 +10,3 @@ export function getAssetProfileUrl(id: string, token?: string) { } return `https://authmedia.net/asset-profile?cid=${id}`; } - -export function getAssetProfileUrlWithTmpToken(id: string, tmpToken?: string) { - if (tmpToken) { - return `https://authmedia.net/asset-profile?cid=${id}&token=${tmpToken}`; - } - return `https://authmedia.net/asset-profile?cid=${id}`; -} From 89411dea208d6400fa6df7ec24bce63afe8ba6c0 Mon Sep 17 00:00:00 2001 From: sultanmyrza Date: Tue, 7 Jun 2022 15:46:08 +0800 Subject: [PATCH 03/41] build: bump to 0.58.2 --- CHANGELOG.md | 6 ++++++ android/app/build.gradle | 4 ++-- package-lock.json | 4 ++-- package.json | 2 +- 4 files changed, 11 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 92b84a14a..9057f70fc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## 0.58.2 - 2022-06-07 + +### Fixed + +- Fix revert share asset profile url + ## 0.58.1 - 2022-06-02 ### Fixed diff --git a/android/app/build.gradle b/android/app/build.gradle index dcff0e9fd..0f827f946 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -6,8 +6,8 @@ android { applicationId "io.numbersprotocol.capturelite" minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 401 - versionName "0.58.1" + versionCode 402 + versionName "0.58.2" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } buildFeatures { diff --git a/package-lock.json b/package-lock.json index 114fce48f..d3041c73f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "capture-lite", - "version": "0.58.1", + "version": "0.58.2", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "capture-lite", - "version": "0.58.1", + "version": "0.58.2", "dependencies": { "@angular/animations": "^12.2.4", "@angular/cdk": "^12.2.4", diff --git a/package.json b/package.json index 43760a195..219821e8a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "capture-lite", - "version": "0.58.1", + "version": "0.58.2", "author": "numbersprotocol", "homepage": "https://numbersprotocol.io/", "scripts": { From 3f304c42abeba97752565715ae8b7d6d90680a17 Mon Sep 17 00:00:00 2001 From: sultanmyrza Date: Wed, 8 Jun 2022 16:13:07 +0800 Subject: [PATCH 04/41] add dia backend service (for service endpoints) --- .../dia-backend-service.service.spec.ts | 18 +++++ .../service/dia-backend-service.service.ts | 66 +++++++++++++++++++ src/app/utils/date.ts | 7 ++ 3 files changed, 91 insertions(+) create mode 100644 src/app/shared/dia-backend/service/dia-backend-service.service.spec.ts create mode 100644 src/app/shared/dia-backend/service/dia-backend-service.service.ts create mode 100644 src/app/utils/date.ts diff --git a/src/app/shared/dia-backend/service/dia-backend-service.service.spec.ts b/src/app/shared/dia-backend/service/dia-backend-service.service.spec.ts new file mode 100644 index 000000000..e4a288768 --- /dev/null +++ b/src/app/shared/dia-backend/service/dia-backend-service.service.spec.ts @@ -0,0 +1,18 @@ +import { TestBed } from '@angular/core/testing'; +import { SharedTestingModule } from '../../shared-testing.module'; +import { DiaBackendService } from './dia-backend-service.service'; + +describe('DiaBackendService', () => { + let service: DiaBackendService; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [SharedTestingModule], + }); + service = TestBed.inject(DiaBackendService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/src/app/shared/dia-backend/service/dia-backend-service.service.ts b/src/app/shared/dia-backend/service/dia-backend-service.service.ts new file mode 100644 index 000000000..045ea6b68 --- /dev/null +++ b/src/app/shared/dia-backend/service/dia-backend-service.service.ts @@ -0,0 +1,66 @@ +import { HttpClient } from '@angular/common/http'; +import { Injectable } from '@angular/core'; +import { defer } from 'rxjs'; +import { concatMap } from 'rxjs/operators'; +import { calcDaysBetweenDates } from '../../../utils/date'; +import { Tuple } from '../../database/table/table'; +import { PreferenceManager } from '../../preference-manager/preference-manager.service'; +import { DiaBackendAuthService } from '../auth/dia-backend-auth.service'; +import { BASE_URL } from '../secret'; + +@Injectable({ + providedIn: 'root', +}) +export class DiaBackendService { + private readonly preferences = + this.preferenceManager.getPreferences('DiaBackendService'); + + constructor( + private readonly httpClient: HttpClient, + private readonly authService: DiaBackendAuthService, + private readonly preferenceManager: PreferenceManager + ) {} + + appInfo$() { + return defer(() => this.authService.getAuthHeaders()).pipe( + concatMap(headers => + this.httpClient.get( + `${BASE_URL}/api/v3/services/app-info/`, + { headers } + ) + ) + ); + } + + async postponedMoreThanOneDayAgo() { + const timestamp = await this.getAppUpdatePromptTimestamp(); + + const lastPostponedTime = new Date(timestamp); + const currentTime = new Date(Date.now()); + + return calcDaysBetweenDates(currentTime, lastPostponedTime) > 1; + } + + async getAppUpdatePromptTimestamp() { + return this.preferences.getNumber( + PrefKeys.APP_UPDATE_PROMPT_TIMESTPAMP, + 0 // new Date(0) == Thu Jan 01 1970 + ); + } + + async setAppUpdatePromptTimestamp(timestamp: number) { + return this.preferences.setNumber( + PrefKeys.APP_UPDATE_PROMPT_TIMESTPAMP, + timestamp + ); + } +} + +export interface DiaBackendAppInfo extends Tuple { + readonly latest_app_version: string; + readonly update_urgency: 'low' | 'high' | 'critical'; +} + +const enum PrefKeys { + APP_UPDATE_PROMPT_TIMESTPAMP = 'APP_UPDATE_PROMPT_TIMESTPAMP', +} diff --git a/src/app/utils/date.ts b/src/app/utils/date.ts new file mode 100644 index 000000000..4c7559de0 --- /dev/null +++ b/src/app/utils/date.ts @@ -0,0 +1,7 @@ +export function calcDaysBetweenDates(a: Date, b: Date) { + // eslint-disable-next-line @typescript-eslint/no-magic-numbers + const millisecondsInOneDay = 1000 * 60 * 60 * 24; + const diffTime = a.getTime() - b.getTime(); + const diffDays = Math.ceil(diffTime / millisecondsInOneDay); + return diffDays; +} From 5dd39069baea68db98d0d88b871e05f908ecc028 Mon Sep 17 00:00:00 2001 From: sultanmyrza Date: Tue, 7 Jun 2022 18:08:27 +0800 Subject: [PATCH 05/41] add update-app-dialog component --- src/app/features/home/home.module.ts | 2 ++ .../update-app-dialog.component.html | 16 ++++++++++++ .../update-app-dialog.component.scss | 0 .../update-app-dialog.component.spec.ts | 25 +++++++++++++++++++ .../update-app-dialog.component.ts | 18 +++++++++++++ src/app/utils/url.ts | 10 ++++++++ 6 files changed, 71 insertions(+) create mode 100644 src/app/features/home/in-app-updates/update-app-dialog/update-app-dialog.component.html create mode 100644 src/app/features/home/in-app-updates/update-app-dialog/update-app-dialog.component.scss create mode 100644 src/app/features/home/in-app-updates/update-app-dialog/update-app-dialog.component.spec.ts create mode 100644 src/app/features/home/in-app-updates/update-app-dialog/update-app-dialog.component.ts diff --git a/src/app/features/home/home.module.ts b/src/app/features/home/home.module.ts index e54a06671..40961cca2 100644 --- a/src/app/features/home/home.module.ts +++ b/src/app/features/home/home.module.ts @@ -6,6 +6,7 @@ import { CaptureTabComponent } from './capture-tab/capture-tab.component'; import { UploadingBarComponent } from './capture-tab/uploading-bar/uploading-bar.component'; import { HomePageRoutingModule } from './home-routing.module'; import { HomePage } from './home.page'; +import { UpdateAppDialogComponent } from './in-app-updates/update-app-dialog/update-app-dialog.component'; import { PrefetchingDialogComponent } from './onboarding/prefetching-dialog/prefetching-dialog.component'; import { PostCaptureTabComponent } from './post-capture-tab/post-capture-tab.component'; @@ -15,6 +16,7 @@ import { PostCaptureTabComponent } from './post-capture-tab/post-capture-tab.com CaptureTabComponent, PostCaptureTabComponent, PrefetchingDialogComponent, + UpdateAppDialogComponent, UploadingBarComponent, CaptureItemComponent, ], diff --git a/src/app/features/home/in-app-updates/update-app-dialog/update-app-dialog.component.html b/src/app/features/home/in-app-updates/update-app-dialog/update-app-dialog.component.html new file mode 100644 index 000000000..e3e0c2c07 --- /dev/null +++ b/src/app/features/home/in-app-updates/update-app-dialog/update-app-dialog.component.html @@ -0,0 +1,16 @@ + +

{{ 'Critical updates are available' }}

+ +
Please update the app for proper functioning
+
+ +
+
diff --git a/src/app/features/home/in-app-updates/update-app-dialog/update-app-dialog.component.scss b/src/app/features/home/in-app-updates/update-app-dialog/update-app-dialog.component.scss new file mode 100644 index 000000000..e69de29bb diff --git a/src/app/features/home/in-app-updates/update-app-dialog/update-app-dialog.component.spec.ts b/src/app/features/home/in-app-updates/update-app-dialog/update-app-dialog.component.spec.ts new file mode 100644 index 000000000..965f9d029 --- /dev/null +++ b/src/app/features/home/in-app-updates/update-app-dialog/update-app-dialog.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; +import { SharedTestingModule } from '../../../../shared/shared-testing.module'; +import { UpdateAppDialogComponent } from './update-app-dialog.component'; + +describe('UpdateAppDialogComponent', () => { + let component: UpdateAppDialogComponent; + let fixture: ComponentFixture; + + beforeEach( + waitForAsync(() => { + TestBed.configureTestingModule({ + declarations: [UpdateAppDialogComponent], + imports: [SharedTestingModule], + }).compileComponents(); + + fixture = TestBed.createComponent(UpdateAppDialogComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }) + ); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/features/home/in-app-updates/update-app-dialog/update-app-dialog.component.ts b/src/app/features/home/in-app-updates/update-app-dialog/update-app-dialog.component.ts new file mode 100644 index 000000000..f1db7e7d0 --- /dev/null +++ b/src/app/features/home/in-app-updates/update-app-dialog/update-app-dialog.component.ts @@ -0,0 +1,18 @@ +import { Component } from '@angular/core'; +import { Browser } from '@capacitor/browser'; +import { Platform } from '@ionic/angular'; +import { getAppDownloadLink } from '../../../../utils/url'; + +@Component({ + selector: 'app-update-app-dialog', + templateUrl: './update-app-dialog.component.html', + styleUrls: ['./update-app-dialog.component.scss'], +}) +export class UpdateAppDialogComponent { + constructor(private readonly platform: Platform) {} + + async redirectToAppUpdatePage() { + const url = getAppDownloadLink(this.platform.is.bind(this)); + await Browser.open({ url }); + } +} diff --git a/src/app/utils/url.ts b/src/app/utils/url.ts index d6ad28bad..6abbe7aa9 100644 --- a/src/app/utils/url.ts +++ b/src/app/utils/url.ts @@ -10,3 +10,13 @@ export function getAssetProfileUrl(id: string, token?: string) { } return `https://authmedia.net/asset-profile?cid=${id}`; } + +export function getAppDownloadLink(isPlatform: (platformName: any) => boolean) { + if (isPlatform('ios')) + return 'https://apps.apple.com/en/app/capture-app/id1536388009'; + + if (isPlatform('android')) + return 'https://play.google.com/store/apps/details?id=io.numbersprotocol.capturelite'; + + return 'https://www.numbersprotocol.io/#products'; +} From 27081de9ebfd73a7d5d3ad9d5620b603c1748016 Mon Sep 17 00:00:00 2001 From: sultanmyrza Date: Tue, 7 Jun 2022 18:11:03 +0800 Subject: [PATCH 06/41] feat: promp app update if any on app start --- src/app/features/home/home.page.ts | 64 ++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/src/app/features/home/home.page.ts b/src/app/features/home/home.page.ts index 88132ead8..b55400b12 100644 --- a/src/app/features/home/home.page.ts +++ b/src/app/features/home/home.page.ts @@ -1,6 +1,7 @@ import { ChangeDetectorRef, Component } from '@angular/core'; import { MatDialog } from '@angular/material/dialog'; import { ActivatedRoute, Router } from '@angular/router'; +import { App } from '@capacitor/app'; import { Browser } from '@capacitor/browser'; import { ActionSheetController, @@ -24,6 +25,7 @@ import { CaptureService, Media } from '../../shared/capture/capture.service'; import { ConfirmAlert } from '../../shared/confirm-alert/confirm-alert.service'; import { DiaBackendAssetRepository } from '../../shared/dia-backend/asset/dia-backend-asset-repository.service'; import { DiaBackendAuthService } from '../../shared/dia-backend/auth/dia-backend-auth.service'; +import { DiaBackendService } from '../../shared/dia-backend/service/dia-backend-service.service'; import { DiaBackendTransactionRepository } from '../../shared/dia-backend/transaction/dia-backend-transaction-repository.service'; import { DiaBackendWalletService } from '../../shared/dia-backend/wallet/dia-backend-wallet.service'; import { ErrorService } from '../../shared/error/error.service'; @@ -31,7 +33,9 @@ import { MigrationService } from '../../shared/migration/service/migration.servi import { OnboardingService } from '../../shared/onboarding/onboarding.service'; import { UserGuideService } from '../../shared/user-guide/user-guide.service'; import { switchTapTo, VOID$ } from '../../utils/rx-operators/rx-operators'; +import { getAppDownloadLink } from '../../utils/url'; import { GoProBluetoothService } from '../settings/go-pro/services/go-pro-bluetooth.service'; +import { UpdateAppDialogComponent } from './in-app-updates/update-app-dialog/update-app-dialog.component'; import { PrefetchingDialogComponent } from './onboarding/prefetching-dialog/prefetching-dialog.component'; @UntilDestroy() @@ -61,6 +65,7 @@ export class HomePage { private readonly diaBackendAuthService: DiaBackendAuthService, private readonly diaBackendAssetRepository: DiaBackendAssetRepository, private readonly diaBackendTransactionRepository: DiaBackendTransactionRepository, + private readonly diaBackendService: DiaBackendService, private readonly onboardingService: OnboardingService, private readonly router: Router, private readonly captureService: CaptureService, @@ -86,6 +91,7 @@ export class HomePage { .pipe( concatMap(isNewLogin => this.migrationService.migrate$(isNewLogin)), catchError(() => VOID$), + switchTapTo(defer(() => this.promptAppUpdateIfAny())), switchTapTo(defer(() => this.onboardingRedirect())), switchTapTo( defer(() => this.userGuideService.showUserGuidesOnHomePage()) @@ -138,6 +144,64 @@ export class HomePage { await this.onboardingService.setHasPrefetchedDiaBackendAssets(true); } + private async promptAppUpdateIfAny() { + // Not applicable to Web App + if (!this.platform.is('hybrid')) return; + + const backendAppInfo = await this.diaBackendService.appInfo$().toPromise(); + const appInfo = await App.getInfo(); + + const current = appInfo.version; + const latest = backendAppInfo.latest_app_version; + + if (this.isEqualOrGreaterThanLatestVersion(current, latest)) return; + + if (backendAppInfo.update_urgency === 'critical') { + this.dialog.open(UpdateAppDialogComponent, { disableClose: true }); + } + + if ( + backendAppInfo.update_urgency === 'high' && + (await this.diaBackendService.postponedMoreThanOneDayAgo()) + ) { + this.diaBackendService.setAppUpdatePromptTimestamp(Date.now()); + const confirmAppUpdate = await this.showAppUpdateAlert(); + if (confirmAppUpdate) await this.redirectToAppUpdatePage(); + } + } + + // eslint-disable-next-line class-methods-use-this + isEqualOrGreaterThanLatestVersion( + currentVersion: string, + latestVersion: string + ) { + const currentVersionArray = currentVersion.split('.'); + const latestVersionArray = latestVersion.split('.'); + + for (const index in currentVersionArray) { + if (currentVersionArray[index] > latestVersionArray[index]) { + return true; + } else if (currentVersionArray[index] < latestVersionArray[index]) { + return false; + } + } + return true; + } + + private async redirectToAppUpdatePage() { + const url = getAppDownloadLink(this.platform.is.bind(this)); + await Browser.open({ url }); + } + + private async showAppUpdateAlert() { + return this.confirmAlert.present({ + header: 'Importan updates are available', + message: 'Please update app for propper functionig', + confirmButtonText: 'Udate now', + cancelButtonText: 'Remind me tomorrow', + }); + } + private async showPrefetchAlert() { return this.confirmAlert.present({ header: this.translocoService.translate('restorePhotos'), From 9abfd2749827c19a0c3a803176d195e42a73672d Mon Sep 17 00:00:00 2001 From: sultanmyrza Date: Tue, 7 Jun 2022 18:53:04 +0800 Subject: [PATCH 07/41] add in app update translations --- src/app/features/home/home.page.ts | 16 ++++++++++++---- .../update-app-dialog.component.html | 8 +++++--- src/assets/i18n/en-us.json | 7 +++++++ src/assets/i18n/zh-tw.json | 7 +++++++ 4 files changed, 31 insertions(+), 7 deletions(-) diff --git a/src/app/features/home/home.page.ts b/src/app/features/home/home.page.ts index b55400b12..64f54e5e6 100644 --- a/src/app/features/home/home.page.ts +++ b/src/app/features/home/home.page.ts @@ -195,10 +195,18 @@ export class HomePage { private async showAppUpdateAlert() { return this.confirmAlert.present({ - header: 'Importan updates are available', - message: 'Please update app for propper functionig', - confirmButtonText: 'Udate now', - cancelButtonText: 'Remind me tomorrow', + header: this.translocoService.translate( + 'inAppUpdate.importantUpdatesAreAvailable' + ), + message: this.translocoService.translate( + 'inAppUpdate.pleaseUpdateTheAppForProperFunctioning' + ), + confirmButtonText: this.translocoService.translate( + 'inAppUpdate.updateNow' + ), + cancelButtonText: this.translocoService.translate( + 'inAppUpdate.remindMeTomorrow' + ), }); } diff --git a/src/app/features/home/in-app-updates/update-app-dialog/update-app-dialog.component.html b/src/app/features/home/in-app-updates/update-app-dialog/update-app-dialog.component.html index e3e0c2c07..9e52399a4 100644 --- a/src/app/features/home/in-app-updates/update-app-dialog/update-app-dialog.component.html +++ b/src/app/features/home/in-app-updates/update-app-dialog/update-app-dialog.component.html @@ -1,7 +1,9 @@ -

{{ 'Critical updates are available' }}

+

{{ t('inAppUpdate.criticalUpdatesAreAvailable') }}

-
Please update the app for proper functioning
+
+ {{ t('inAppUpdate.pleaseUpdateTheAppForProperFunctioning') }} +
diff --git a/src/assets/i18n/en-us.json b/src/assets/i18n/en-us.json index f16fd3600..d3a2671eb 100644 --- a/src/assets/i18n/en-us.json +++ b/src/assets/i18n/en-us.json @@ -330,5 +330,12 @@ "previous": "Prev", "next": "Next", "okIGotIt": "Ok, I got it" + }, + "inAppUpdate": { + "criticalUpdatesAreAvailable": "Critical updates are available", + "importantUpdatesAreAvailable": "Important updates are available", + "pleaseUpdateTheAppForProperFunctioning": "Please update the app for proper functioning", + "remindMeTomorrow": "REMIND ME TOMORROW", + "updateNow": "UPDATE NOW" } } diff --git a/src/assets/i18n/zh-tw.json b/src/assets/i18n/zh-tw.json index abb6df09f..47d4fc667 100644 --- a/src/assets/i18n/zh-tw.json +++ b/src/assets/i18n/zh-tw.json @@ -330,5 +330,12 @@ "previous": "上一步", "next": "下一步", "okIGotIt": "好,我知道了" + }, + "inAppUpdate": { + "criticalUpdatesAreAvailable": "重要更新可用", + "importantUpdatesAreAvailable": "重要更新可用", + "pleaseUpdateTheAppForProperFunctioning": "請更新應用程序以使其正常運行", + "remindMeTomorrow": "明天提醒我", + "updateNow": "現在更新" } } From 47d22eab8d6e106e13816d9b355c7f6f8f38b930 Mon Sep 17 00:00:00 2001 From: sultanmyrza Date: Thu, 9 Jun 2022 16:57:48 +0800 Subject: [PATCH 08/41] fix(phone-verification.page): specify error messages on phone submit --- .../phone-verification.page.ts | 18 +++++++++++++++++- src/assets/i18n/en-us.json | 2 ++ src/assets/i18n/zh-tw.json | 2 ++ 3 files changed, 21 insertions(+), 1 deletion(-) diff --git a/src/app/features/profile/phone-verification/phone-verification.page.ts b/src/app/features/profile/phone-verification/phone-verification.page.ts index b941c9246..341994c19 100644 --- a/src/app/features/profile/phone-verification/phone-verification.page.ts +++ b/src/app/features/profile/phone-verification/phone-verification.page.ts @@ -127,7 +127,7 @@ export class PhoneVerificationPage { ); const action$ = this.diaBackendAuthService .sendPhoneVerification$(this.phoneNumberModel.phoneNumber) - .pipe(catchError((err: unknown) => this.errorService.toastError$(err))); + .pipe(catchError((err: unknown) => this.handlePhoneSubmitError$(err))); return this.blockingActionService .run$(action$, { message: this.translocoService.translate('message.pleaseWait'), @@ -172,6 +172,22 @@ export class PhoneVerificationPage { .subscribe(); } + private handlePhoneSubmitError$(err: unknown) { + if (err instanceof HttpErrorResponse) { + const errorType = err.error.error?.type; + if ( + errorType === 'validation_error' || + errorType === 'throttled' || + errorType === 'external_api_error' + ) { + return this.errorService.toastError$( + this.translocoService.translate(`error.diaBackend.${errorType}`) + ); + } + } + return this.errorService.toastError$(err); + } + private handleVerificationError$(err: unknown) { if (err instanceof HttpErrorResponse) { const errorType = err.error.error?.type; diff --git a/src/assets/i18n/en-us.json b/src/assets/i18n/en-us.json index f16fd3600..9aea92efa 100644 --- a/src/assets/i18n/en-us.json +++ b/src/assets/i18n/en-us.json @@ -211,6 +211,8 @@ "user_is_not_active": "Account is not active. Please activate the account from the activation email you received.", "incorrect_login_credentials": "Incorrect email or password.", "untrusted_client": "Untrusted client.", + "validation_error": "Enter a valid phone number", + "external_api_error": "External API error", "phone_verification_failed": "Incorrect verification code", "phone_verification_code_expired": "The verification code has been expired", "throttled": "Too frequent request", diff --git a/src/assets/i18n/zh-tw.json b/src/assets/i18n/zh-tw.json index abb6df09f..1e9b510a4 100644 --- a/src/assets/i18n/zh-tw.json +++ b/src/assets/i18n/zh-tw.json @@ -211,6 +211,8 @@ "user_is_not_active": "帳號未啟用。請透過您收到的帳號驗證信啟用帳號。", "incorrect_login_credentials": "不正確的電子郵件或密碼。", "untrusted_client": "不受信任的客戶端。", + "validation_error": "請入有效的電話號碼", + "external_api_error": "外部 API 錯誤", "phone_verification_failed": "驗證碼錯誤", "phone_verification_code_expired": "驗證碼已過期", "throttled": "短時間內傳送過多請求", From 0c13b95c2de45fd75bb8db5a30b5ee74b6f19c28 Mon Sep 17 00:00:00 2001 From: sultanmyrza Date: Thu, 9 Jun 2022 18:05:56 +0800 Subject: [PATCH 09/41] fix translations for `external_api_error` --- src/assets/i18n/en-us.json | 2 +- src/assets/i18n/zh-tw.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/assets/i18n/en-us.json b/src/assets/i18n/en-us.json index 9aea92efa..cb3316a20 100644 --- a/src/assets/i18n/en-us.json +++ b/src/assets/i18n/en-us.json @@ -212,7 +212,7 @@ "incorrect_login_credentials": "Incorrect email or password.", "untrusted_client": "Untrusted client.", "validation_error": "Enter a valid phone number", - "external_api_error": "External API error", + "external_api_error": "An error occurred. Please try again later", "phone_verification_failed": "Incorrect verification code", "phone_verification_code_expired": "The verification code has been expired", "throttled": "Too frequent request", diff --git a/src/assets/i18n/zh-tw.json b/src/assets/i18n/zh-tw.json index 1e9b510a4..04b55af35 100644 --- a/src/assets/i18n/zh-tw.json +++ b/src/assets/i18n/zh-tw.json @@ -212,7 +212,7 @@ "incorrect_login_credentials": "不正確的電子郵件或密碼。", "untrusted_client": "不受信任的客戶端。", "validation_error": "請入有效的電話號碼", - "external_api_error": "外部 API 錯誤", + "external_api_error": "發生錯誤,請稍後再試", "phone_verification_failed": "驗證碼錯誤", "phone_verification_code_expired": "驗證碼已過期", "throttled": "短時間內傳送過多請求", From 50b708ea3f5dc4fb24fd150ca681705c31973001 Mon Sep 17 00:00:00 2001 From: Olga Shen Date: Wed, 15 Jun 2022 12:06:34 +0800 Subject: [PATCH 10/41] fix(.github/workflows): node version 16.15.0 --- .github/workflows/build-apks.yml | 2 +- .github/workflows/build.yml | 8 ++++---- .github/workflows/pre-release.yml | 8 ++++---- .github/workflows/uiux-release.yml | 2 +- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/workflows/build-apks.yml b/.github/workflows/build-apks.yml index 169e9a69b..d4820ccc1 100644 --- a/.github/workflows/build-apks.yml +++ b/.github/workflows/build-apks.yml @@ -40,7 +40,7 @@ jobs: - uses: actions/setup-node@v2 with: - node-version: '16' + node-version: '16.15.0' - name: Check if version has been updated id: version_check diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b558f93fd..10926e241 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -13,7 +13,7 @@ jobs: - name: Use Node.js uses: actions/setup-node@v2 with: - node-version: '16' + node-version: '16.15.0' - name: Install Ionic CLI run: npm install -g @ionic/cli @@ -34,7 +34,7 @@ jobs: - name: Use Node.js uses: actions/setup-node@v2 with: - node-version: '16' + node-version: '16.15.0' - name: Install Ionic CLI run: npm install -g @ionic/cli @@ -63,7 +63,7 @@ jobs: - name: Use Node.js uses: actions/setup-node@v2 with: - node-version: '16' + node-version: '16.15.0' - name: Install Ionic CLI run: npm install -g @ionic/cli @@ -96,7 +96,7 @@ jobs: - name: Use Node.js uses: actions/setup-node@v2 with: - node-version: '16' + node-version: '16.15.0' - name: Install Ionic CLI run: npm install -g @ionic/cli diff --git a/.github/workflows/pre-release.yml b/.github/workflows/pre-release.yml index 02dc7111f..2e2b7e501 100644 --- a/.github/workflows/pre-release.yml +++ b/.github/workflows/pre-release.yml @@ -15,7 +15,7 @@ jobs: - uses: actions/setup-node@v2 with: - node-version: '16' + node-version: '16.15.0' - name: Check if version has been updated id: version_check @@ -75,7 +75,7 @@ jobs: - uses: actions/setup-node@v2 with: - node-version: '16' + node-version: '16.15.0' - name: Check if version has been updated id: version_check @@ -185,7 +185,7 @@ jobs: - uses: actions/setup-node@v2 with: - node-version: '16' + node-version: '16.15.0' - name: Check if version has been updated id: version_check @@ -227,7 +227,7 @@ jobs: - uses: actions/setup-node@v2 with: - node-version: '16' + node-version: '16.15.0' - name: Check if version has been updated id: version_check diff --git a/.github/workflows/uiux-release.yml b/.github/workflows/uiux-release.yml index 4816265f2..8cb6516a9 100644 --- a/.github/workflows/uiux-release.yml +++ b/.github/workflows/uiux-release.yml @@ -13,7 +13,7 @@ jobs: - uses: actions/setup-node@v2 with: - node-version: '16' + node-version: '16.15.0' - name: Build Ionic env: From ab2c1af3757bff7683a89d3f0fe26909548027f8 Mon Sep 17 00:00:00 2001 From: sultanmyrza Date: Tue, 14 Jun 2022 18:51:51 +0800 Subject: [PATCH 11/41] add translations for network actions errors in details page --- src/assets/i18n/en-us.json | 11 +++++++++++ src/assets/i18n/zh-tw.json | 11 +++++++++++ 2 files changed, 22 insertions(+) diff --git a/src/assets/i18n/en-us.json b/src/assets/i18n/en-us.json index d3a2671eb..597310c72 100644 --- a/src/assets/i18n/en-us.json +++ b/src/assets/i18n/en-us.json @@ -254,6 +254,17 @@ "fee": "Gas", "totalCost": "Total Cost" }, + "details": { + "error": { + "transferOwnershipActionIsUnavailable": "Transfer ownership action is unavailable. Please try again later.", + "viewOnCaptureClubIsUnavailable": "View on CaptureClub action is unavailable. Please try again later.", + "viewSupportingVideoOnIpfsIsUnavailable": "View supporting video on IPFS action is unavailable. Please try again later.", + "deregisterFromNetworkIsUnavailable": "Deregister from network action is unavailable. Please try again later.", + "mintNftTokenIsUnavailable": "Mint NFT token action is unavailable. Please try again later.", + "viewBlockchainCertificateIsUnavailable": "View blockchain certificate action is unavailable. Please try again later.", + "networkActionsAreUnavailable": "Network Actions are unavailable. Please try again later." + } + }, "wallets": { "wallets": "Wallets", "pullToRefreshBalance": "Pull to refresh balance", diff --git a/src/assets/i18n/zh-tw.json b/src/assets/i18n/zh-tw.json index 47d4fc667..4797c8eb5 100644 --- a/src/assets/i18n/zh-tw.json +++ b/src/assets/i18n/zh-tw.json @@ -254,6 +254,17 @@ "fee": "油費", "totalCost": "總額" }, + "details": { + "error": { + "transferOwnershipActionIsUnavailable": "轉讓所有權操作不可用。 請稍後再試。", + "viewOnCaptureClubIsUnavailable": "無法查看 CaptureClub 操作。 請稍後再試。", + "viewSupportingVideoOnIpfsIsUnavailable": "查看有關 IPFS 操作的支持視頻不可用。 請稍後再試。", + "deregisterFromNetworkIsUnavailable": "從網絡取消註冊操作不可用。 請稍後再試。", + "mintNftTokenIsUnavailable": "Mint NFT 令牌操作不可用。 請稍後再試。", + "viewBlockchainCertificateIsUnavailable": "查看區塊鏈證書操作不可用。 請稍後再試。", + "networkActionsAreUnavailable": "網絡操作不可用。 請稍後再試。" + } + }, "wallets": { "wallets": "錢包", "pullToRefreshBalance": "下拉以刷新餘額", From 774781b8ae2395a07c5241fb62d8dc7963d25e31 Mon Sep 17 00:00:00 2001 From: sultanmyrza Date: Tue, 14 Jun 2022 20:06:26 +0800 Subject: [PATCH 12/41] fix(details.page.ts): show all option menus anyway --- src/app/features/home/details/details.page.ts | 166 +++++++++++------- 1 file changed, 105 insertions(+), 61 deletions(-) diff --git a/src/app/features/home/details/details.page.ts b/src/app/features/home/details/details.page.ts index 7403f8762..f3fdf4f39 100644 --- a/src/app/features/home/details/details.page.ts +++ b/src/app/features/home/details/details.page.ts @@ -332,12 +332,18 @@ export class DetailsPage { ), this.translocoService.selectTranslateObject({ 'message.transferOwnership': null, + 'details.error.transferOwnershipActionIsUnavailable': null, 'message.viewOnCaptureClub': null, + 'details.error.viewOnCaptureClubIsUnavailable': null, 'message.deregisterFromNetwork': null, 'message.mintNftToken': null, + 'details.error.mintNftTokenIsUnavailable': null, 'message.viewBlockchainCertificate': null, + 'details.error.viewBlockchainCertificateIsUnavailable': null, 'message.viewSupportingVideoOnIpfs': null, + 'details.error.viewSupportingVideoOnIpfsIsUnavailable': null, networkActions: null, + 'details.error.networkActionsAreUnavailable': null, }), ]) .pipe( @@ -349,83 +355,121 @@ export class DetailsPage { postCreationWorkflowCompleted, [ messageTransferOwnership, + messageTransferOwnershipIsUnavailable, messageViewOnCaptureClub, + messageviewOnCaptureClubIsUnavailable, messageDeregisterFromNetwork, messageMintNftToken, + messageMintNftTokenIsUnavailable, messageViewBlockchainCertificate, + messageViewBlockchainCertificateIsUnavailable, messageViewSupportingVideoOnIpfs, + messageviewSupportingVideoOnIpfsIsUnavailable, messageNetworkActions, + messageNetworkActionsAreUnavailable, ], ]) => new Promise(resolve => { const buttons: ActionSheetButton[] = []; - if ( - postCreationWorkflowCompleted && - diaBackendAsset?.supporting_file - ) { - buttons.push({ - text: messageViewSupportingVideoOnIpfs, - handler: () => { - this.openIpfsSupportingVideo(); - }, - }); - } - if (postCreationWorkflowCompleted && detailedCapture.id) { - buttons.push({ - text: messageTransferOwnership, - handler: () => { - this.openContactSelectionDialog(); - resolve(); - }, - }); - } - if (diaBackendAsset?.source_type === 'store') { - buttons.push({ - text: messageViewOnCaptureClub, - handler: () => { - this.openCaptureClub(); - }, - }); - } + buttons.push({ + text: messageViewSupportingVideoOnIpfs, + handler: + postCreationWorkflowCompleted && + diaBackendAsset?.supporting_file + ? () => { + this.openIpfsSupportingVideo(); + } + : () => { + this.errorService + .toastError$( + messageviewSupportingVideoOnIpfsIsUnavailable + ) + .toPromise(); + }, + }); + + buttons.push({ + text: messageTransferOwnership, + handler: + postCreationWorkflowCompleted && detailedCapture.id + ? () => { + this.openContactSelectionDialog(); + resolve(); + } + : () => { + this.errorService + .toastError$(messageTransferOwnershipIsUnavailable) + .toPromise(); + }, + }); + + buttons.push({ + text: messageViewOnCaptureClub, + handler: + diaBackendAsset?.source_type === 'store' + ? () => { + this.openCaptureClub(); + } + : () => { + this.errorService + .toastError$(messageviewOnCaptureClubIsUnavailable) + .toPromise(); + }, + }); buttons.push({ text: messageDeregisterFromNetwork, handler: () => { this.remove().then(() => resolve()); }, }); - if ( - postCreationWorkflowCompleted && - diaBackendAsset?.nft_token_id === null - ) { - buttons.push({ - text: messageMintNftToken, - handler: () => { - this.mintNft().then(() => resolve()); - }, - role: 'destructive', - }); - } - if (postCreationWorkflowCompleted && detailedCapture.id) { - buttons.push({ - text: messageViewBlockchainCertificate, - handler: () => { - this.openCertificate(); - resolve(); - }, - }); - } - if (postCreationWorkflowCompleted) { - buttons.push({ - text: messageNetworkActions, - handler: () => { - this.router.navigate( - ['actions', { id: detailedCapture.id }], - { relativeTo: this.route } - ); - resolve(); - }, - }); - } + + buttons.push({ + text: messageMintNftToken, + handler: + postCreationWorkflowCompleted && + diaBackendAsset?.nft_token_id === null + ? () => { + this.mintNft().then(() => resolve()); + } + : () => { + this.errorService + .toastError$(messageMintNftTokenIsUnavailable) + .toPromise(); + }, + role: 'destructive', + }); + buttons.push({ + text: messageViewBlockchainCertificate, + handler: + postCreationWorkflowCompleted && detailedCapture.id + ? () => { + this.openCertificate(); + resolve(); + } + : () => { + this.errorService + .toastError$( + messageViewBlockchainCertificateIsUnavailable + ) + .toPromise(); + }, + }); + buttons.push({ + text: messageNetworkActions, + handler: postCreationWorkflowCompleted + ? () => { + this.router.navigate( + ['actions', { id: detailedCapture.id }], + { relativeTo: this.route } + ); + resolve(); + } + : () => { + this.errorService + .toastError$(messageNetworkActionsAreUnavailable) + .toPromise(); + }, + }); this.actionSheetController .create({ buttons }) .then(sheet => sheet.present()); From 7d64fbefdb5211be3025de87b2e25c6e94444b25 Mon Sep 17 00:00:00 2001 From: sultanmyrza Date: Mon, 20 Jun 2022 15:55:47 +0800 Subject: [PATCH 13/41] fix(zh-tw.json): translations related to errors in details page --- src/assets/i18n/zh-tw.json | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/assets/i18n/zh-tw.json b/src/assets/i18n/zh-tw.json index 4797c8eb5..ac72da6b3 100644 --- a/src/assets/i18n/zh-tw.json +++ b/src/assets/i18n/zh-tw.json @@ -256,13 +256,13 @@ }, "details": { "error": { - "transferOwnershipActionIsUnavailable": "轉讓所有權操作不可用。 請稍後再試。", - "viewOnCaptureClubIsUnavailable": "無法查看 CaptureClub 操作。 請稍後再試。", - "viewSupportingVideoOnIpfsIsUnavailable": "查看有關 IPFS 操作的支持視頻不可用。 請稍後再試。", - "deregisterFromNetworkIsUnavailable": "從網絡取消註冊操作不可用。 請稍後再試。", - "mintNftTokenIsUnavailable": "Mint NFT 令牌操作不可用。 請稍後再試。", - "viewBlockchainCertificateIsUnavailable": "查看區塊鏈證書操作不可用。 請稍後再試。", - "networkActionsAreUnavailable": "網絡操作不可用。 請稍後再試。" + "transferOwnershipActionIsUnavailable": "暫時無法轉換所有權,請稍後再試。", + "viewOnCaptureClubIsUnavailable": "暫時無法在 CaptureClub 觀看,請稍後再試。", + "viewSupportingVideoOnIpfsIsUnavailable": "暫時無法觀看 IPFS 影片,請稍後再試。", + "deregisterFromNetworkIsUnavailable": "暫時無法註銷網路註冊,請稍後再試。", + "mintNftTokenIsUnavailable": "暫時無法鍛造 NFT,請稍後再試。", + "viewBlockchainCertificateIsUnavailable": "暫時無法查看區塊鏈證書,請稍後再試。", + "networkActionsAreUnavailable": "暫時無法使用 Network Action,請稍後再試。" } }, "wallets": { From 72bd504501bee887bffb76f2b5241b7de20c9adc Mon Sep 17 00:00:00 2001 From: sultanmyrza Date: Wed, 8 Jun 2022 18:38:06 +0800 Subject: [PATCH 14/41] install appsflyer package --- android/app/capacitor.build.gradle | 1 + .../app/src/main/assets/capacitor.plugins.json | 4 ++++ android/capacitor.settings.gradle | 3 +++ ios/App/Podfile | 1 + package-lock.json | 15 +++++++++++++++ package.json | 1 + 6 files changed, 25 insertions(+) diff --git a/android/app/capacitor.build.gradle b/android/app/capacitor.build.gradle index 3f80fddfe..486d8f266 100644 --- a/android/app/capacitor.build.gradle +++ b/android/app/capacitor.build.gradle @@ -27,6 +27,7 @@ dependencies { implementation project(':capacitor-storage') implementation project(':numbersprotocol-preview-camera') implementation project(':numbersprotocol-preview-video') + implementation project(':appsflyer-capacitor-plugin') implementation project(':capacitor-blob-writer') } diff --git a/android/app/src/main/assets/capacitor.plugins.json b/android/app/src/main/assets/capacitor.plugins.json index 952ae273f..62b1ffd76 100644 --- a/android/app/src/main/assets/capacitor.plugins.json +++ b/android/app/src/main/assets/capacitor.plugins.json @@ -71,6 +71,10 @@ "pkg": "@numbersprotocol/preview-video", "classpath": "io.numbersprotocol.capturelite.plugins.previewvideo.PreviewVideoPlugin" }, + { + "pkg": "appsflyer-capacitor-plugin", + "classpath": "capacitor.plugin.appsflyer.sdk.AppsFlyerPlugin" + }, { "pkg": "capacitor-blob-writer", "classpath": "com.equimaps.capacitorblobwriter.BlobWriter" diff --git a/android/capacitor.settings.gradle b/android/capacitor.settings.gradle index 82f3a2aa4..a5cf34e62 100644 --- a/android/capacitor.settings.gradle +++ b/android/capacitor.settings.gradle @@ -56,5 +56,8 @@ project(':numbersprotocol-preview-camera').projectDir = new File('../node_module include ':numbersprotocol-preview-video' project(':numbersprotocol-preview-video').projectDir = new File('../node_modules/@numbersprotocol/preview-video/android') +include ':appsflyer-capacitor-plugin' +project(':appsflyer-capacitor-plugin').projectDir = new File('../node_modules/appsflyer-capacitor-plugin/android') + include ':capacitor-blob-writer' project(':capacitor-blob-writer').projectDir = new File('../node_modules/capacitor-blob-writer/android') diff --git a/ios/App/Podfile b/ios/App/Podfile index 3a6b06b05..31f3e9b6c 100644 --- a/ios/App/Podfile +++ b/ios/App/Podfile @@ -27,6 +27,7 @@ def capacitor_pods pod 'CapacitorStorage', :path => '../../node_modules/@capacitor/storage' pod 'NumbersprotocolPreviewCamera', :path => '../../node_modules/@numbersprotocol/preview-camera' pod 'NumbersprotocolPreviewVideo', :path => '../../node_modules/@numbersprotocol/preview-video' + pod 'AppsflyerCapacitorPlugin', :path => '../../node_modules/appsflyer-capacitor-plugin' pod 'CapacitorBlobWriter', :path => '../../node_modules/capacitor-blob-writer' end diff --git a/package-lock.json b/package-lock.json index d3041c73f..193077f7e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -48,6 +48,7 @@ "@ngx-formly/schematics": "^5.10.22", "@numbersprotocol/preview-camera": "github:numbersprotocol/preview-camera#release-0.0.2-auto-rotate-fix", "@numbersprotocol/preview-video": "github:numbersprotocol/preview-video", + "appsflyer-capacitor-plugin": "^6.5.2", "async-mutex": "^0.3.2", "buffer": "^5.7.1", "capacitor-blob-writer": "^1.0.4", @@ -5267,6 +5268,14 @@ "node": ">= 8" } }, + "node_modules/appsflyer-capacitor-plugin": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/appsflyer-capacitor-plugin/-/appsflyer-capacitor-plugin-6.5.2.tgz", + "integrity": "sha512-bCQI/T0mxUE/cwTlLEt8NY7foLyevovB2aeb7qjQfyoL4+7Lv5BHmo6l5ING7ZPSQTEF2MgEzouIuKdAhpXqRg==", + "peerDependencies": { + "@capacitor/core": "^3.0.0" + } + }, "node_modules/aproba": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", @@ -29130,6 +29139,12 @@ "picomatch": "^2.0.4" } }, + "appsflyer-capacitor-plugin": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/appsflyer-capacitor-plugin/-/appsflyer-capacitor-plugin-6.5.2.tgz", + "integrity": "sha512-bCQI/T0mxUE/cwTlLEt8NY7foLyevovB2aeb7qjQfyoL4+7Lv5BHmo6l5ING7ZPSQTEF2MgEzouIuKdAhpXqRg==", + "requires": {} + }, "aproba": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", diff --git a/package.json b/package.json index 219821e8a..cc6982459 100644 --- a/package.json +++ b/package.json @@ -59,6 +59,7 @@ "@ngx-formly/schematics": "^5.10.22", "@numbersprotocol/preview-camera": "github:numbersprotocol/preview-camera#release-0.0.2-auto-rotate-fix", "@numbersprotocol/preview-video": "github:numbersprotocol/preview-video", + "appsflyer-capacitor-plugin": "^6.5.2", "async-mutex": "^0.3.2", "buffer": "^5.7.1", "capacitor-blob-writer": "^1.0.4", From abd75048420aaec581d1d832600ba4296746673f Mon Sep 17 00:00:00 2001 From: sultanmyrza Date: Thu, 9 Jun 2022 01:37:12 +0800 Subject: [PATCH 15/41] install app transparency package for iOS 14+ --- android/app/capacitor.build.gradle | 1 + .../app/src/main/assets/capacitor.plugins.json | 4 ++++ android/capacitor.settings.gradle | 3 +++ ios/App/Podfile | 1 + package-lock.json | 15 +++++++++++++++ package.json | 1 + 6 files changed, 25 insertions(+) diff --git a/android/app/capacitor.build.gradle b/android/app/capacitor.build.gradle index 486d8f266..9f456b3a7 100644 --- a/android/app/capacitor.build.gradle +++ b/android/app/capacitor.build.gradle @@ -9,6 +9,7 @@ android { apply from: "../capacitor-cordova-android-plugins/cordova.variables.gradle" dependencies { + implementation project(':capacitor-community-advertising-id') implementation project(':capacitor-community-bluetooth-le') implementation project(':capacitor-community-http') implementation project(':capacitor-community-wifi') diff --git a/android/app/src/main/assets/capacitor.plugins.json b/android/app/src/main/assets/capacitor.plugins.json index 62b1ffd76..dc40e55f9 100644 --- a/android/app/src/main/assets/capacitor.plugins.json +++ b/android/app/src/main/assets/capacitor.plugins.json @@ -1,4 +1,8 @@ [ + { + "pkg": "@capacitor-community/advertising-id", + "classpath": "com.thomasvidas.advertising.AdvertisingIdPlugin" + }, { "pkg": "@capacitor-community/bluetooth-le", "classpath": "com.capacitorjs.community.plugins.bluetoothle.BluetoothLe" diff --git a/android/capacitor.settings.gradle b/android/capacitor.settings.gradle index a5cf34e62..e404eabf0 100644 --- a/android/capacitor.settings.gradle +++ b/android/capacitor.settings.gradle @@ -2,6 +2,9 @@ include ':capacitor-android' project(':capacitor-android').projectDir = new File('../node_modules/@capacitor/android/capacitor') +include ':capacitor-community-advertising-id' +project(':capacitor-community-advertising-id').projectDir = new File('../node_modules/@capacitor-community/advertising-id/android') + include ':capacitor-community-bluetooth-le' project(':capacitor-community-bluetooth-le').projectDir = new File('../node_modules/@capacitor-community/bluetooth-le/android') diff --git a/ios/App/Podfile b/ios/App/Podfile index 31f3e9b6c..c531c9e8c 100644 --- a/ios/App/Podfile +++ b/ios/App/Podfile @@ -9,6 +9,7 @@ install! 'cocoapods', :disable_input_output_paths => true def capacitor_pods pod 'Capacitor', :path => '../../node_modules/@capacitor/ios' pod 'CapacitorCordova', :path => '../../node_modules/@capacitor/ios' + pod 'CapacitorCommunityAdvertisingId', :path => '../../node_modules/@capacitor-community/advertising-id' pod 'CapacitorCommunityBluetoothLe', :path => '../../node_modules/@capacitor-community/bluetooth-le' pod 'CapacitorCommunityHttp', :path => '../../node_modules/@capacitor-community/http' pod 'CapacitorCommunityWifi', :path => '../../node_modules/@capacitor-community/wifi' diff --git a/package-lock.json b/package-lock.json index 193077f7e..4fa019e58 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,6 +17,7 @@ "@angular/platform-browser": "^12.2.4", "@angular/platform-browser-dynamic": "^12.2.4", "@angular/router": "^12.2.4", + "@capacitor-community/advertising-id": "^1.0.0", "@capacitor-community/bluetooth-le": "^1.7.0", "@capacitor-community/http": "github:numbersprotocol/http#fix-catch-disabled-Local-Network-case-on-iOS", "@capacitor-community/wifi": "github:numbersprotocol/community-capacitor-wifi#capacitor3", @@ -2552,6 +2553,14 @@ "node": ">=6.9.0" } }, + "node_modules/@capacitor-community/advertising-id": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@capacitor-community/advertising-id/-/advertising-id-1.0.0.tgz", + "integrity": "sha512-KOpmE/mWqDYmIFhGjlo/ppiEoI3R8SW6rEJHIY75CdziTwz67byn+fyV2E+T3YB9UoFWMDyImnTK/MZXgkZNug==", + "peerDependencies": { + "@capacitor/core": "^3.0.0" + } + }, "node_modules/@capacitor-community/bluetooth-le": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/@capacitor-community/bluetooth-le/-/bluetooth-le-1.7.0.tgz", @@ -27127,6 +27136,12 @@ "to-fast-properties": "^2.0.0" } }, + "@capacitor-community/advertising-id": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@capacitor-community/advertising-id/-/advertising-id-1.0.0.tgz", + "integrity": "sha512-KOpmE/mWqDYmIFhGjlo/ppiEoI3R8SW6rEJHIY75CdziTwz67byn+fyV2E+T3YB9UoFWMDyImnTK/MZXgkZNug==", + "requires": {} + }, "@capacitor-community/bluetooth-le": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/@capacitor-community/bluetooth-le/-/bluetooth-le-1.7.0.tgz", diff --git a/package.json b/package.json index cc6982459..d22267e02 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "@angular/platform-browser": "^12.2.4", "@angular/platform-browser-dynamic": "^12.2.4", "@angular/router": "^12.2.4", + "@capacitor-community/advertising-id": "^1.0.0", "@capacitor-community/bluetooth-le": "^1.7.0", "@capacitor-community/http": "github:numbersprotocol/http#fix-catch-disabled-Local-Network-case-on-iOS", "@capacitor-community/wifi": "github:numbersprotocol/community-capacitor-wifi#capacitor3", From 7fdad6f227df2cb8a3265bc7b37e0b482ecb27c4 Mon Sep 17 00:00:00 2001 From: sultanmyrza Date: Thu, 9 Jun 2022 01:38:28 +0800 Subject: [PATCH 16/41] add `NSUserTrackingUsageDescription` for iOS --- ios/App/App/Info.plist | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ios/App/App/Info.plist b/ios/App/App/Info.plist index 65bf2de48..d6ee25d2c 100644 --- a/ios/App/App/Info.plist +++ b/ios/App/App/Info.plist @@ -86,5 +86,7 @@ UIViewControllerBasedStatusBarAppearance + NSUserTrackingUsageDescription + Your data will be used to measure advertising efficiency From 6fb18aa1435c5fed4cb9cd12377f400529cbfbcd Mon Sep 17 00:00:00 2001 From: sultanmyrza Date: Thu, 9 Jun 2022 03:18:08 +0800 Subject: [PATCH 17/41] add app-flyer service --- .../app-flyer/app-flyer.service.spec.ts | 34 ++++++++++++++++ src/app/shared/app-flyer/app-flyer.service.ts | 40 +++++++++++++++++++ 2 files changed, 74 insertions(+) create mode 100644 src/app/shared/app-flyer/app-flyer.service.spec.ts create mode 100644 src/app/shared/app-flyer/app-flyer.service.ts diff --git a/src/app/shared/app-flyer/app-flyer.service.spec.ts b/src/app/shared/app-flyer/app-flyer.service.spec.ts new file mode 100644 index 000000000..4d65255d4 --- /dev/null +++ b/src/app/shared/app-flyer/app-flyer.service.spec.ts @@ -0,0 +1,34 @@ +import { TestBed, waitForAsync } from '@angular/core/testing'; +import { Platform } from '@ionic/angular'; +import { SharedTestingModule } from '../shared-testing.module'; +import { AppFlyerService } from './app-flyer.service'; + +describe('AppFlyerService', () => { + let service: AppFlyerService; + let platformIs: (platformName: string) => boolean; + let platformReadySpy: Promise; + let platformSpy: Platform; + + beforeEach( + waitForAsync(async () => { + platformReadySpy = Promise.resolve(); + platformIs = _ => false; + + platformSpy = jasmine.createSpyObj('Platform', { + ready: platformReadySpy, + is: platformIs, + }); + + await TestBed.configureTestingModule({ + imports: [SharedTestingModule], + providers: [{ provide: Platform, useValue: platformSpy }], + }).compileComponents(); + + service = TestBed.inject(AppFlyerService); + }) + ); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/src/app/shared/app-flyer/app-flyer.service.ts b/src/app/shared/app-flyer/app-flyer.service.ts new file mode 100644 index 000000000..cc234e326 --- /dev/null +++ b/src/app/shared/app-flyer/app-flyer.service.ts @@ -0,0 +1,40 @@ +import { Injectable } from '@angular/core'; +import { AdvertisingId } from '@capacitor-community/advertising-id'; +import { Platform } from '@ionic/angular'; +import { AFInit, AppsFlyer } from 'appsflyer-capacitor-plugin'; +import { environment } from '../../../environments/environment'; +import { APP_FLYER_DEV_KEY } from '../dia-backend/secret'; + +@Injectable({ + providedIn: 'root', +}) +export class AppFlyerService { + private readonly afConfig: AFInit = { + appID: '1536388009', // AppStore Application ID. For iOS only. + devKey: APP_FLYER_DEV_KEY, + isDebug: !environment.production, + waitForATTUserAuthorization: 10, // for iOS 14 and higher + minTimeBetweenSessions: 6, // default 5 sec + registerOnDeepLink: true, + registerConversionListener: true, + registerOnAppOpenAttribution: false, + deepLinkTimeout: 4000, // default 3000 ms + useReceiptValidationSandbox: true, // iOS only + useUninstallSandbox: true, // iOS only + }; + + constructor(private readonly platform: Platform) {} + + async initAppFlyerSDK() { + if (this.platform.is('ios')) { + await AdvertisingId.requestTracking(); + } + if (this.isNativePlatform) { + await AppsFlyer.initSDK(this.afConfig); + } + } + + private get isNativePlatform() { + return this.platform.is('ios') || this.platform.is('android'); + } +} From 77b828face85ac5bdf84385a742992e00aea0bb0 Mon Sep 17 00:00:00 2001 From: sultanmyrza Date: Thu, 9 Jun 2022 03:18:58 +0800 Subject: [PATCH 18/41] init app flyer sdk on app start --- set-secret.js | 1 + src/app/app.component.ts | 3 +++ 2 files changed, 4 insertions(+) diff --git a/set-secret.js b/set-secret.js index 4f1ab3771..61870f92f 100644 --- a/set-secret.js +++ b/set-secret.js @@ -8,6 +8,7 @@ const envConfigFile = ` export const BASE_URL = '${process.env.NUMBERS_STORAGE_BASE_URL}'; export const TRUSTED_CLIENT_KEY = '${process.env.NUMBERS_STORAGE_TRUSTED_CLIENT_KEY}'; export const BUBBLE_DB_URL = '${process.env.NUMBERS_BUBBLE_DB_URL}'; +export const APP_FLYER_DEV_KEY = '${process.env.APP_FLYER_DEV_KEY}' `; fs.writeFile(targetPath, envConfigFile, err => { if (err) { diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 735edf1cc..2effe568b 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -5,6 +5,7 @@ import { SplashScreen } from '@capacitor/splash-screen'; import { Platform } from '@ionic/angular'; import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy'; import { catchError, concatMap } from 'rxjs/operators'; +import { AppFlyerService } from './shared/app-flyer/app-flyer.service'; import { CameraService } from './shared/camera/camera.service'; import { CaptureService } from './shared/capture/capture.service'; import { CollectorService } from './shared/collector/collector.service'; @@ -35,6 +36,7 @@ export class AppComponent { private readonly captureService: CaptureService, private readonly cameraService: CameraService, private readonly errorService: ErrorService, + appFlyerService: AppFlyerService, notificationService: NotificationService, pushNotificationService: PushNotificationService, langaugeService: LanguageService, @@ -55,6 +57,7 @@ export class AppComponent { this.restoreAppState(); this.initializeCollector(); this.registerIcon(); + appFlyerService.initAppFlyerSDK(); } static setDarkMode() { From 050a42ced0693c188edd13809d50cb1580d2a1ec Mon Sep 17 00:00:00 2001 From: sultanmyrza Date: Thu, 9 Jun 2022 03:24:57 +0800 Subject: [PATCH 19/41] add simple AppFlyer logEvet on Wallets page open --- src/app/features/wallets/wallets.page.ts | 8 +++++++- src/app/shared/app-flyer/app-flyer.service.ts | 10 +++++++++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/src/app/features/wallets/wallets.page.ts b/src/app/features/wallets/wallets.page.ts index bb0cd5030..4cc340a5d 100644 --- a/src/app/features/wallets/wallets.page.ts +++ b/src/app/features/wallets/wallets.page.ts @@ -18,6 +18,7 @@ import { switchMap, tap, } from 'rxjs/operators'; +import { AppFlyerService } from '../../shared/app-flyer/app-flyer.service'; import { WebCryptoApiSignatureProvider } from '../../shared/collector/signature/web-crypto-api-signature-provider/web-crypto-api-signature-provider.service'; import { ConfirmAlert } from '../../shared/confirm-alert/confirm-alert.service'; import { DiaBackendAuthService } from '../../shared/dia-backend/auth/dia-backend-auth.service'; @@ -59,7 +60,8 @@ export class WalletsPage { private readonly confirmAlert: ConfirmAlert, private readonly dialog: MatDialog, private readonly errorService: ErrorService, - private readonly router: Router + private readonly router: Router, + private readonly appFlyerService: AppFlyerService ) { this.matIconRegistry.addSvgIcon( 'wallet', @@ -79,6 +81,10 @@ export class WalletsPage { .subscribe(totalBalance => this.totalBalance$.next(totalBalance)); } + ionViewDidEnter() { + this.appFlyerService.trackUserOpenedWalletsPage(); + } + // eslint-disable-next-line class-methods-use-this onBuyNumBtnClicked() { Browser.open({ diff --git a/src/app/shared/app-flyer/app-flyer.service.ts b/src/app/shared/app-flyer/app-flyer.service.ts index cc234e326..3580f021c 100644 --- a/src/app/shared/app-flyer/app-flyer.service.ts +++ b/src/app/shared/app-flyer/app-flyer.service.ts @@ -1,7 +1,7 @@ import { Injectable } from '@angular/core'; import { AdvertisingId } from '@capacitor-community/advertising-id'; import { Platform } from '@ionic/angular'; -import { AFInit, AppsFlyer } from 'appsflyer-capacitor-plugin'; +import { AFEvent, AFInit, AppsFlyer } from 'appsflyer-capacitor-plugin'; import { environment } from '../../../environments/environment'; import { APP_FLYER_DEV_KEY } from '../dia-backend/secret'; @@ -34,6 +34,14 @@ export class AppFlyerService { } } + async trackUserOpenedWalletsPage() { + if (this.isNativePlatform) return; + + const data: AFEvent = { eventName: 'open-wallets-page' }; + + return AppsFlyer.logEvent(data).catch(() => ({})); + } + private get isNativePlatform() { return this.platform.is('ios') || this.platform.is('android'); } From 00ca46551f691b53a27c62ee01d3815f6d09795e Mon Sep 17 00:00:00 2001 From: sultanmyrza Date: Tue, 14 Jun 2022 14:05:43 +0800 Subject: [PATCH 20/41] fix: app-flyer related tests --- src/app/app.component.spec.ts | 3 +++ src/app/shared/app-flyer/app-flyer.service.spec.ts | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/app/app.component.spec.ts b/src/app/app.component.spec.ts index b47d0a43b..4d3c14ca8 100644 --- a/src/app/app.component.spec.ts +++ b/src/app/app.component.spec.ts @@ -9,13 +9,16 @@ import { MaterialTestingModule } from './shared/material/material-testing.module describe('AppComponent', () => { let platformReadySpy: Promise; + let platformIsSpy: boolean | undefined; let platformSpy: Platform; beforeEach( waitForAsync(() => { platformReadySpy = Promise.resolve(); + platformIsSpy = false; platformSpy = jasmine.createSpyObj('Platform', { ready: platformReadySpy, + is: platformIsSpy, }); TestBed.configureTestingModule({ diff --git a/src/app/shared/app-flyer/app-flyer.service.spec.ts b/src/app/shared/app-flyer/app-flyer.service.spec.ts index 4d65255d4..264628153 100644 --- a/src/app/shared/app-flyer/app-flyer.service.spec.ts +++ b/src/app/shared/app-flyer/app-flyer.service.spec.ts @@ -5,14 +5,14 @@ import { AppFlyerService } from './app-flyer.service'; describe('AppFlyerService', () => { let service: AppFlyerService; - let platformIs: (platformName: string) => boolean; + let platformIs: boolean | undefined; let platformReadySpy: Promise; let platformSpy: Platform; beforeEach( waitForAsync(async () => { platformReadySpy = Promise.resolve(); - platformIs = _ => false; + platformIs = false; platformSpy = jasmine.createSpyObj('Platform', { ready: platformReadySpy, From 98fd4df7c035605c1cab4b500aaee95cb80c9150 Mon Sep 17 00:00:00 2001 From: sultanmyrza Date: Thu, 16 Jun 2022 17:47:18 +0800 Subject: [PATCH 21/41] rename(app-flyer.service): to apps-flyer.service --- src/app/app.component.ts | 6 +++--- src/app/features/wallets/wallets.page.ts | 6 +++--- ...p-flyer.service.spec.ts => apps-flyer.service.spec.ts} | 8 ++++---- .../{app-flyer.service.ts => apps-flyer.service.ts} | 2 +- 4 files changed, 11 insertions(+), 11 deletions(-) rename src/app/shared/app-flyer/{app-flyer.service.spec.ts => apps-flyer.service.spec.ts} (81%) rename src/app/shared/app-flyer/{app-flyer.service.ts => apps-flyer.service.ts} (97%) diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 2effe568b..aba021865 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -5,7 +5,7 @@ import { SplashScreen } from '@capacitor/splash-screen'; import { Platform } from '@ionic/angular'; import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy'; import { catchError, concatMap } from 'rxjs/operators'; -import { AppFlyerService } from './shared/app-flyer/app-flyer.service'; +import { AppsFlyerService } from './shared/app-flyer/apps-flyer.service'; import { CameraService } from './shared/camera/camera.service'; import { CaptureService } from './shared/capture/capture.service'; import { CollectorService } from './shared/collector/collector.service'; @@ -36,7 +36,7 @@ export class AppComponent { private readonly captureService: CaptureService, private readonly cameraService: CameraService, private readonly errorService: ErrorService, - appFlyerService: AppFlyerService, + appsFlyerService: AppsFlyerService, notificationService: NotificationService, pushNotificationService: PushNotificationService, langaugeService: LanguageService, @@ -57,7 +57,7 @@ export class AppComponent { this.restoreAppState(); this.initializeCollector(); this.registerIcon(); - appFlyerService.initAppFlyerSDK(); + appsFlyerService.initAppFlyerSDK(); } static setDarkMode() { diff --git a/src/app/features/wallets/wallets.page.ts b/src/app/features/wallets/wallets.page.ts index 4cc340a5d..e76d9b161 100644 --- a/src/app/features/wallets/wallets.page.ts +++ b/src/app/features/wallets/wallets.page.ts @@ -18,7 +18,7 @@ import { switchMap, tap, } from 'rxjs/operators'; -import { AppFlyerService } from '../../shared/app-flyer/app-flyer.service'; +import { AppsFlyerService } from '../../shared/app-flyer/apps-flyer.service'; import { WebCryptoApiSignatureProvider } from '../../shared/collector/signature/web-crypto-api-signature-provider/web-crypto-api-signature-provider.service'; import { ConfirmAlert } from '../../shared/confirm-alert/confirm-alert.service'; import { DiaBackendAuthService } from '../../shared/dia-backend/auth/dia-backend-auth.service'; @@ -61,7 +61,7 @@ export class WalletsPage { private readonly dialog: MatDialog, private readonly errorService: ErrorService, private readonly router: Router, - private readonly appFlyerService: AppFlyerService + private readonly appsFlyerService: AppsFlyerService ) { this.matIconRegistry.addSvgIcon( 'wallet', @@ -82,7 +82,7 @@ export class WalletsPage { } ionViewDidEnter() { - this.appFlyerService.trackUserOpenedWalletsPage(); + this.appsFlyerService.trackUserOpenedWalletsPage(); } // eslint-disable-next-line class-methods-use-this diff --git a/src/app/shared/app-flyer/app-flyer.service.spec.ts b/src/app/shared/app-flyer/apps-flyer.service.spec.ts similarity index 81% rename from src/app/shared/app-flyer/app-flyer.service.spec.ts rename to src/app/shared/app-flyer/apps-flyer.service.spec.ts index 264628153..f475afa51 100644 --- a/src/app/shared/app-flyer/app-flyer.service.spec.ts +++ b/src/app/shared/app-flyer/apps-flyer.service.spec.ts @@ -1,10 +1,10 @@ import { TestBed, waitForAsync } from '@angular/core/testing'; import { Platform } from '@ionic/angular'; import { SharedTestingModule } from '../shared-testing.module'; -import { AppFlyerService } from './app-flyer.service'; +import { AppsFlyerService } from './apps-flyer.service'; -describe('AppFlyerService', () => { - let service: AppFlyerService; +describe('AppsFlyerService', () => { + let service: AppsFlyerService; let platformIs: boolean | undefined; let platformReadySpy: Promise; let platformSpy: Platform; @@ -24,7 +24,7 @@ describe('AppFlyerService', () => { providers: [{ provide: Platform, useValue: platformSpy }], }).compileComponents(); - service = TestBed.inject(AppFlyerService); + service = TestBed.inject(AppsFlyerService); }) ); diff --git a/src/app/shared/app-flyer/app-flyer.service.ts b/src/app/shared/app-flyer/apps-flyer.service.ts similarity index 97% rename from src/app/shared/app-flyer/app-flyer.service.ts rename to src/app/shared/app-flyer/apps-flyer.service.ts index 3580f021c..80c2a8d51 100644 --- a/src/app/shared/app-flyer/app-flyer.service.ts +++ b/src/app/shared/app-flyer/apps-flyer.service.ts @@ -8,7 +8,7 @@ import { APP_FLYER_DEV_KEY } from '../dia-backend/secret'; @Injectable({ providedIn: 'root', }) -export class AppFlyerService { +export class AppsFlyerService { private readonly afConfig: AFInit = { appID: '1536388009', // AppStore Application ID. For iOS only. devKey: APP_FLYER_DEV_KEY, From f206b6a1837c6f0873a6f50ab510cde71db0794a Mon Sep 17 00:00:00 2001 From: sultanmyrza Date: Mon, 20 Jun 2022 15:49:10 +0800 Subject: [PATCH 22/41] fix(Info.plist): change NSUserTrackingUsageDescription --- ios/App/App/Info.plist | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ios/App/App/Info.plist b/ios/App/App/Info.plist index d6ee25d2c..1e46ba0bb 100644 --- a/ios/App/App/Info.plist +++ b/ios/App/App/Info.plist @@ -87,6 +87,6 @@ UIViewControllerBasedStatusBarAppearance NSUserTrackingUsageDescription - Your data will be used to measure advertising efficiency + We collect your data only for counting download numbers and will never share it with any 3rd party From 789ae7b3fd4d756f6fd0c828d66de5bf9f2c2dc6 Mon Sep 17 00:00:00 2001 From: sultanmyrza Date: Mon, 20 Jun 2022 16:51:11 +0800 Subject: [PATCH 23/41] rename all appFlyer to appsFlyer --- set-secret.js | 2 +- src/app/app.component.ts | 2 +- src/app/shared/app-flyer/apps-flyer.service.ts | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/set-secret.js b/set-secret.js index 61870f92f..0ff04ccfb 100644 --- a/set-secret.js +++ b/set-secret.js @@ -8,7 +8,7 @@ const envConfigFile = ` export const BASE_URL = '${process.env.NUMBERS_STORAGE_BASE_URL}'; export const TRUSTED_CLIENT_KEY = '${process.env.NUMBERS_STORAGE_TRUSTED_CLIENT_KEY}'; export const BUBBLE_DB_URL = '${process.env.NUMBERS_BUBBLE_DB_URL}'; -export const APP_FLYER_DEV_KEY = '${process.env.APP_FLYER_DEV_KEY}' +export const APPS_FLYER_DEV_KEY = '${process.env.APPS_FLYER_DEV_KEY}' `; fs.writeFile(targetPath, envConfigFile, err => { if (err) { diff --git a/src/app/app.component.ts b/src/app/app.component.ts index aba021865..beea5933b 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -57,7 +57,7 @@ export class AppComponent { this.restoreAppState(); this.initializeCollector(); this.registerIcon(); - appsFlyerService.initAppFlyerSDK(); + appsFlyerService.initAppsFlyerSDK(); } static setDarkMode() { diff --git a/src/app/shared/app-flyer/apps-flyer.service.ts b/src/app/shared/app-flyer/apps-flyer.service.ts index 80c2a8d51..7c3155607 100644 --- a/src/app/shared/app-flyer/apps-flyer.service.ts +++ b/src/app/shared/app-flyer/apps-flyer.service.ts @@ -3,7 +3,7 @@ import { AdvertisingId } from '@capacitor-community/advertising-id'; import { Platform } from '@ionic/angular'; import { AFEvent, AFInit, AppsFlyer } from 'appsflyer-capacitor-plugin'; import { environment } from '../../../environments/environment'; -import { APP_FLYER_DEV_KEY } from '../dia-backend/secret'; +import { APPS_FLYER_DEV_KEY } from '../dia-backend/secret'; @Injectable({ providedIn: 'root', @@ -11,7 +11,7 @@ import { APP_FLYER_DEV_KEY } from '../dia-backend/secret'; export class AppsFlyerService { private readonly afConfig: AFInit = { appID: '1536388009', // AppStore Application ID. For iOS only. - devKey: APP_FLYER_DEV_KEY, + devKey: APPS_FLYER_DEV_KEY, isDebug: !environment.production, waitForATTUserAuthorization: 10, // for iOS 14 and higher minTimeBetweenSessions: 6, // default 5 sec @@ -25,7 +25,7 @@ export class AppsFlyerService { constructor(private readonly platform: Platform) {} - async initAppFlyerSDK() { + async initAppsFlyerSDK() { if (this.platform.is('ios')) { await AdvertisingId.requestTracking(); } From 0fbce999705b8971d10650cfa72f6e941f9db192 Mon Sep 17 00:00:00 2001 From: sultanmyrza Date: Mon, 20 Jun 2022 19:22:08 +0800 Subject: [PATCH 24/41] install in-app-purchase-2 plugin --- android/app/capacitor.build.gradle | 2 +- android/app/src/main/res/xml/config.xml | 4 ++ ios/App/App/config.xml | 5 ++ ios/App/Podfile | 1 + package-lock.json | 63 +++++++++++++++++++++++++ package.json | 2 + 6 files changed, 76 insertions(+), 1 deletion(-) diff --git a/android/app/capacitor.build.gradle b/android/app/capacitor.build.gradle index 3f80fddfe..797a56675 100644 --- a/android/app/capacitor.build.gradle +++ b/android/app/capacitor.build.gradle @@ -28,7 +28,7 @@ dependencies { implementation project(':numbersprotocol-preview-camera') implementation project(':numbersprotocol-preview-video') implementation project(':capacitor-blob-writer') - + implementation "com.android.billingclient:billing:4.0.0" } diff --git a/android/app/src/main/res/xml/config.xml b/android/app/src/main/res/xml/config.xml index 1b1b0e0dc..404a1f136 100644 --- a/android/app/src/main/res/xml/config.xml +++ b/android/app/src/main/res/xml/config.xml @@ -2,5 +2,9 @@ + + + + \ No newline at end of file diff --git a/ios/App/App/config.xml b/ios/App/App/config.xml index 1b1b0e0dc..1a9662ebc 100644 --- a/ios/App/App/config.xml +++ b/ios/App/App/config.xml @@ -2,5 +2,10 @@ + + + + + \ No newline at end of file diff --git a/ios/App/Podfile b/ios/App/Podfile index 3a6b06b05..b3c63f989 100644 --- a/ios/App/Podfile +++ b/ios/App/Podfile @@ -28,6 +28,7 @@ def capacitor_pods pod 'NumbersprotocolPreviewCamera', :path => '../../node_modules/@numbersprotocol/preview-camera' pod 'NumbersprotocolPreviewVideo', :path => '../../node_modules/@numbersprotocol/preview-video' pod 'CapacitorBlobWriter', :path => '../../node_modules/capacitor-blob-writer' + pod 'CordovaPlugins', :path => '../capacitor-cordova-ios-plugins' end target 'App' do diff --git a/package-lock.json b/package-lock.json index d3041c73f..ccaba08bf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,6 +17,7 @@ "@angular/platform-browser": "^12.2.4", "@angular/platform-browser-dynamic": "^12.2.4", "@angular/router": "^12.2.4", + "@awesome-cordova-plugins/in-app-purchase-2": "^5.43.0", "@capacitor-community/bluetooth-le": "^1.7.0", "@capacitor-community/http": "github:numbersprotocol/http#fix-catch-disabled-Local-Network-case-on-iOS", "@capacitor-community/wifi": "github:numbersprotocol/community-capacitor-wifi#capacitor3", @@ -52,6 +53,7 @@ "buffer": "^5.7.1", "capacitor-blob-writer": "^1.0.4", "compressorjs": "^1.0.7", + "cordova-plugin-purchase": "^11.0.0", "immutable": "^4.0.0-rc.14", "lodash-es": "^4.17.21", "material-design-icons-iconfont": "^6.1.0", @@ -912,6 +914,30 @@ "integrity": "sha512-H71nDOOL8Y7kWRLqf6Sums+01Q5msqBW2KhDUTemh1tvY04eSkSXrK0uj/4mmY0Xr16/3zyZmsrxN7CKuRbNRg==", "dev": true }, + "node_modules/@awesome-cordova-plugins/core": { + "version": "5.43.0", + "resolved": "https://registry.npmjs.org/@awesome-cordova-plugins/core/-/core-5.43.0.tgz", + "integrity": "sha512-DDLzEYtP6fDqyVORwuzXH64VFYTcW5qoaXAvghWtc5O+wciqeX1hFO7WY7l+1Ytkf6J4IbyMhrsOgZb3bC1eMQ==", + "peer": true, + "dependencies": { + "@types/cordova": "latest" + }, + "peerDependencies": { + "rxjs": "^5.5.0 || ^6.5.0 || ^7.3.0" + } + }, + "node_modules/@awesome-cordova-plugins/in-app-purchase-2": { + "version": "5.43.0", + "resolved": "https://registry.npmjs.org/@awesome-cordova-plugins/in-app-purchase-2/-/in-app-purchase-2-5.43.0.tgz", + "integrity": "sha512-y292xt+DSqsIpFCe0X7yjTZkC4moPT+gYpo7C59yBR8OeZPx0LHNTth3rzeSYsVityyCjnpEIf08x0hGfKr3bw==", + "dependencies": { + "@types/cordova": "latest" + }, + "peerDependencies": { + "@awesome-cordova-plugins/core": "^5.1.0", + "rxjs": "^5.5.0 || ^6.5.0 || ^7.3.0" + } + }, "node_modules/@babel/code-frame": { "version": "7.14.5", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.14.5.tgz", @@ -4271,6 +4297,11 @@ "integrity": "sha512-y7mImlc/rNkvCRmg8gC3/lj87S7pTUIJ6QGjwHR9WQJcFs+ZMTOaoPrkdFA/YdbuqVEmEbb5RdhVxMkAcgOnpg==", "dev": true }, + "node_modules/@types/cordova": { + "version": "0.0.34", + "resolved": "https://registry.npmjs.org/@types/cordova/-/cordova-0.0.34.tgz", + "integrity": "sha512-rkiiTuf/z2wTd4RxFOb+clE7PF4AEJU0hsczbUdkHHBtkUmpWQpEddynNfJYKYtZFJKbq4F+brfekt1kx85IZA==" + }, "node_modules/@types/cors": { "version": "2.8.10", "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.10.tgz", @@ -7225,6 +7256,11 @@ "url": "https://opencollective.com/webpack" } }, + "node_modules/cordova-plugin-purchase": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/cordova-plugin-purchase/-/cordova-plugin-purchase-11.0.0.tgz", + "integrity": "sha512-FkgOyWBYS989dYpM6d3R36VrdsHweTbmp5y7ELa1GwMYq2AG+ImdB6DJcVpyU/se/zpTndBWH/zN9s3AFKfsLg==" + }, "node_modules/core-js": { "version": "3.16.0", "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.16.0.tgz", @@ -25967,6 +26003,23 @@ "integrity": "sha512-H71nDOOL8Y7kWRLqf6Sums+01Q5msqBW2KhDUTemh1tvY04eSkSXrK0uj/4mmY0Xr16/3zyZmsrxN7CKuRbNRg==", "dev": true }, + "@awesome-cordova-plugins/core": { + "version": "5.43.0", + "resolved": "https://registry.npmjs.org/@awesome-cordova-plugins/core/-/core-5.43.0.tgz", + "integrity": "sha512-DDLzEYtP6fDqyVORwuzXH64VFYTcW5qoaXAvghWtc5O+wciqeX1hFO7WY7l+1Ytkf6J4IbyMhrsOgZb3bC1eMQ==", + "peer": true, + "requires": { + "@types/cordova": "latest" + } + }, + "@awesome-cordova-plugins/in-app-purchase-2": { + "version": "5.43.0", + "resolved": "https://registry.npmjs.org/@awesome-cordova-plugins/in-app-purchase-2/-/in-app-purchase-2-5.43.0.tgz", + "integrity": "sha512-y292xt+DSqsIpFCe0X7yjTZkC4moPT+gYpo7C59yBR8OeZPx0LHNTth3rzeSYsVityyCjnpEIf08x0hGfKr3bw==", + "requires": { + "@types/cordova": "latest" + } + }, "@babel/code-frame": { "version": "7.14.5", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.14.5.tgz", @@ -28370,6 +28423,11 @@ "integrity": "sha512-y7mImlc/rNkvCRmg8gC3/lj87S7pTUIJ6QGjwHR9WQJcFs+ZMTOaoPrkdFA/YdbuqVEmEbb5RdhVxMkAcgOnpg==", "dev": true }, + "@types/cordova": { + "version": "0.0.34", + "resolved": "https://registry.npmjs.org/@types/cordova/-/cordova-0.0.34.tgz", + "integrity": "sha512-rkiiTuf/z2wTd4RxFOb+clE7PF4AEJU0hsczbUdkHHBtkUmpWQpEddynNfJYKYtZFJKbq4F+brfekt1kx85IZA==" + }, "@types/cors": { "version": "2.8.10", "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.10.tgz", @@ -30700,6 +30758,11 @@ } } }, + "cordova-plugin-purchase": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/cordova-plugin-purchase/-/cordova-plugin-purchase-11.0.0.tgz", + "integrity": "sha512-FkgOyWBYS989dYpM6d3R36VrdsHweTbmp5y7ELa1GwMYq2AG+ImdB6DJcVpyU/se/zpTndBWH/zN9s3AFKfsLg==" + }, "core-js": { "version": "3.16.0", "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.16.0.tgz", diff --git a/package.json b/package.json index 219821e8a..3640b5d18 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "@angular/platform-browser": "^12.2.4", "@angular/platform-browser-dynamic": "^12.2.4", "@angular/router": "^12.2.4", + "@awesome-cordova-plugins/in-app-purchase-2": "^5.43.0", "@capacitor-community/bluetooth-le": "^1.7.0", "@capacitor-community/http": "github:numbersprotocol/http#fix-catch-disabled-Local-Network-case-on-iOS", "@capacitor-community/wifi": "github:numbersprotocol/community-capacitor-wifi#capacitor3", @@ -63,6 +64,7 @@ "buffer": "^5.7.1", "capacitor-blob-writer": "^1.0.4", "compressorjs": "^1.0.7", + "cordova-plugin-purchase": "^11.0.0", "immutable": "^4.0.0-rc.14", "lodash-es": "^4.17.21", "material-design-icons-iconfont": "^6.1.0", From 42c5ab5d9a0cf13c8e6e5e407265c2c1dce5334f Mon Sep 17 00:00:00 2001 From: sultanmyrza Date: Mon, 20 Jun 2022 19:22:33 +0800 Subject: [PATCH 25/41] add android billing permission --- android/app/src/main/AndroidManifest.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 580396b24..2ceb870d7 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -44,6 +44,8 @@ + + From 086a962a99827fe9c75149ffb57eee4e5b5a2cb4 Mon Sep 17 00:00:00 2001 From: sultanmyrza Date: Mon, 20 Jun 2022 19:23:25 +0800 Subject: [PATCH 26/41] add StoreKit config to test in-app purchases locally on iOS --- ios/App/App.xcodeproj/project.pbxproj | 2 + ios/App/Configuration.storekit | 78 +++++++++++++++++++++++++++ 2 files changed, 80 insertions(+) create mode 100644 ios/App/Configuration.storekit diff --git a/ios/App/App.xcodeproj/project.pbxproj b/ios/App/App.xcodeproj/project.pbxproj index 063bf1ee4..f9c84841e 100644 --- a/ios/App/App.xcodeproj/project.pbxproj +++ b/ios/App/App.xcodeproj/project.pbxproj @@ -31,6 +31,7 @@ AF277DCFFFF123FFC6DF26C7 /* Pods_App.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_App.framework; sourceTree = BUILT_PRODUCTS_DIR; }; AF51FD2D460BCFE21FA515B2 /* Pods-App.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App.release.xcconfig"; path = "Pods/Target Support Files/Pods-App/Pods-App.release.xcconfig"; sourceTree = ""; }; DE095CFF277AF00900242276 /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = ""; }; + DE2706742854C3960046512E /* Configuration.storekit */ = {isa = PBXFileReference; lastKnownFileType = text; path = Configuration.storekit; sourceTree = ""; }; DE28998F27B790A900F6581C /* public */ = {isa = PBXFileReference; lastKnownFileType = folder; path = public; sourceTree = ""; }; FC68EB0AF532CFC21C3344DD /* Pods-App.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App.debug.xcconfig"; path = "Pods/Target Support Files/Pods-App/Pods-App.debug.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ @@ -58,6 +59,7 @@ 504EC2FB1FED79650016851F = { isa = PBXGroup; children = ( + DE2706742854C3960046512E /* Configuration.storekit */, 504EC3061FED79650016851F /* App */, 504EC3051FED79650016851F /* Products */, 7F8756D8B27F46E3366F6CEA /* Pods */, diff --git a/ios/App/Configuration.storekit b/ios/App/Configuration.storekit new file mode 100644 index 000000000..ac072982e --- /dev/null +++ b/ios/App/Configuration.storekit @@ -0,0 +1,78 @@ +{ + "identifier" : "509552BA", + "nonRenewingSubscriptions" : [ + + ], + "products" : [ + { + "displayPrice" : "0.99", + "familyShareable" : false, + "internalID" : "68D9071C", + "localizations" : [ + { + "description" : "Bronze Pack (Description)", + "displayName" : "Bronze Pack", + "locale" : "en_US" + } + ], + "productID" : "cap_lite_consumable_bronze_pack_099", + "referenceName" : "Bronze Pack", + "type" : "Consumable" + }, + { + "displayPrice" : "1.99", + "familyShareable" : false, + "internalID" : "9A0F041B", + "localizations" : [ + { + "description" : "Silver Pack (Description)", + "displayName" : "Silver Pack", + "locale" : "en_US" + } + ], + "productID" : "cap_lite_consumable_silver_pack_199", + "referenceName" : "Silver Pack", + "type" : "Consumable" + }, + { + "displayPrice" : "2.99", + "familyShareable" : false, + "internalID" : "D92B12C1", + "localizations" : [ + { + "description" : "Gold Pack (Description)", + "displayName" : "Gold Pack", + "locale" : "en_US" + } + ], + "productID" : "cap_lite_consumable_gold_pack_299", + "referenceName" : "Gold Pack", + "type" : "Consumable" + }, + { + "displayPrice" : "3.99", + "familyShareable" : false, + "internalID" : "D0441E1B", + "localizations" : [ + { + "description" : "Platinum Pack (Description)", + "displayName" : "Platinum Pack", + "locale" : "en_US" + } + ], + "productID" : "cap_lite_consumable_platinum_pack_399", + "referenceName" : "Platinum Pack", + "type" : "Consumable" + } + ], + "settings" : { + + }, + "subscriptionGroups" : [ + + ], + "version" : { + "major" : 1, + "minor" : 2 + } +} From 578db44f626383e1b90dec7fdd7537574c358a02 Mon Sep 17 00:00:00 2001 From: sultanmyrza Date: Mon, 20 Jun 2022 19:24:01 +0800 Subject: [PATCH 27/41] add translations for In App Purchases --- src/assets/i18n/en-us.json | 12 +++++++++++- src/assets/i18n/zh-tw.json | 12 +++++++++++- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/src/assets/i18n/en-us.json b/src/assets/i18n/en-us.json index d3a2671eb..b2342bc1b 100644 --- a/src/assets/i18n/en-us.json +++ b/src/assets/i18n/en-us.json @@ -277,7 +277,13 @@ "gasFee": "Gas Fee", "pending": "Pending", "total": "Total", - "calculateGasFee": "Calculate Gas" + "calculateGasFee": "Calculate Gas", + "buyNum": { + "buyNum": "Buy NUM", + "buyPoints": "Buy {{points}} Points", + "pointsAdded": "{{points}} points added", + "failedToAddPoints": "Failed to add points" + } }, "invitation": { "invitation": "Invitation", @@ -337,5 +343,9 @@ "pleaseUpdateTheAppForProperFunctioning": "Please update the app for proper functioning", "remindMeTomorrow": "REMIND ME TOMORROW", "updateNow": "UPDATE NOW" + }, + "inAppPurchase": { + "failedToInitInAppStore": "An error occurred during the initialization of the In-App Store. Please check your Internet connection quality.", + "inAppPurchaseErrorOcurred": "In-App Purchase error occurred. Please try again later." } } diff --git a/src/assets/i18n/zh-tw.json b/src/assets/i18n/zh-tw.json index 47d4fc667..497d57a4e 100644 --- a/src/assets/i18n/zh-tw.json +++ b/src/assets/i18n/zh-tw.json @@ -277,7 +277,13 @@ "gasFee": "油費", "pending": "待計算", "total": "總額", - "calculateGasFee": "計算油費" + "calculateGasFee": "計算油費", + "buyNum": { + "buyNum": "購買 NUM", + "buyPoints": "買{{points}}分", + "pointsAdded": "加了{{points}}分", + "failedToAddPoints": "加分失敗" + } }, "invitation": { "invitation": "邀請", @@ -337,5 +343,9 @@ "pleaseUpdateTheAppForProperFunctioning": "請更新應用程序以使其正常運行", "remindMeTomorrow": "明天提醒我", "updateNow": "現在更新" + }, + "inAppPurchase": { + "failedToInitInAppStore": "In-App Store 初始化期間發生錯誤。 請檢查您的互聯網連接質量。", + "inAppPurchaseErrorOcurred": "發生應用內購買錯誤。 請稍後再試。" } } From f1d3c5583a19fe0cb9c1d19e064f4e4882fc335b Mon Sep 17 00:00:00 2001 From: sultanmyrza Date: Mon, 20 Jun 2022 19:24:43 +0800 Subject: [PATCH 28/41] add env var for BUBBLE_API endpoint --- set-secret.js | 1 + 1 file changed, 1 insertion(+) diff --git a/set-secret.js b/set-secret.js index 4f1ab3771..bc28b3298 100644 --- a/set-secret.js +++ b/set-secret.js @@ -8,6 +8,7 @@ const envConfigFile = ` export const BASE_URL = '${process.env.NUMBERS_STORAGE_BASE_URL}'; export const TRUSTED_CLIENT_KEY = '${process.env.NUMBERS_STORAGE_TRUSTED_CLIENT_KEY}'; export const BUBBLE_DB_URL = '${process.env.NUMBERS_BUBBLE_DB_URL}'; +export const BUBBLE_API_URL = '${process.env.BUBBLE_API_URL}'; `; fs.writeFile(targetPath, envConfigFile, err => { if (err) { From f3e7d8df8f0cee8c67bdbd1e1e4830b8dca8cec3 Mon Sep 17 00:00:00 2001 From: sultanmyrza Date: Mon, 20 Jun 2022 19:25:16 +0800 Subject: [PATCH 29/41] add utility functions for in app purchases --- src/app/utils/in-app-purchase.ts | 97 ++++++++++++++++++++++++++++++++ 1 file changed, 97 insertions(+) create mode 100644 src/app/utils/in-app-purchase.ts diff --git a/src/app/utils/in-app-purchase.ts b/src/app/utils/in-app-purchase.ts new file mode 100644 index 000000000..7ce396de4 --- /dev/null +++ b/src/app/utils/in-app-purchase.ts @@ -0,0 +1,97 @@ +import { isDevMode } from '@angular/core'; +import { IAPProduct } from '@awesome-cordova-plugins/in-app-purchase-2/ngx'; +import { CaptureInAppProductIds } from '../shared/in-app-store/in-app-store.service'; + +/** + * Usefull to see in app product state changes in console for better debugging. + * It will pring to console only in dev mode aka isDevMode() === true + */ +export function setupInAppPurchaseDebugPrint(tag: string) { + return function (message: string, data?: any) { + if (!isDevMode()) return; + + // eslint-disable-next-line no-console + console.log(`${tag}: ${message}`); + + if (data) { + const tabIndent = 4; + // eslint-disable-next-line no-console + console.log(`${JSON.stringify(data, null, tabIndent)}`); + } + }; +} + +/** + * Usefull during UI development in Web environment. In App purchase plugin + * does not work in Web environment therefore we can use this util function + * to pupulate with mock product to develop UI with different product states + */ +export function generateMockInAppProducts(): IAPProduct[] { + const mockInAppProductSample: IAPProduct = { + id: 'string', + alias: 'string', + type: 'string', + state: 'string', + title: 'string', + description: 'string', + priceMicros: 0, + price: 'string', + currency: 'string', + loaded: true, + valid: true, + canPurchase: true, + owned: true, + finish: () => ({}), + verify: () => ({}), + set: (_: string, __: any) => ({}), + stateChanged: () => ({}), + on: (_: string, __: any) => ({}), + once: (_: string, __: any) => ({}), + off: (_: any) => ({}), + trigger: (_: string, __: any) => ({}), + }; + + return [ + { + ...mockInAppProductSample, + id: CaptureInAppProductIds.BRONZE_PACK, + title: 'Bronze Pack', + price: '0.99', + currency: 'USD', + canPurchase: true, + state: 'valid', + type: 'CONSUMABLE', + }, + { + ...mockInAppProductSample, + + id: CaptureInAppProductIds.SLIVER_PACK, + title: 'Silver Pack', + price: '1.99', + state: 'valid', + canPurchase: false, + currency: 'USD', + type: 'CONSUMABLE', + }, + { + ...mockInAppProductSample, + id: CaptureInAppProductIds.GOLD_PACK, + title: 'Gold Pack', + price: '2.99', + state: 'valid', + currency: 'USD', + type: 'CONSUMABLE', + canPurchase: true, + }, + { + ...mockInAppProductSample, + id: CaptureInAppProductIds.PLATINUM_PACK, + title: 'Platinum Pack', + price: '3.99', + state: 'valid', + currency: 'USD', + type: 'CONSUMABLE', + canPurchase: true, + }, + ]; +} From 00fcfb36127692353ad35ea9d38a60931bcc1b6b Mon Sep 17 00:00:00 2001 From: sultanmyrza Date: Mon, 20 Jun 2022 19:53:58 +0800 Subject: [PATCH 30/41] add dia-backend-num service --- .../num/dia-backend-num.service.spec.ts | 17 +++++ .../num/dia-backend-num.service.ts | 73 +++++++++++++++++++ 2 files changed, 90 insertions(+) create mode 100644 src/app/shared/dia-backend/num/dia-backend-num.service.spec.ts create mode 100644 src/app/shared/dia-backend/num/dia-backend-num.service.ts diff --git a/src/app/shared/dia-backend/num/dia-backend-num.service.spec.ts b/src/app/shared/dia-backend/num/dia-backend-num.service.spec.ts new file mode 100644 index 000000000..39f47506b --- /dev/null +++ b/src/app/shared/dia-backend/num/dia-backend-num.service.spec.ts @@ -0,0 +1,17 @@ +import { TestBed } from '@angular/core/testing'; +import { SharedModule } from '../../shared.module'; + +import { DiaBackendNumService } from './dia-backend-num.service'; + +describe('DiaBackendNumService', () => { + let service: DiaBackendNumService; + + beforeEach(() => { + TestBed.configureTestingModule({ imports: [SharedModule] }); + service = TestBed.inject(DiaBackendNumService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/src/app/shared/dia-backend/num/dia-backend-num.service.ts b/src/app/shared/dia-backend/num/dia-backend-num.service.ts new file mode 100644 index 000000000..1c902310d --- /dev/null +++ b/src/app/shared/dia-backend/num/dia-backend-num.service.ts @@ -0,0 +1,73 @@ +import { HttpClient } from '@angular/common/http'; +import { Injectable } from '@angular/core'; +import { defer } from 'rxjs'; +import { concatMap } from 'rxjs/operators'; +import { DiaBackendAuthService } from '../auth/dia-backend-auth.service'; +import { BASE_URL, BUBBLE_API_URL } from '../secret'; + +@Injectable({ + providedIn: 'root', +}) +export class DiaBackendNumService { + constructor( + private readonly httpClient: HttpClient, + private readonly authService: DiaBackendAuthService + ) {} + + purchaseNumPoints$(pointsToAdd: number, receiptId: string) { + return defer(() => this.authService.getAuthHeadersWithApiKey()).pipe( + concatMap(headers => { + const formData = new FormData(); + formData.set('points', pointsToAdd.toString()); + formData.set('receipt_id', receiptId); + return this.httpClient.post( + `${BASE_URL}/api/v3/num/points/purchase/`, + formData, + { headers } + ); + }) + ); + } + + numPointsPriceList$() { + // ask @ethan wu to change bubble endpoint from POST to GET + return defer(() => + this.httpClient.post( + `${BUBBLE_API_URL}/version-num-points-price-list/api/1.1/wf/num-points-price-list`, + {} + ) + ); + } +} + +export interface DiaBackendNumPointPurchaseResult { + id: string; + user: string; + type: + | 'airdrop' + | 'purchase' + | 'user_reward' + | 'order' + | 'network_app_order' + | 'existing'; + event_identifier: string; + receipt_id: string; + points: string; + spent: string; + created_at: string; + expired_at: string; +} + +export interface NumPointPrice { + id: number; + inAppPurchaseId: string; + // ask @ethan wu to fix type in rest api + quantitiy: number; +} + +export interface NumPointPriceListResponse { + status: 'success' | string; + response: { + price_list: NumPointPrice[]; + }; +} From 667fe19474919f98d55ae2725cdb77418d738c70 Mon Sep 17 00:00:00 2001 From: sultanmyrza Date: Mon, 20 Jun 2022 19:54:48 +0800 Subject: [PATCH 31/41] add in-app-store service --- .../in-app-store/in-app-store.service.spec.ts | 21 ++ .../in-app-store/in-app-store.service.ts | 255 ++++++++++++++++++ 2 files changed, 276 insertions(+) create mode 100644 src/app/shared/in-app-store/in-app-store.service.spec.ts create mode 100644 src/app/shared/in-app-store/in-app-store.service.ts diff --git a/src/app/shared/in-app-store/in-app-store.service.spec.ts b/src/app/shared/in-app-store/in-app-store.service.spec.ts new file mode 100644 index 000000000..37e304a94 --- /dev/null +++ b/src/app/shared/in-app-store/in-app-store.service.spec.ts @@ -0,0 +1,21 @@ +import { TestBed } from '@angular/core/testing'; +import { InAppPurchase2 } from '@awesome-cordova-plugins/in-app-purchase-2/ngx'; +import { SharedModule } from '../shared.module'; + +import { InAppStoreService } from './in-app-store.service'; + +describe('InAppStoreService', () => { + let service: InAppStoreService; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [SharedModule], + providers: [InAppPurchase2], + }); + service = TestBed.inject(InAppStoreService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/src/app/shared/in-app-store/in-app-store.service.ts b/src/app/shared/in-app-store/in-app-store.service.ts new file mode 100644 index 000000000..aa266bcd2 --- /dev/null +++ b/src/app/shared/in-app-store/in-app-store.service.ts @@ -0,0 +1,255 @@ +import { Injectable, OnDestroy } from '@angular/core'; +import { + IAPError, + IAPProduct, + InAppPurchase2, +} from '@awesome-cordova-plugins/in-app-purchase-2/ngx'; +import { Platform, ToastController } from '@ionic/angular'; +import { TranslocoService } from '@ngneat/transloco'; +import { BehaviorSubject, combineLatest } from 'rxjs'; +import { map } from 'rxjs/operators'; +import { + generateMockInAppProducts, + setupInAppPurchaseDebugPrint, +} from '../../utils/in-app-purchase'; +import { + DiaBackendNumService, + NumPointPrice, +} from '../dia-backend/num/dia-backend-num.service'; +import { ErrorService } from '../error/error.service'; + +@Injectable({ + providedIn: 'root', +}) +export class InAppStoreService implements OnDestroy { + debugPrint = setupInAppPurchaseDebugPrint('InAppStoreService'); + + readonly inAppProducts$ = new BehaviorSubject([]); + readonly numPointPricesById$ = new BehaviorSubject({}); + + readonly inAppProductsWithNumpoints$ = combineLatest([ + this.inAppProducts$, + this.numPointPricesById$, + ]).pipe( + map(([inAppProducts, numPointPricesById]) => { + return inAppProducts.map(inAppProduct => { + const numPoints = this.numPointsForProduct( + inAppProduct, + numPointPricesById + ); + return { inAppProduct, numPoints }; + }); + }) + ); + + private readonly appId = 'io.numbersprotocol.capturelite'; + + constructor( + private readonly store: InAppPurchase2, + private readonly platform: Platform, + private readonly errorService: ErrorService, + private readonly toastController: ToastController, + private readonly diaBackendNumService: DiaBackendNumService, + private readonly translocoService: TranslocoService + ) {} + + async initialize() { + // Usefull for UI development in WEB environment + if (!this.isNativePlatform()) { + this.refreshNumPointsPricing(); + const mockData = generateMockInAppProducts(); + this.inAppProducts$.next(mockData); + return; + } + + try { + await this.platform.ready(); + await this.refreshNumPointsPricing(); + + this.regiseterStoreListeners(); + this.registerStoreProducts(); + + this.store.refresh(); + } catch (error) { + const errorMessage = this.translocoService.getTranslation( + 'inAppPurchase.failedToInitInAppStore' + ); + this.errorService.toastError$(errorMessage).toPromise(); + } + } + + ngOnDestroy(): void { + this.unregisterStoreListeners(); + } + + async refreshNumPointsPricing() { + const result = await this.diaBackendNumService + .numPointsPriceList$() + .toPromise(); + const priceListFromRestApi = result.response.price_list; + + const numPointPricesById: NumPointPricesById = {}; + for (const item of priceListFromRestApi) { + numPointPricesById[item.inAppPurchaseId] = item; + } + + this.numPointPricesById$.next(numPointPricesById); + } + + purchase(product: IAPProduct) { + this.store.order(product); + } + + private async finishPurchase(inAppProduct: IAPProduct) { + const pointsToAdd = this.numPointsForProduct( + inAppProduct, + this.numPointPricesById$.value + ); + + let receipt; + if (inAppProduct.transaction?.type === 'ios-appstore') { + receipt = inAppProduct.transaction.appStoreReceipt; + } + if (inAppProduct.transaction?.type === 'android-playstore') { + receipt = inAppProduct.transaction.receipt; + } + if (!receipt) return; + + try { + await this.diaBackendNumService + .purchaseNumPoints$(pointsToAdd, receipt) + .toPromise(); + inAppProduct.finish(); + + this.notifyUser( + this.translocoService.translate('wallets.buyNum.pointsAdded', { + points: pointsToAdd, + }) + ); + } catch (error) { + const errorMessage = this.translocoService.getTranslation( + 'wallets.buyNum.failedToAddPoints' + ); + this.errorService.toastError$(errorMessage).toPromise(); + } + } + + private async notifyUser(message: string) { + return this.toastController + .create({ message, duration: 700 }) + .then(toast => toast.present()); + } + + private regiseterStoreListeners() { + this.store.error(this.onStoreError); + this.store.ready(this.onStoreReady); + this.store.when('product').approved(this.onStoreProductApproved); + this.store.when('product').updated(this.onStoreProductUpdated); + this.store.when('product').verified(this.onStoreProductVerified); + } + + private unregisterStoreListeners() { + this.store.off(this.onStoreError); + this.store.off(this.onStoreReady); + this.store.off(this.onStoreProductApproved); + this.store.off(this.onStoreProductUpdated); + this.store.off(this.onStoreProductVerified); + } + + private registerStoreProducts() { + const consumableProductIds = [ + CaptureInAppProductIds.BRONZE_PACK, + CaptureInAppProductIds.SLIVER_PACK, + CaptureInAppProductIds.GOLD_PACK, + CaptureInAppProductIds.PLATINUM_PACK, + ]; + const type = this.store.CONSUMABLE; + + for (const id of consumableProductIds) { + this.store.register({ id, type }); + } + } + + private readonly onStoreError = (_: IAPError) => { + const errorMessage = this.translocoService.getTranslation( + 'inAppPurchase.inAppPurchaseErrorOcurred' + ); + this.errorService.toastError$(errorMessage).toPromise(); + }; + + private readonly onStoreReady = () => { + const inAppProducts = this.store.products.filter( + product => this.shouldIgnoreProduct(product) === false + ); + this.inAppProducts$.next(inAppProducts); + }; + + private readonly onStoreProductUpdated = (updatedProduct: IAPProduct) => { + if (this.shouldIgnoreProduct(updatedProduct)) { + return; + } + + this.debugPrint('onStoreProductUpdated', updatedProduct); + + const inAppProducts = this.inAppProducts$.value.map(product => + product.id === updatedProduct.id ? updatedProduct : product + ); + + this.inAppProducts$.next(inAppProducts); + }; + + private readonly onStoreProductApproved = (product: IAPProduct) => { + if (this.shouldIgnoreProduct(product)) { + return; + } + this.debugPrint('onStoreProductApproved', product); + // TODO: in the future add validation logic here + product.verify(); + }; + + private readonly onStoreProductVerified = (product: IAPProduct) => { + if (this.shouldIgnoreProduct(product)) { + return; + } + this.debugPrint('onStoreProductVerified', product); + this.finishPurchase(product); + }; + + private shouldIgnoreProduct(product: IAPProduct) { + // For some reason on iOS there will be 1 in app product + // with product.id === io.numbersprotocol.capturelite + // we should ignore that product + return product.id === this.appId; + } + + // eslint-disable-next-line class-methods-use-this + private numPointsForProduct( + product: IAPProduct, + numPriceListById: NumPointPricesById + ) { + if (product.id in numPriceListById) { + return numPriceListById[product.id].quantitiy; + } + return 0; + } + + private isNativePlatform() { + return this.platform.is('hybrid'); + } +} + +export enum CaptureInAppProductIds { + BRONZE_PACK = 'cap_lite_consumable_bronze_pack_099', + SLIVER_PACK = 'cap_lite_consumable_silver_pack_199', + GOLD_PACK = 'cap_lite_consumable_gold_pack_299', + PLATINUM_PACK = 'cap_lite_consumable_platinum_pack_399', +} + +interface InAppProductsWithNumPoint { + inAppProduct: IAPProduct; + numPoints: number; +} + +interface NumPointPricesById { + [id: string]: NumPointPrice; +} From 6a637a1e9e8fd51d7021e2785b216c7ea27f318d Mon Sep 17 00:00:00 2001 From: sultanmyrza Date: Mon, 20 Jun 2022 20:03:23 +0800 Subject: [PATCH 32/41] add buy num page for in-app purchases --- .../wallets/buy-num/buy-num-routing.module.ts | 17 ++++ .../wallets/buy-num/buy-num.module.ts | 11 +++ .../wallets/buy-num/buy-num.page.html | 49 +++++++++++ .../wallets/buy-num/buy-num.page.scss | 85 +++++++++++++++++++ .../wallets/buy-num/buy-num.page.spec.ts | 34 ++++++++ .../features/wallets/buy-num/buy-num.page.ts | 52 ++++++++++++ .../wallets/wallets-routing.module.ts | 5 ++ src/app/features/wallets/wallets.page.html | 6 +- src/app/features/wallets/wallets.page.ts | 4 + src/assets/i18n/en-us.json | 4 +- src/assets/i18n/zh-tw.json | 4 +- 11 files changed, 266 insertions(+), 5 deletions(-) create mode 100644 src/app/features/wallets/buy-num/buy-num-routing.module.ts create mode 100644 src/app/features/wallets/buy-num/buy-num.module.ts create mode 100644 src/app/features/wallets/buy-num/buy-num.page.html create mode 100644 src/app/features/wallets/buy-num/buy-num.page.scss create mode 100644 src/app/features/wallets/buy-num/buy-num.page.spec.ts create mode 100644 src/app/features/wallets/buy-num/buy-num.page.ts diff --git a/src/app/features/wallets/buy-num/buy-num-routing.module.ts b/src/app/features/wallets/buy-num/buy-num-routing.module.ts new file mode 100644 index 000000000..95b19411f --- /dev/null +++ b/src/app/features/wallets/buy-num/buy-num-routing.module.ts @@ -0,0 +1,17 @@ +import { NgModule } from '@angular/core'; +import { Routes, RouterModule } from '@angular/router'; + +import { BuyNumPage } from './buy-num.page'; + +const routes: Routes = [ + { + path: '', + component: BuyNumPage, + }, +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule], +}) +export class BuyNumPageRoutingModule {} diff --git a/src/app/features/wallets/buy-num/buy-num.module.ts b/src/app/features/wallets/buy-num/buy-num.module.ts new file mode 100644 index 000000000..c3981ec40 --- /dev/null +++ b/src/app/features/wallets/buy-num/buy-num.module.ts @@ -0,0 +1,11 @@ +import { NgModule } from '@angular/core'; + +import { SharedModule } from '../../../shared/shared.module'; +import { BuyNumPageRoutingModule } from './buy-num-routing.module'; +import { BuyNumPage } from './buy-num.page'; + +@NgModule({ + imports: [SharedModule, BuyNumPageRoutingModule], + declarations: [BuyNumPage], +}) +export class BuyNumPageModule {} diff --git a/src/app/features/wallets/buy-num/buy-num.page.html b/src/app/features/wallets/buy-num/buy-num.page.html new file mode 100644 index 000000000..0120f7708 --- /dev/null +++ b/src/app/features/wallets/buy-num/buy-num.page.html @@ -0,0 +1,49 @@ + + + {{ t('wallets.buyNum.buyNum') }} + + + + + + In App Products are not available yet. Try again later + + + + + +
+
+ {{ product.inAppProduct.title }} + + +
+
+ {{ product.inAppProduct.price }} +
+
+
+ + {{ t('wallets.buyNum.buyPoints', { points: product.numPoints }) }} + + +
+
+
+
diff --git a/src/app/features/wallets/buy-num/buy-num.page.scss b/src/app/features/wallets/buy-num/buy-num.page.scss new file mode 100644 index 000000000..957e7b4a0 --- /dev/null +++ b/src/app/features/wallets/buy-num/buy-num.page.scss @@ -0,0 +1,85 @@ +mat-toolbar { + span { + padding-right: 40px; + } +} + +ion-card { + border-radius: 8px; +} + +ion-item { + --background: white; + --inner-padding-top: 0; +} + +ion-avatar { + width: 24px; + height: 24px; +} + +.in-app-product-description { + display: flex; + flex-direction: column; + justify-content: center; + min-height: 42px; +} + +.title-text { + font-weight: 700; + font-size: 18px; + color: black; + + @media (max-width: 360px) { + font-size: 16px; + } +} + +.title-text-large { + font-weight: 700; + font-size: 19px; + color: black; + + @media (max-width: 360px) { + font-size: 18px; + } +} + +ion-icon.num-point-info { + transform: translate(4px, 4px); + + @media (max-width: 360px) { + transform: translateY(4px); + } +} + +.subtitle-text { + color: black; + font-weight: 700; + font-size: 18px; + + @media (max-width: 360px) { + font-size: 16px; + } +} + +ion-button { + font-weight: 700; + font-size: 14px; + min-width: 140px; + + --border-radius: 8px; + --color: #00a84d; + --border-color: #00a84d; +} + +ion-button[slot='end'] { + margin: 0 0 0 4px; + padding: 4px; +} + +ion-spinner { + margin-left: 4px; + width: 12px; + height: 12px; +} diff --git a/src/app/features/wallets/buy-num/buy-num.page.spec.ts b/src/app/features/wallets/buy-num/buy-num.page.spec.ts new file mode 100644 index 000000000..a55dc6b13 --- /dev/null +++ b/src/app/features/wallets/buy-num/buy-num.page.spec.ts @@ -0,0 +1,34 @@ +import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; +import { InAppPurchase2 } from '@awesome-cordova-plugins/in-app-purchase-2/ngx'; +import { SharedTestingModule } from '../../../shared/shared-testing.module'; + +import { BuyNumPage } from './buy-num.page'; + +describe('BuyNumPage', () => { + let component: BuyNumPage; + let fixture: ComponentFixture; + + beforeEach( + waitForAsync(() => { + const iap2SpyMethods = ['error', 'ready', 'when', 'refresh', 'off']; + const inAppPurchase2Spy = jasmine.createSpyObj( + 'InAppPurchase2', + iap2SpyMethods + ); + + TestBed.configureTestingModule({ + declarations: [BuyNumPage], + imports: [SharedTestingModule], + providers: [{ provide: InAppPurchase2, useValue: inAppPurchase2Spy }], + }).compileComponents(); + + fixture = TestBed.createComponent(BuyNumPage); + component = fixture.componentInstance; + fixture.detectChanges(); + }) + ); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/features/wallets/buy-num/buy-num.page.ts b/src/app/features/wallets/buy-num/buy-num.page.ts new file mode 100644 index 000000000..4230cb206 --- /dev/null +++ b/src/app/features/wallets/buy-num/buy-num.page.ts @@ -0,0 +1,52 @@ +import { ChangeDetectorRef, Component, OnInit } from '@angular/core'; +import { IAPProduct } from '@awesome-cordova-plugins/in-app-purchase-2/ngx'; +import { AlertController } from '@ionic/angular'; +import { TranslocoService } from '@ngneat/transloco'; +import { map, tap } from 'rxjs/operators'; +import { InAppStoreService } from '../../../shared/in-app-store/in-app-store.service'; + +@Component({ + selector: 'app-buy-num', + templateUrl: './buy-num.page.html', + styleUrls: ['./buy-num.page.scss'], +}) +export class BuyNumPage implements OnInit { + readonly tmpIcon = '../../../../assets/images/num-icon.svg'; + + readonly inAppProducts$ = this.store.inAppProductsWithNumpoints$.pipe( + tap(_ => this.ref.detectChanges()) + ); + + readonly totalProducts$ = this.store.inAppProductsWithNumpoints$.pipe( + map(products => products.length), + tap(_ => this.ref.detectChanges()) + ); + + constructor( + private readonly store: InAppStoreService, + private readonly ref: ChangeDetectorRef, + private readonly alertController: AlertController, + private readonly translocoService: TranslocoService + ) {} + + ngOnInit() { + this.store.refreshNumPointsPricing(); + } + + purchase(product: IAPProduct) { + this.store.purchase(product); + } + + async showNumPointsQuantity(numPoints: number) { + const info = this.translocoService.translate( + 'wallets.buyNum.thisPackageIncludesXNumPoints', + { points: numPoints } + ); + const okText = this.translocoService.translate('wallets.buyNum.okIGotIt'); + const alert = await this.alertController.create({ + header: info, + buttons: [okText], + }); + await alert.present(); + } +} diff --git a/src/app/features/wallets/wallets-routing.module.ts b/src/app/features/wallets/wallets-routing.module.ts index be5aa70a6..31859663d 100644 --- a/src/app/features/wallets/wallets-routing.module.ts +++ b/src/app/features/wallets/wallets-routing.module.ts @@ -12,6 +12,11 @@ const routes: Routes = [ loadChildren: () => import('./transfer/transfer.module').then(m => m.TransferPageModule), }, + { + path: 'buy-num', + loadChildren: () => + import('./buy-num/buy-num.module').then(m => m.BuyNumPageModule), + }, ]; @NgModule({ diff --git a/src/app/features/wallets/wallets.page.html b/src/app/features/wallets/wallets.page.html index 805ec224a..822d520dd 100644 --- a/src/app/features/wallets/wallets.page.html +++ b/src/app/features/wallets/wallets.page.html @@ -49,13 +49,13 @@

NUM

> - + > Date: Mon, 20 Jun 2022 20:03:57 +0800 Subject: [PATCH 33/41] init in app purchases at app start --- src/app/app.component.spec.ts | 15 ++++++++++++++- src/app/app.component.ts | 3 +++ src/app/app.module.ts | 2 ++ 3 files changed, 19 insertions(+), 1 deletion(-) diff --git a/src/app/app.component.spec.ts b/src/app/app.component.spec.ts index b47d0a43b..d535634eb 100644 --- a/src/app/app.component.spec.ts +++ b/src/app/app.component.spec.ts @@ -1,6 +1,7 @@ import { HttpClientTestingModule } from '@angular/common/http/testing'; import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; import { TestBed, waitForAsync } from '@angular/core/testing'; +import { InAppPurchase2 } from '@awesome-cordova-plugins/in-app-purchase-2/ngx'; import { Platform } from '@ionic/angular'; import { AppComponent } from './app.component'; import { CapacitorPluginsTestingModule } from './shared/capacitor-plugins/capacitor-plugins-testing.module'; @@ -9,15 +10,24 @@ import { MaterialTestingModule } from './shared/material/material-testing.module describe('AppComponent', () => { let platformReadySpy: Promise; + let platformIsSpy: boolean | undefined; let platformSpy: Platform; beforeEach( waitForAsync(() => { platformReadySpy = Promise.resolve(); + platformIsSpy = false; platformSpy = jasmine.createSpyObj('Platform', { ready: platformReadySpy, + is: platformIsSpy, }); + const iap2SpyMethods = ['error', 'ready', 'when', 'refresh', 'off']; + const inAppPurchase2Spy = jasmine.createSpyObj( + 'InAppPurchase2', + iap2SpyMethods + ); + TestBed.configureTestingModule({ declarations: [AppComponent], schemas: [CUSTOM_ELEMENTS_SCHEMA], @@ -27,7 +37,10 @@ describe('AppComponent', () => { getTranslocoTestingModule(), MaterialTestingModule, ], - providers: [{ provide: Platform, useValue: platformSpy }], + providers: [ + { provide: Platform, useValue: platformSpy }, + { provide: InAppPurchase2, useValue: inAppPurchase2Spy }, + ], }).compileComponents(); }) ); diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 735edf1cc..2bceb0093 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -14,6 +14,7 @@ import { DiaBackendAssetUploadingService } from './shared/dia-backend/asset/uplo import { DiaBackendAuthService } from './shared/dia-backend/auth/dia-backend-auth.service'; import { DiaBackendNotificationService } from './shared/dia-backend/notification/dia-backend-notification.service'; import { ErrorService } from './shared/error/error.service'; +import { InAppStoreService } from './shared/in-app-store/in-app-store.service'; import { LanguageService } from './shared/language/service/language.service'; import { NotificationService } from './shared/notification/notification.service'; import { PushNotificationService } from './shared/push-notification/push-notification.service'; @@ -35,6 +36,7 @@ export class AppComponent { private readonly captureService: CaptureService, private readonly cameraService: CameraService, private readonly errorService: ErrorService, + private readonly inAppStoreService: InAppStoreService, notificationService: NotificationService, pushNotificationService: PushNotificationService, langaugeService: LanguageService, @@ -51,6 +53,7 @@ export class AppComponent { .initialize$() .pipe(untilDestroyed(this)) .subscribe(); + this.inAppStoreService.initialize(); this.initializeApp(); this.restoreAppState(); this.initializeCollector(); diff --git a/src/app/app.module.ts b/src/app/app.module.ts index ae93490a6..7d2b1982e 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -3,6 +3,7 @@ import { MAT_SNACK_BAR_DEFAULT_OPTIONS } from '@angular/material/snack-bar'; import { BrowserModule } from '@angular/platform-browser'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { RouteReuseStrategy } from '@angular/router'; +import { InAppPurchase2 } from '@awesome-cordova-plugins/in-app-purchase-2/ngx'; import { IonicModule, IonicRouteStrategy } from '@ionic/angular'; import { FormlyModule } from '@ngx-formly/core'; import { FormlyMaterialModule } from '@ngx-formly/material'; @@ -35,6 +36,7 @@ import { SharedModule } from './shared/shared.module'; provide: MAT_SNACK_BAR_DEFAULT_OPTIONS, useValue: { duration: 2500 }, }, + InAppPurchase2, ], bootstrap: [AppComponent], }) From 854a2293022f21cda16c2e77ed5b600268625efa Mon Sep 17 00:00:00 2001 From: James Chien Date: Tue, 21 Jun 2022 10:09:39 +0800 Subject: [PATCH 34/41] ci(.github/workflows): add BUBBLE_API_URL build env var --- .github/workflows/build-apks.yml | 1 + .github/workflows/build.yml | 2 ++ .github/workflows/pre-release.yml | 4 ++++ .github/workflows/uiux-release.yml | 1 + 4 files changed, 8 insertions(+) diff --git a/.github/workflows/build-apks.yml b/.github/workflows/build-apks.yml index d4820ccc1..4c008e378 100644 --- a/.github/workflows/build-apks.yml +++ b/.github/workflows/build-apks.yml @@ -54,6 +54,7 @@ jobs: NUMBERS_STORAGE_BASE_URL: ${{ secrets[matrix.storage_base_url] }} NUMBERS_STORAGE_TRUSTED_CLIENT_KEY: ${{ secrets[matrix.storage_trusted_client_key] }} NUMBERS_BUBBLE_DB_URL: ${{ secrets[matrix.bubble_db_url] }} + BUBBLE_API_URL: ${{ secrets.BUBBLE_API_URL }} run: | npm install -g @ionic/cli npm install diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 10926e241..2406bdaa5 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -76,6 +76,7 @@ jobs: NUMBERS_STORAGE_BASE_URL: ${{ secrets.NUMBERS_STORAGE_BASE_URL }} NUMBERS_STORAGE_TRUSTED_CLIENT_KEY: ${{ secrets.NUMBERS_STORAGE_TRUSTED_CLIENT_KEY }} NUMBERS_BUBBLE_DB_URL: ${{ secrets.NUMBERS_BUBBLE_DB_URL }} + BUBBLE_API_URL: ${{ secrets.BUBBLE_API_URL }} run: npm run build - name: Build Android @@ -109,6 +110,7 @@ jobs: NUMBERS_STORAGE_BASE_URL: ${{ secrets.NUMBERS_STORAGE_BASE_URL }} NUMBERS_STORAGE_TRUSTED_CLIENT_KEY: ${{ secrets.NUMBERS_STORAGE_TRUSTED_CLIENT_KEY }} NUMBERS_BUBBLE_DB_URL: ${{ secrets.NUMBERS_BUBBLE_DB_URL }} + BUBBLE_API_URL: ${{ secrets.BUBBLE_API_URL }} run: npm run build - name: Import the Code-Signing PKCS12 Certificate diff --git a/.github/workflows/pre-release.yml b/.github/workflows/pre-release.yml index 2e2b7e501..bf692d6ed 100644 --- a/.github/workflows/pre-release.yml +++ b/.github/workflows/pre-release.yml @@ -30,6 +30,7 @@ jobs: NUMBERS_STORAGE_BASE_URL: ${{ secrets.NUMBERS_STORAGE_BASE_URL }} NUMBERS_STORAGE_TRUSTED_CLIENT_KEY: ${{ secrets.NUMBERS_STORAGE_TRUSTED_CLIENT_KEY }} NUMBERS_BUBBLE_DB_URL: ${{ secrets.NUMBERS_BUBBLE_DB_URL }} + BUBBLE_API_URL: ${{ secrets.BUBBLE_API_URL }} run: | npm install -g @ionic/cli npm install @@ -90,6 +91,7 @@ jobs: NUMBERS_STORAGE_BASE_URL: ${{ secrets.NUMBERS_STORAGE_BASE_URL }} NUMBERS_STORAGE_TRUSTED_CLIENT_KEY: ${{ secrets.NUMBERS_STORAGE_TRUSTED_CLIENT_KEY }} NUMBERS_BUBBLE_DB_URL: ${{ secrets.NUMBERS_BUBBLE_DB_URL }} + BUBBLE_API_URL: ${{ secrets.BUBBLE_API_URL }} run: | npm install -g @ionic/cli npm install @@ -199,6 +201,7 @@ jobs: NUMBERS_STORAGE_BASE_URL: ${{ secrets[matrix.storage_base_url] }} NUMBERS_STORAGE_TRUSTED_CLIENT_KEY: ${{ secrets[matrix.storage_trusted_client_key] }} NUMBERS_BUBBLE_DB_URL: ${{ secrets[matrix.bubble_db_url] }} + BUBBLE_API_URL: ${{ secrets.BUBBLE_API_URL }} run: | npm install -g @ionic/cli npm install @@ -242,6 +245,7 @@ jobs: NUMBERS_STORAGE_BASE_URL: ${{ secrets.NUMBERS_STORAGE_BASE_URL }} NUMBERS_STORAGE_TRUSTED_CLIENT_KEY: ${{ secrets.NUMBERS_STORAGE_TRUSTED_CLIENT_KEY }} NUMBERS_BUBBLE_DB_URL: ${{ secrets.NUMBERS_BUBBLE_DB_URL }} + BUBBLE_API_URL: ${{ secrets.BUBBLE_API_URL }} run: | npm install -g @ionic/cli npm install diff --git a/.github/workflows/uiux-release.yml b/.github/workflows/uiux-release.yml index 8cb6516a9..e7603f49f 100644 --- a/.github/workflows/uiux-release.yml +++ b/.github/workflows/uiux-release.yml @@ -20,6 +20,7 @@ jobs: NUMBERS_STORAGE_BASE_URL: ${{ secrets.NUMBERS_STORAGE_BASE_URL }} NUMBERS_STORAGE_TRUSTED_CLIENT_KEY: ${{ secrets.NUMBERS_STORAGE_TRUSTED_CLIENT_KEY }} NUMBERS_BUBBLE_DB_URL: ${{ secrets.NUMBERS_BUBBLE_DB_URL }} + BUBBLE_API_URL: ${{ secrets.BUBBLE_API_URL }} run: | npm install -g @ionic/cli npm install From b464c68054559a2aa6bb17b0a25f09ea16c0c4d0 Mon Sep 17 00:00:00 2001 From: James Chien Date: Tue, 21 Jun 2022 10:17:42 +0800 Subject: [PATCH 35/41] build(set-secret.js): add BUBBLE_API_URL in secret.js generation --- set-secret.js | 1 + 1 file changed, 1 insertion(+) diff --git a/set-secret.js b/set-secret.js index 0ff04ccfb..e16d5bf77 100644 --- a/set-secret.js +++ b/set-secret.js @@ -8,6 +8,7 @@ const envConfigFile = ` export const BASE_URL = '${process.env.NUMBERS_STORAGE_BASE_URL}'; export const TRUSTED_CLIENT_KEY = '${process.env.NUMBERS_STORAGE_TRUSTED_CLIENT_KEY}'; export const BUBBLE_DB_URL = '${process.env.NUMBERS_BUBBLE_DB_URL}'; +export const BUBBLE_API_URL = '${process.env.BUBBLE_API_URL}'; export const APPS_FLYER_DEV_KEY = '${process.env.APPS_FLYER_DEV_KEY}' `; fs.writeFile(targetPath, envConfigFile, err => { From 68f40841bd729fb19d411297502a7b43b33ee74a Mon Sep 17 00:00:00 2001 From: sultanmyrza Date: Tue, 21 Jun 2022 12:16:02 +0800 Subject: [PATCH 36/41] fix(buy-num.page): add missing translation --- src/app/features/wallets/buy-num/buy-num.page.html | 5 ++--- src/assets/i18n/en-us.json | 3 ++- src/assets/i18n/zh-tw.json | 3 ++- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/app/features/wallets/buy-num/buy-num.page.html b/src/app/features/wallets/buy-num/buy-num.page.html index 0120f7708..1cdb35665 100644 --- a/src/app/features/wallets/buy-num/buy-num.page.html +++ b/src/app/features/wallets/buy-num/buy-num.page.html @@ -7,8 +7,8 @@ - - In App Products are not available yet. Try again later + + {{ t('wallets.buyNum.inAppProductsNotAvailableYetPleaseTryAgainLater') }} @@ -19,7 +19,6 @@ [ngClass]="{ 'title-text': !first, 'title-text-large': first }" > {{ product.inAppProduct.title }} - Date: Tue, 21 Jun 2022 12:16:32 +0800 Subject: [PATCH 37/41] fix(set-secret.js): add missing env var for BUBBLE_API_URL --- set-secret.js | 1 + 1 file changed, 1 insertion(+) diff --git a/set-secret.js b/set-secret.js index 0ff04ccfb..cac6683d5 100644 --- a/set-secret.js +++ b/set-secret.js @@ -9,6 +9,7 @@ export const BASE_URL = '${process.env.NUMBERS_STORAGE_BASE_URL}'; export const TRUSTED_CLIENT_KEY = '${process.env.NUMBERS_STORAGE_TRUSTED_CLIENT_KEY}'; export const BUBBLE_DB_URL = '${process.env.NUMBERS_BUBBLE_DB_URL}'; export const APPS_FLYER_DEV_KEY = '${process.env.APPS_FLYER_DEV_KEY}' +export const BUBBLE_API_URL = '${process.env.BUBBLE_API_URL}' `; fs.writeFile(targetPath, envConfigFile, err => { if (err) { From f0d244c7d2b49fa5ef907af59bd7c576c3046dec Mon Sep 17 00:00:00 2001 From: sultanmyrza Date: Tue, 21 Jun 2022 12:17:58 +0800 Subject: [PATCH 38/41] fix(buy-num.page): catch error for refreshNumPointsPricing --- .../features/wallets/buy-num/buy-num.page.ts | 16 +++++++++++-- .../in-app-store/in-app-store.service.ts | 24 +++++++++++-------- 2 files changed, 28 insertions(+), 12 deletions(-) diff --git a/src/app/features/wallets/buy-num/buy-num.page.ts b/src/app/features/wallets/buy-num/buy-num.page.ts index 4230cb206..b96592c39 100644 --- a/src/app/features/wallets/buy-num/buy-num.page.ts +++ b/src/app/features/wallets/buy-num/buy-num.page.ts @@ -2,6 +2,7 @@ import { ChangeDetectorRef, Component, OnInit } from '@angular/core'; import { IAPProduct } from '@awesome-cordova-plugins/in-app-purchase-2/ngx'; import { AlertController } from '@ionic/angular'; import { TranslocoService } from '@ngneat/transloco'; +import { combineLatest } from 'rxjs'; import { map, tap } from 'rxjs/operators'; import { InAppStoreService } from '../../../shared/in-app-store/in-app-store.service'; @@ -13,11 +14,22 @@ import { InAppStoreService } from '../../../shared/in-app-store/in-app-store.ser export class BuyNumPage implements OnInit { readonly tmpIcon = '../../../../assets/images/num-icon.svg'; - readonly inAppProducts$ = this.store.inAppProductsWithNumpoints$.pipe( + readonly inAppProducts$ = combineLatest([ + this.store.inAppProductsWithNumpoints$, + this.store.numPointPricesById$, + ]).pipe( + map(([inAppProductsWithNumpoints, numPointPricesById]) => { + const totalProducts = inAppProductsWithNumpoints.length; + const totalPrices = Object.keys(numPointPricesById).length; + if (totalProducts === 0 || totalPrices === 0) { + return []; + } + return inAppProductsWithNumpoints; + }), tap(_ => this.ref.detectChanges()) ); - readonly totalProducts$ = this.store.inAppProductsWithNumpoints$.pipe( + readonly totalProducts$ = this.inAppProducts$.pipe( map(products => products.length), tap(_ => this.ref.detectChanges()) ); diff --git a/src/app/shared/in-app-store/in-app-store.service.ts b/src/app/shared/in-app-store/in-app-store.service.ts index aa266bcd2..df6259bc6 100644 --- a/src/app/shared/in-app-store/in-app-store.service.ts +++ b/src/app/shared/in-app-store/in-app-store.service.ts @@ -83,17 +83,21 @@ export class InAppStoreService implements OnDestroy { } async refreshNumPointsPricing() { - const result = await this.diaBackendNumService - .numPointsPriceList$() - .toPromise(); - const priceListFromRestApi = result.response.price_list; - - const numPointPricesById: NumPointPricesById = {}; - for (const item of priceListFromRestApi) { - numPointPricesById[item.inAppPurchaseId] = item; - } + try { + const result = await this.diaBackendNumService + .numPointsPriceList$() + .toPromise(); + const priceListFromRestApi = result.response.price_list; - this.numPointPricesById$.next(numPointPricesById); + const numPointPricesById: NumPointPricesById = {}; + for (const item of priceListFromRestApi) { + numPointPricesById[item.inAppPurchaseId] = item; + } + + this.numPointPricesById$.next(numPointPricesById); + } catch (_) { + this.numPointPricesById$.next({}); + } } purchase(product: IAPProduct) { From 0c675df94a3ad4d620e90cde1c3a5c3706bb685d Mon Sep 17 00:00:00 2001 From: sultanmyrza Date: Tue, 21 Jun 2022 13:11:08 +0800 Subject: [PATCH 39/41] fix: in-app purchase related translations --- src/assets/i18n/en-us.json | 4 ++-- src/assets/i18n/zh-tw.json | 18 +++++++++--------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/assets/i18n/en-us.json b/src/assets/i18n/en-us.json index 0c9f31252..6fd573d4c 100644 --- a/src/assets/i18n/en-us.json +++ b/src/assets/i18n/en-us.json @@ -290,9 +290,9 @@ "total": "Total", "calculateGasFee": "Calculate Gas", "buyNum": { - "buyNum": "Buy NUM", + "buyNum": "Buy NUM Points", "buyPoints": "Buy {{points}} Points", - "pointsAdded": "{{points}} points added", + "pointsAdded": "{{points}} NUM points added", "failedToAddPoints": "Failed to add points", "thisPackageIncludesXNumPoints": "This package include {{points}} NUM points.", "okIGotIt": "Ok, I got it", diff --git a/src/assets/i18n/zh-tw.json b/src/assets/i18n/zh-tw.json index 75ec09147..f9d03a331 100644 --- a/src/assets/i18n/zh-tw.json +++ b/src/assets/i18n/zh-tw.json @@ -290,13 +290,13 @@ "total": "總額", "calculateGasFee": "計算油費", "buyNum": { - "buyNum": "購買 NUM", - "buyPoints": "買{{points}}分", - "pointsAdded": "加了{{points}}分", - "failedToAddPoints": "加分失敗", - "thisPackageIncludesXNumPoints": "此套餐包括{{points}}個 NUM 分。", - "okIGotIt": "好,我知道了", - "inAppProductsNotAvailableYetPleaseTryAgainLater": "應用內產品尚不可用。 稍後再試。" + "buyNum": "購買 NUM 點數", + "buyPoints": "購買{{points}}點數", + "pointsAdded": "加值 {{points}} NUM 點數", + "failedToAddPoints": "加值失敗", + "thisPackageIncludesXNumPoints": "此方案包括{{points}}個 NUM 點數", + "okIGotIt": "好,我了解", + "inAppProductsNotAvailableYetPleaseTryAgainLater": "App 內購買目前無法使用,請稍後再試" } }, "invitation": { @@ -359,7 +359,7 @@ "updateNow": "現在更新" }, "inAppPurchase": { - "failedToInitInAppStore": "In-App Store 初始化期間發生錯誤。 請檢查您的互聯網連接質量。", - "inAppPurchaseErrorOcurred": "發生應用內購買錯誤。 請稍後再試。" + "failedToInitInAppStore": "App 購買功能發生錯誤,請檢查網路連線狀況", + "inAppPurchaseErrorOcurred": "發生 App 內購買錯誤,請稍後再試" } } From 9286ed908c94ca8acac8371215d7365e21851237 Mon Sep 17 00:00:00 2001 From: sultanmyrza Date: Tue, 21 Jun 2022 13:22:34 +0800 Subject: [PATCH 40/41] fix(set-secret.js): remove duplicate identifier BUBBLE_API_URL --- set-secret.js | 1 - 1 file changed, 1 deletion(-) diff --git a/set-secret.js b/set-secret.js index f46fbbca8..e16d5bf77 100644 --- a/set-secret.js +++ b/set-secret.js @@ -10,7 +10,6 @@ export const TRUSTED_CLIENT_KEY = '${process.env.NUMBERS_STORAGE_TRUSTED_CLIENT_ export const BUBBLE_DB_URL = '${process.env.NUMBERS_BUBBLE_DB_URL}'; export const BUBBLE_API_URL = '${process.env.BUBBLE_API_URL}'; export const APPS_FLYER_DEV_KEY = '${process.env.APPS_FLYER_DEV_KEY}' -export const BUBBLE_API_URL = '${process.env.BUBBLE_API_URL}' `; fs.writeFile(targetPath, envConfigFile, err => { if (err) { From eade538b8891d0ceb04425a04e97493714a9627f Mon Sep 17 00:00:00 2001 From: sultanmyrza Date: Tue, 21 Jun 2022 14:25:58 +0800 Subject: [PATCH 41/41] build: bump to 0.59.0 --- CHANGELOG.md | 13 +++++++++++++ android/app/build.gradle | 4 ++-- package-lock.json | 4 ++-- package.json | 2 +- 4 files changed, 18 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9057f70fc..1231d5cdd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,19 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## 0.59.0 - 2222-06-21 + +### Added + +- In App Purchase NUM points +- In App Upgrade nontification +- Integration with Apps Flyer SDK + +### Fixed + +- Fix phone verification error messages on phone submit +- Show capture options menu regardless of backend response + ## 0.58.2 - 2022-06-07 ### Fixed diff --git a/android/app/build.gradle b/android/app/build.gradle index 0f827f946..4631c191f 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -6,8 +6,8 @@ android { applicationId "io.numbersprotocol.capturelite" minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 402 - versionName "0.58.2" + versionCode 410 + versionName "0.59.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } buildFeatures { diff --git a/package-lock.json b/package-lock.json index 874662124..9259056b6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "capture-lite", - "version": "0.58.2", + "version": "0.59.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "capture-lite", - "version": "0.58.2", + "version": "0.59.0", "dependencies": { "@angular/animations": "^12.2.4", "@angular/cdk": "^12.2.4", diff --git a/package.json b/package.json index 2963a9071..b5c99717a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "capture-lite", - "version": "0.58.2", + "version": "0.59.0", "author": "numbersprotocol", "homepage": "https://numbersprotocol.io/", "scripts": {