From a7530d538a7eedced6db066e5f3d67b2e7cec9e9 Mon Sep 17 00:00:00 2001 From: Sean Wu Date: Wed, 13 Jan 2021 11:32:44 +0800 Subject: [PATCH 1/7] Add mutex lock when updating table. --- .../capacitor-filesystem-table.ts | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/app/shared/services/database/table/capacitor-filesystem-table/capacitor-filesystem-table.ts b/src/app/shared/services/database/table/capacitor-filesystem-table/capacitor-filesystem-table.ts index 28974eb63..2cfc17057 100644 --- a/src/app/shared/services/database/table/capacitor-filesystem-table/capacitor-filesystem-table.ts +++ b/src/app/shared/services/database/table/capacitor-filesystem-table/capacitor-filesystem-table.ts @@ -136,14 +136,16 @@ export class CapacitorFilesystemTable implements Table { } async update(newTuple: T, comparator: (x: T, y: T) => boolean) { - const afterDeletion = differenceWith( - this.tuples$.value, - [newTuple], - comparator - ); - this.tuples$.next(afterDeletion.concat(newTuple)); - await this.dumpJson(); - return newTuple; + return this.mutex.runExclusive(async () => { + const afterDeletion = differenceWith( + this.tuples$.value, + [newTuple], + comparator + ); + this.tuples$.next(afterDeletion.concat(newTuple)); + await this.dumpJson(); + return newTuple; + }); } private assertTuplesExist(tuples: T[], comparator: (x: T, y: T) => boolean) { From b1767bb32636fcb17994a7b436e220d1e429331d Mon Sep 17 00:00:00 2001 From: Sean Wu Date: Wed, 13 Jan 2021 11:33:10 +0800 Subject: [PATCH 2/7] Navigate up after deleting capture item. --- .../capture-details/capture-details.page.ts | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/app/features/home/capture-tab/capture-details/capture-details.page.ts b/src/app/features/home/capture-tab/capture-details/capture-details.page.ts index ba51de1b7..698f4c937 100644 --- a/src/app/features/home/capture-tab/capture-details/capture-details.page.ts +++ b/src/app/features/home/capture-tab/capture-details/capture-details.page.ts @@ -4,7 +4,15 @@ import { MatDialog } from '@angular/material/dialog'; import { ActivatedRoute, Router } from '@angular/router'; import { TranslocoService } from '@ngneat/transloco'; import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy'; -import { concatMap, map, shareReplay, switchMap, tap } from 'rxjs/operators'; +import { defer } from 'rxjs'; +import { + concatMap, + concatMapTo, + map, + shareReplay, + switchMap, + tap, +} from 'rxjs/operators'; import { BlockingActionService } from '../../../../shared/services/blocking-action/blocking-action.service'; import { ConfirmAlert } from '../../../../shared/services/confirm-alert/confirm-alert.service'; import { DiaBackendAuthService } from '../../../../shared/services/dia-backend/auth/dia-backend-auth.service'; @@ -96,7 +104,8 @@ export class CaptureDetailsPage { private async remove() { const action$ = this.proof$.pipe( - concatMap(proof => this.proofRepository.remove(proof)) + concatMap(proof => this.proofRepository.remove(proof)), + concatMapTo(defer(() => this.router.navigate(['..']))) ); const result = await this.confirmAlert.present(); if (result) { From 0aa6241a6c4748a17eeacb2a517a55349e5a5a90 Mon Sep 17 00:00:00 2001 From: Sean Wu Date: Wed, 13 Jan 2021 11:33:24 +0800 Subject: [PATCH 3/7] Remove notification when collecting and uploading proof. --- .../services/collector/collector.service.ts | 20 +------- .../dia-backend-asset-repository.service.ts | 10 +--- .../notification/notification-item.spec.ts | 42 ----------------- .../notification/notification-item.ts | 47 ------------------- .../notification/notification.service.spec.ts | 36 -------------- .../notification/notification.service.ts | 19 -------- src/assets/i18n/en-us.json | 4 -- src/assets/i18n/zh-tw.json | 4 -- 8 files changed, 2 insertions(+), 180 deletions(-) diff --git a/src/app/shared/services/collector/collector.service.ts b/src/app/shared/services/collector/collector.service.ts index a6c2219ec..662f7bf9b 100644 --- a/src/app/shared/services/collector/collector.service.ts +++ b/src/app/shared/services/collector/collector.service.ts @@ -1,8 +1,5 @@ import { Injectable } from '@angular/core'; -import { TranslocoService } from '@ngneat/transloco'; -import { defer } from 'rxjs'; import { ImageStore } from '../image-store/image-store.service'; -import { NotificationService } from '../notification/notification.service'; import { Assets, getSerializedSortedSignedTargets, @@ -11,7 +8,6 @@ import { SignedTargets, Truth, } from '../repositories/proof/proof'; -import { ProofRepository } from '../repositories/proof/proof-repository.service'; import { FactsProvider } from './facts/facts-provider'; import { SignatureProvider } from './signature/signature-provider'; @@ -22,23 +18,9 @@ export class CollectorService { private readonly factsProviders = new Set(); private readonly signatureProviders = new Set(); - constructor( - private readonly notificationService: NotificationService, - private readonly translocoService: TranslocoService, - private readonly proofRepository: ProofRepository, - private readonly imageStore: ImageStore - ) {} + constructor(private readonly imageStore: ImageStore) {} async run(assets: Assets) { - return this.notificationService.notifyOnGoing( - defer(() => this._run(assets)), - this.translocoService.translate('storingAssets'), - this.translocoService.translate('message.storingAssets'), - true - ); - } - - private async _run(assets: Assets) { const truth = await this.collectTruth(assets); const signatures = await this.signTargets({ assets, truth }); return Proof.from(this.imageStore, assets, truth, signatures); diff --git a/src/app/shared/services/dia-backend/asset/dia-backend-asset-repository.service.ts b/src/app/shared/services/dia-backend/asset/dia-backend-asset-repository.service.ts index 1f1a7409c..cdee561ad 100644 --- a/src/app/shared/services/dia-backend/asset/dia-backend-asset-repository.service.ts +++ b/src/app/shared/services/dia-backend/asset/dia-backend-asset-repository.service.ts @@ -68,15 +68,7 @@ export class DiaBackendAssetRepository { ); } - async add(proof: Proof) { - return this.notificationService.notifyOnGoing( - this.createAsset$(proof), - this.translocoService.translate('registeringProof'), - this.translocoService.translate('message.registeringProof') - ); - } - - private createAsset$(proof: Proof) { + add$(proof: Proof) { return forkJoin([ defer(() => this.authService.getAuthHeaders()), defer(() => buildFormDataToCreateAsset(proof)), diff --git a/src/app/shared/services/notification/notification-item.spec.ts b/src/app/shared/services/notification/notification-item.spec.ts index f85141473..97f498bc2 100644 --- a/src/app/shared/services/notification/notification-item.spec.ts +++ b/src/app/shared/services/notification/notification-item.spec.ts @@ -3,7 +3,6 @@ import { TestBed } from '@angular/core/testing'; import { LocalNotificationsPlugin, Plugins } from '@capacitor/core'; import { TranslocoService } from '@ngneat/transloco'; -import { of, throwError } from 'rxjs'; import { LOCAL_NOTIFICATIONS_PLUGIN } from '../../../shared/core/capacitor-plugins/capacitor-plugins.module'; import { SharedTestingModule } from '../../../shared/shared-testing.module'; import { NotificationItem } from './notification-item'; @@ -35,47 +34,6 @@ describe('NotificationItem', () => { NotificationItem ); }); - - it('should be able to notify error', async () => { - spyOn(console, 'error'); - expect(await item.error(SAMPLE_ERROR)).toBeInstanceOf(Error); - }); - - it('should be able to cancel itself', async () => { - spyOn(console, 'log'); - expect(await item.cancel()).toBeInstanceOf(NotificationItem); - }); - - it('should be able to notify with on going action and cancel automatically', async () => { - spyOn(console, 'info'); - spyOn(localNotificationsPlugin, 'cancel').and.resolveTo(); - const expected = 2; - const result = await item.notifyOnGoing( - of(1, expected), - SAMPLE_TITLE, - SAMPLE_MESSAGE - ); - expect(localNotificationsPlugin.cancel).toHaveBeenCalled(); - expect(result).toEqual(expected); - }); - - it('should return an Error and do not cancel automatically when throw during on going action', async () => { - const expected = SAMPLE_ERROR; - spyOn(console, 'info'); - spyOn(localNotificationsPlugin, 'cancel').and.resolveTo(); - spyOn(item, 'error').and.resolveTo(expected); - try { - await item.notifyOnGoing( - throwError(expected), - SAMPLE_TITLE, - SAMPLE_MESSAGE - ); - } catch (err) { - expect(err).toEqual(expected); - } - expect(localNotificationsPlugin.cancel).not.toHaveBeenCalled(); - expect(item.error).toHaveBeenCalled(); - }); }); const SAMPLE_TITLE = 'SAMPLE_TITLE'; diff --git a/src/app/shared/services/notification/notification-item.ts b/src/app/shared/services/notification/notification-item.ts index 8f54c4b1d..0f6773c10 100644 --- a/src/app/shared/services/notification/notification-item.ts +++ b/src/app/shared/services/notification/notification-item.ts @@ -1,8 +1,6 @@ import { Inject } from '@angular/core'; import { LocalNotification, LocalNotificationsPlugin } from '@capacitor/core'; import { TranslocoService } from '@ngneat/transloco'; -import { Observable } from 'rxjs'; -import { last } from 'rxjs/operators'; import { LOCAL_NOTIFICATIONS_PLUGIN } from '../../../shared/core/capacitor-plugins/capacitor-plugins.module'; export class NotificationItem { @@ -18,51 +16,6 @@ export class NotificationItem { return this.schedule({ id: this.id, title, body }); } - async error(error: Error) { - console.error(error); - await this.schedule({ - id: this.id, - title: this.translocoService.translate('unknownError'), - body: JSON.stringify(error), - ongoing: false, - }); - return error; - } - - async cancel() { - this.localNotificationsPlugin.cancel({ - notifications: [{ id: String(this.id) }], - }); - return this; - } - - async notifyOnGoing( - action$: Observable, - title: string, - body: string, - swipable = false - ) { - console.info(`${title}: ${body}`); - const notification = await this.schedule({ - id: this.id, - title, - body, - ongoing: !swipable, - }); - return new Promise((resolve, reject) => { - action$.pipe(last()).subscribe({ - next(value) { - notification.cancel(); - resolve(value); - }, - error(err) { - notification.error(err); - reject(err); - }, - }); - }); - } - private async schedule(options: LocalNotification) { await this.localNotificationsPlugin.schedule({ notifications: [options], diff --git a/src/app/shared/services/notification/notification.service.spec.ts b/src/app/shared/services/notification/notification.service.spec.ts index e7c203582..aa449502b 100644 --- a/src/app/shared/services/notification/notification.service.spec.ts +++ b/src/app/shared/services/notification/notification.service.spec.ts @@ -2,7 +2,6 @@ import { TestBed } from '@angular/core/testing'; import { LocalNotificationsPlugin, Plugins } from '@capacitor/core'; -import { of, throwError } from 'rxjs'; import { LOCAL_NOTIFICATIONS_PLUGIN } from '../../../shared/core/capacitor-plugins/capacitor-plugins.module'; import { SharedTestingModule } from '../../../shared/shared-testing.module'; import { NotificationItem } from './notification-item'; @@ -36,41 +35,6 @@ describe('NotificationService', () => { NotificationItem ); }); - - it('should be able to notify error', async () => { - spyOn(console, 'error'); - expect(await service.error(SAMPLE_ERROR)).toBeInstanceOf(Error); - }); - - it('should be able to notify with on going action and cancel automatically', async () => { - spyOn(console, 'info'); - spyOn(localNotificationsPlugin, 'cancel').and.resolveTo(); - const expected = 2; - const result = await service.notifyOnGoing( - of(1, expected), - SAMPLE_TITLE, - SAMPLE_MESSAGE - ); - expect(localNotificationsPlugin.cancel).toHaveBeenCalled(); - expect(result).toEqual(expected); - }); - - it('should return an Error and do not cancel automatically when throw during on going action', async () => { - spyOn(console, 'info'); - spyOn(console, 'error'); - const expected = SAMPLE_ERROR; - spyOn(localNotificationsPlugin, 'cancel').and.resolveTo(); - try { - await service.notifyOnGoing( - throwError(expected), - SAMPLE_TITLE, - SAMPLE_MESSAGE - ); - } catch (err) { - expect(err).toEqual(expected); - } - expect(localNotificationsPlugin.cancel).not.toHaveBeenCalled(); - }); }); const SAMPLE_TITLE = 'SAMPLE_TITLE'; diff --git a/src/app/shared/services/notification/notification.service.ts b/src/app/shared/services/notification/notification.service.ts index 40a356040..d569e0fe0 100644 --- a/src/app/shared/services/notification/notification.service.ts +++ b/src/app/shared/services/notification/notification.service.ts @@ -1,7 +1,6 @@ import { Inject, Injectable } from '@angular/core'; import { LocalNotificationsPlugin } from '@capacitor/core'; import { TranslocoService } from '@ngneat/transloco'; -import { Observable } from 'rxjs'; import { LOCAL_NOTIFICATIONS_PLUGIN } from '../../../shared/core/capacitor-plugins/capacitor-plugins.module'; import { NotificationItem } from './notification-item'; @@ -37,22 +36,4 @@ export class NotificationService { async notify(title: string, body: string) { return this.createNotification().notify(title, body); } - - async error(error: Error) { - return this.createNotification().error(error); - } - - async notifyOnGoing( - action$: Observable, - title: string, - body: string, - swipable = false - ) { - return this.createNotification().notifyOnGoing( - action$, - title, - body, - swipable - ); - } } diff --git a/src/assets/i18n/en-us.json b/src/assets/i18n/en-us.json index e29a4bb96..5a66d2681 100644 --- a/src/assets/i18n/en-us.json +++ b/src/assets/i18n/en-us.json @@ -23,8 +23,6 @@ "emptyInbox": "Empty Inbox", "editCaption": "Edit Caption", "selectAPublisher": "Select a Publisher", - "registeringProof": "Registering Asset", - "storingAssets": "Storing Capture", "about": "About", "version": "Version", "informationProvider": "Information Provider", @@ -97,8 +95,6 @@ "accountNotActivated": "This account has not been activated yet.", "ifYouNotReceiveEmail": "If you haven't received the verification email in 10 minutes, you can click the button below to resend the verification email.", "tooManyRetries": "You have entered the invalid email or password too many times. Please try again later.", - "storingAssets": "Collecting environment variables, signing and storing assets...", - "registeringProof": "Registering the asset to Numbers DIA.", "forbiddenAllNumeric": "Cannot contain only numbers.", "isNotEmail": "Does not follow email format.", "pleaseWait": "Please wait...", diff --git a/src/assets/i18n/zh-tw.json b/src/assets/i18n/zh-tw.json index ff7538d80..d5d0e4816 100644 --- a/src/assets/i18n/zh-tw.json +++ b/src/assets/i18n/zh-tw.json @@ -23,8 +23,6 @@ "emptyInbox": "空收件匣", "editCaption": "編輯標題", "selectAPublisher": "選擇發佈者", - "registeringProof": "正在發佈拍攝", - "storingAssets": "正在儲存拍攝", "about": "關於", "version": "版本", "informationProvider": "資訊提供者", @@ -97,8 +95,6 @@ "accountNotActivated": "此帳戶尚未啟用。", "ifYouNotReceiveEmail": "如果您在10分鐘內仍未收到驗證電子郵件,請單擊下面的按鈕重新發送驗證電子郵件。", "tooManyRetries": "您輸入無效的電子郵件或密碼的次數過多。請稍後再試。", - "storingAssets": "正在蒐集環境資料、簽章並保存拍攝...", - "registeringProof": "正在將影像資產註冊至 Numbers DIA。", "forbiddenAllNumeric": "密碼不能只有數字。", "isNotEmail": "電子郵件格式不符。", "pleaseWait": "請稍候...", From ba9547e58dacc9e9b2014e8fbc515280a65d1dad Mon Sep 17 00:00:00 2001 From: Sean Wu Date: Wed, 13 Jan 2021 12:58:41 +0800 Subject: [PATCH 4/7] Implement stateful CaptureItem. --- .../capture-item/capture-item.component.html | 5 ++ .../capture-item/capture-item.component.scss | 46 ++++++++++++++ .../capture-item.component.spec.ts | 23 +++++++ .../capture-item/capture-item.component.ts | 61 +++++++++++++++++++ .../capture-tab/capture-tab.component.html | 21 +++---- .../capture-tab/capture-tab.component.scss | 43 ------------- .../home/capture-tab/capture-tab.component.ts | 29 +++++---- src/app/features/home/home.module.ts | 3 +- 8 files changed, 163 insertions(+), 68 deletions(-) create mode 100644 src/app/features/home/capture-tab/capture-item/capture-item.component.html create mode 100644 src/app/features/home/capture-tab/capture-item/capture-item.component.scss create mode 100644 src/app/features/home/capture-tab/capture-item/capture-item.component.spec.ts create mode 100644 src/app/features/home/capture-tab/capture-item/capture-item.component.ts diff --git a/src/app/features/home/capture-tab/capture-item/capture-item.component.html b/src/app/features/home/capture-tab/capture-item/capture-item.component.html new file mode 100644 index 000000000..d0de47476 --- /dev/null +++ b/src/app/features/home/capture-tab/capture-item/capture-item.component.html @@ -0,0 +1,5 @@ +
+ +
+ + diff --git a/src/app/features/home/capture-tab/capture-item/capture-item.component.scss b/src/app/features/home/capture-tab/capture-item/capture-item.component.scss new file mode 100644 index 000000000..7423e74f3 --- /dev/null +++ b/src/app/features/home/capture-tab/capture-item/capture-item.component.scss @@ -0,0 +1,46 @@ +:host { + display: block; + position: relative; + width: 100%; + height: 100%; + overflow: hidden; + border-radius: 4px; + background-color: lightgray; +} + +ion-icon { + position: absolute; + top: 50%; + left: 50%; + margin-right: -50%; + transform: translate(-50%, -50%); + z-index: 10; + opacity: 50%; + font-size: 24px; + color: white; +} + +.spinner-container { + position: absolute; + top: 50%; + left: 50%; + margin-right: -50%; + transform: translate(-50%, -50%); + z-index: 9; + + ion-spinner { + width: 48px; + height: 48px; + opacity: 50%; + + --color: white; + } +} + +ion-img { + width: 100%; + height: 100%; + object-fit: cover; + object-position: center; + background-color: lightgray; +} diff --git a/src/app/features/home/capture-tab/capture-item/capture-item.component.spec.ts b/src/app/features/home/capture-tab/capture-item/capture-item.component.spec.ts new file mode 100644 index 000000000..17af03712 --- /dev/null +++ b/src/app/features/home/capture-tab/capture-item/capture-item.component.spec.ts @@ -0,0 +1,23 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { SharedTestingModule } from '../../../../shared/shared-testing.module'; +import { CaptureItemComponent } from './capture-item.component'; + +describe('CaptureItemComponent', () => { + let component: CaptureItemComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [CaptureItemComponent], + imports: [SharedTestingModule], + }).compileComponents(); + + fixture = TestBed.createComponent(CaptureItemComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + })); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/features/home/capture-tab/capture-item/capture-item.component.ts b/src/app/features/home/capture-tab/capture-item/capture-item.component.ts new file mode 100644 index 000000000..cfa50fe06 --- /dev/null +++ b/src/app/features/home/capture-tab/capture-item/capture-item.component.ts @@ -0,0 +1,61 @@ +import { Component, Input } from '@angular/core'; +import { UntilDestroy } from '@ngneat/until-destroy'; +import { BehaviorSubject } from 'rxjs'; +import { map, switchMap } from 'rxjs/operators'; +import { getOldProof } from '../../../../shared/services/repositories/proof/old-proof-adapter'; +import { Proof } from '../../../../shared/services/repositories/proof/proof'; +import { isNonNullable } from '../../../../utils/rx-operators/rx-operators'; + +@UntilDestroy({ checkProperties: true }) +@Component({ + selector: 'app-capture-item', + templateUrl: './capture-item.component.html', + styleUrls: ['./capture-item.component.scss'], +}) +export class CaptureItemComponent { + @Input() + set item(value: CaptureItem) { + this._item$.next(value); + } + + // tslint:disable-next-line: rxjs-no-explicit-generics + private readonly _item$ = new BehaviorSubject( + undefined + ); + readonly item$ = this._item$.asObservable().pipe(isNonNullable()); + readonly thumbnailUrl$ = this.item$.pipe( + switchMap(item => item.getThumbnailUrl()) + ); + readonly willCollectTruth$ = this.item$.pipe( + map(item => item.proof?.willCollectTruth === true) + ); +} + +export class CaptureItem { + proof?: Proof; + private readonly createdTimestamp: number; + + get oldProofHash() { + if (this.proof) { + return getOldProof(this.proof).hash; + } + } + + get timestamp() { + if (this.proof) { + return this.proof.timestamp; + } + return this.createdTimestamp; + } + + constructor({ proof }: { proof?: Proof }) { + this.proof = proof; + this.createdTimestamp = Date.now(); + } + + async getThumbnailUrl() { + if (this.proof) { + return this.proof.getThumbnailUrl(); + } + } +} diff --git a/src/app/features/home/capture-tab/capture-tab.component.html b/src/app/features/home/capture-tab/capture-tab.component.html index 656ce31c0..092c4b0d1 100644 --- a/src/app/features/home/capture-tab/capture-tab.component.html +++ b/src/app/features/home/capture-tab/capture-tab.component.html @@ -1,25 +1,20 @@
{{ group.key }}
-
- -
- - +
diff --git a/src/app/features/home/capture-tab/capture-tab.component.scss b/src/app/features/home/capture-tab/capture-tab.component.scss index c327d6a3c..f803369be 100644 --- a/src/app/features/home/capture-tab/capture-tab.component.scss +++ b/src/app/features/home/capture-tab/capture-tab.component.scss @@ -9,46 +9,3 @@ div.mat-title { margin: 26px 0 0; font-size: large; } - -.capture-item { - height: 30vw; - overflow: hidden; - object-fit: cover; - border-radius: 4px; - - .spinner-container { - position: absolute; - top: 50%; - left: 50%; - margin-right: -50%; - transform: translate(-50%, -50%); - z-index: 9; - - ion-spinner { - width: 48px; - height: 48px; - opacity: 50%; - - --color: white; - } - } - - ion-icon { - position: absolute; - top: 50%; - left: 50%; - margin-right: -50%; - transform: translate(-50%, -50%); - z-index: 10; - opacity: 30%; - color: white; - font-size: 24px; - } - - img { - width: 100%; - height: 100%; - object-fit: cover; - object-position: inherit; - } -} diff --git a/src/app/features/home/capture-tab/capture-tab.component.ts b/src/app/features/home/capture-tab/capture-tab.component.ts index 2f093fa7f..31bf18864 100644 --- a/src/app/features/home/capture-tab/capture-tab.component.ts +++ b/src/app/features/home/capture-tab/capture-tab.component.ts @@ -2,8 +2,8 @@ import { formatDate, KeyValue } from '@angular/common'; import { Component } from '@angular/core'; import { groupBy } from 'lodash'; import { concatMap, map } from 'rxjs/operators'; -import { getOldProof } from '../../../shared/services/repositories/proof/old-proof-adapter'; import { ProofRepository } from '../../../shared/services/repositories/proof/proof-repository.service'; +import { CaptureItem } from './capture-item/capture-item.component'; @Component({ selector: 'app-capture-tab', @@ -15,17 +15,11 @@ export class CaptureTabComponent { readonly capturesByDate$ = this.proofs$.pipe( map(proofs => proofs.sort((a, b) => b.timestamp - a.timestamp)), concatMap(proofs => - Promise.all( - proofs.map(async proof => ({ - proof, - thumbnailUrl: await proof.getThumbnailUrl(), - oldProofHash: getOldProof(proof).hash, - })) - ) + Promise.all(proofs.map(async proof => new CaptureItem({ proof }))) ), - map(captures => - groupBy(captures, capture => - formatDate(capture.proof.timestamp, 'yyyy/MM/dd', 'en-US') + map(captureItems => + groupBy(captureItems, item => + formatDate(item.timestamp, 'yyyy/MM/dd', 'en-US') ) ) ); @@ -39,4 +33,17 @@ export class CaptureTabComponent { ): number { return a.key > b.key ? -1 : b.key > a.key ? 1 : 0; } + + // tslint:disable-next-line: prefer-function-over-method + trackCaptureGroupByDate( + _: number, + item: { key: string; value: CaptureItem[] } + ) { + return item.key; + } + + // tslint:disable-next-line: prefer-function-over-method + trackCaptureItem(_: number, item: CaptureItem) { + return `${item.oldProofHash}_${item.proof?.willCollectTruth}`; + } } diff --git a/src/app/features/home/home.module.ts b/src/app/features/home/home.module.ts index 35b2d91dc..1df576792 100644 --- a/src/app/features/home/home.module.ts +++ b/src/app/features/home/home.module.ts @@ -1,12 +1,13 @@ import { NgModule } from '@angular/core'; import { PostCaptureCardModule } from '../../shared/core/post-capture-card/post-capture-card.module'; import { SharedModule } from '../../shared/shared.module'; +import { CaptureItemComponent } from './capture-tab/capture-item/capture-item.component'; import { CaptureTabComponent } from './capture-tab/capture-tab.component'; import { HomePageRoutingModule } from './home-routing.module'; import { HomePage } from './home.page'; @NgModule({ imports: [SharedModule, HomePageRoutingModule, PostCaptureCardModule], - declarations: [HomePage, CaptureTabComponent], + declarations: [HomePage, CaptureTabComponent, CaptureItemComponent], }) export class HomePageModule {} From 2f6b97578414c9c861262922c2ea5f92483d9224 Mon Sep 17 00:00:00 2001 From: Sean Wu Date: Wed, 13 Jan 2021 13:40:02 +0800 Subject: [PATCH 5/7] Display an empty small image to avoid border of img without src. --- .../capture-tab/capture-item/capture-item.component.html | 8 +++++++- .../capture-tab/capture-item/capture-item.component.scss | 6 +++--- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/app/features/home/capture-tab/capture-item/capture-item.component.html b/src/app/features/home/capture-tab/capture-item/capture-item.component.html index d0de47476..b72448ec5 100644 --- a/src/app/features/home/capture-tab/capture-item/capture-item.component.html +++ b/src/app/features/home/capture-tab/capture-item/capture-item.component.html @@ -2,4 +2,10 @@ - + + diff --git a/src/app/features/home/capture-tab/capture-item/capture-item.component.scss b/src/app/features/home/capture-tab/capture-item/capture-item.component.scss index 7423e74f3..fc66c5c56 100644 --- a/src/app/features/home/capture-tab/capture-item/capture-item.component.scss +++ b/src/app/features/home/capture-tab/capture-item/capture-item.component.scss @@ -5,7 +5,7 @@ height: 100%; overflow: hidden; border-radius: 4px; - background-color: lightgray; + background-color: whitesmoke; } ion-icon { @@ -37,10 +37,10 @@ ion-icon { } } -ion-img { +img { width: 100%; height: 100%; object-fit: cover; object-position: center; - background-color: lightgray; + background-color: whitesmoke; } From a96a353793644d581ee259b0b3d63ce7b7251818 Mon Sep 17 00:00:00 2001 From: Sean Wu Date: Wed, 13 Jan 2021 13:43:02 +0800 Subject: [PATCH 6/7] Remove spinner in inbox and transactions page. --- src/app/features/home/inbox/inbox.page.html | 4 ---- src/app/features/home/transaction/transaction.page.html | 6 ------ 2 files changed, 10 deletions(-) diff --git a/src/app/features/home/inbox/inbox.page.html b/src/app/features/home/inbox/inbox.page.html index a28d493d4..11004f5a0 100644 --- a/src/app/features/home/inbox/inbox.page.html +++ b/src/app/features/home/inbox/inbox.page.html @@ -6,10 +6,6 @@
- -