diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts index 40a8e54d2..e62f5493c 100644 --- a/src/app/app-routing.module.ts +++ b/src/app/app-routing.module.ts @@ -14,6 +14,9 @@ const routes: Routes = [{ }, { path: 'proof', loadChildren: () => import('./pages/proof/proof.module').then(m => m.ProofPageModule) +}, { + path: 'information', + loadChildren: () => import('./pages/information/information.module').then(m => m.InformationPageModule) }]; @NgModule({ diff --git a/src/app/pages/information/information-routing.module.ts b/src/app/pages/information/information-routing.module.ts new file mode 100644 index 000000000..dc82f9a3e --- /dev/null +++ b/src/app/pages/information/information-routing.module.ts @@ -0,0 +1,14 @@ +import { NgModule } from '@angular/core'; +import { RouterModule, Routes } from '@angular/router'; +import { InformationPage } from './information.page'; + +const routes: Routes = [{ + path: '', + component: InformationPage +}]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule], +}) +export class InformationPageRoutingModule { } diff --git a/src/app/pages/information/information.module.ts b/src/app/pages/information/information.module.ts new file mode 100644 index 000000000..c0b55be49 --- /dev/null +++ b/src/app/pages/information/information.module.ts @@ -0,0 +1,19 @@ +import { CommonModule } from '@angular/common'; +import { NgModule } from '@angular/core'; +import { FormsModule } from '@angular/forms'; +import { IonicModule } from '@ionic/angular'; +import { TranslocoModule } from '@ngneat/transloco'; +import { InformationPageRoutingModule } from './information-routing.module'; +import { InformationPage } from './information.page'; + +@NgModule({ + imports: [ + CommonModule, + FormsModule, + IonicModule, + InformationPageRoutingModule, + TranslocoModule + ], + declarations: [InformationPage] +}) +export class InformationPageModule { } diff --git a/src/app/pages/information/information.page.html b/src/app/pages/information/information.page.html new file mode 100644 index 000000000..8ddba8475 --- /dev/null +++ b/src/app/pages/information/information.page.html @@ -0,0 +1,69 @@ + + + + + + + + {{ t('informationDetails') }} + + + + + + + + {{ t('location') }} + + + + + +

{{ information.name }}

+

{{ information.value }}

+
+
+
+
+
+
+
+ + + + + {{ t('other') }} + + + + + +

{{ information.name }}

+

{{ information.value }}

+
+
+
+
+
+
+
+ + + + + {{ t('device') }} + + + + + +

{{ information.name }}

+

{{ information.value }}

+
+
+
+
+
+
+
+
\ No newline at end of file diff --git a/src/app/pages/information/information.page.scss b/src/app/pages/information/information.page.scss new file mode 100644 index 000000000..410d97ad1 --- /dev/null +++ b/src/app/pages/information/information.page.scss @@ -0,0 +1,7 @@ +.slide-card { + width: 100%; +} + +.multiline { + white-space: pre-wrap; +} diff --git a/src/app/pages/information/information.page.spec.ts b/src/app/pages/information/information.page.spec.ts new file mode 100644 index 000000000..53322231a --- /dev/null +++ b/src/app/pages/information/information.page.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { IonicModule } from '@ionic/angular'; +import { getTranslocoModule } from 'src/app/transloco/transloco-root.module.spec'; +import { InformationPage } from './information.page'; + +describe('InformationPage', () => { + let component: InformationPage; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [InformationPage], + imports: [IonicModule.forRoot(), RouterTestingModule, getTranslocoModule()] + }).compileComponents(); + + fixture = TestBed.createComponent(InformationPage); + component = fixture.componentInstance; + fixture.detectChanges(); + })); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/pages/information/information.page.ts b/src/app/pages/information/information.page.ts new file mode 100644 index 000000000..bab91ab3b --- /dev/null +++ b/src/app/pages/information/information.page.ts @@ -0,0 +1,54 @@ +import { Component } from '@angular/core'; +import { ActivatedRoute } from '@angular/router'; +import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy'; +import { map, pluck, switchMap, switchMapTo } from 'rxjs/operators'; +import { InformationType } from 'src/app/services/data/information/information'; +import { InformationRepository } from 'src/app/services/data/information/information-repository.service'; +import { ProofRepository } from 'src/app/services/data/proof/proof-repository.service'; +import { isNonNullable } from 'src/app/utils/rx-operators'; + +@UntilDestroy({ checkProperties: true }) +@Component({ + selector: 'app-information', + templateUrl: './information.page.html', + styleUrls: ['./information.page.scss'], +}) +export class InformationPage { + + readonly proof$ = this.route.paramMap.pipe( + map(params => params.get('hash')), + isNonNullable(), + switchMap(hash => this.proofRepository.getByHash$(hash)), + isNonNullable() + ); + + readonly hash$ = this.proof$.pipe(pluck('hash')); + + readonly locationInformation$ = this.proof$.pipe( + switchMap(proof => this.informationRepository.getByProof$(proof)), + map(informationList => informationList.filter(information => information.type === InformationType.Location)) + ); + + readonly otherInformation$ = this.proof$.pipe( + switchMap(proof => this.informationRepository.getByProof$(proof)), + map(informationList => informationList.filter(information => information.type === InformationType.Other)) + ); + + readonly deviceInformation$ = this.proof$.pipe( + switchMap(proof => this.informationRepository.getByProof$(proof)), + map(informationList => informationList.filter(information => information.type === InformationType.Device)) + ); + + constructor( + private readonly route: ActivatedRoute, + private readonly proofRepository: ProofRepository, + private readonly informationRepository: InformationRepository, + ) { } + + ionViewWillEnter() { + this.proofRepository.refresh$().pipe( + switchMapTo(this.informationRepository.refresh$()), + untilDestroyed(this) + ).subscribe(); + } +} diff --git a/src/app/pages/proof/proof.page.html b/src/app/pages/proof/proof.page.html index 5e31f6333..22410603f 100644 --- a/src/app/pages/proof/proof.page.html +++ b/src/app/pages/proof/proof.page.html @@ -1,7 +1,7 @@ - + @@ -58,20 +58,25 @@

{{ t('information') }}

- - + + {{ providerWithInformationList.provider }} - - + +

{{ information.name }}

{{ information.value }}

+
+ View All + +
diff --git a/src/app/pages/proof/proof.page.ts b/src/app/pages/proof/proof.page.ts index 160ab8b63..aaf868bfe 100644 --- a/src/app/pages/proof/proof.page.ts +++ b/src/app/pages/proof/proof.page.ts @@ -7,6 +7,7 @@ import { defer } from 'rxjs'; import { first, map, pluck, switchMap, switchMapTo } from 'rxjs/operators'; import { ConfirmAlert } from 'src/app/services/confirm-alert/confirm-alert.service'; import { CaptionRepository } from 'src/app/services/data/caption/caption-repository.service'; +import { Importance } from 'src/app/services/data/information/information'; import { InformationRepository } from 'src/app/services/data/information/information-repository.service'; import { ProofRepository } from 'src/app/services/data/proof/proof-repository.service'; import { SignatureRepository } from 'src/app/services/data/signature/signature-repository.service'; @@ -27,6 +28,7 @@ export class ProofPage { switchMap(hash => this.proofRepository.getByHash$(hash)), isNonNullable() ); + readonly rawBase64$ = this.proof$.pipe(switchMap(proof => this.proofRepository.getRawFile$(proof))); readonly hash$ = this.proof$.pipe(pluck('hash')); readonly mimeType$ = this.proof$.pipe(pluck('mimeType', 'type')); @@ -38,16 +40,20 @@ export class ProofPage { return ''; }) ); - readonly providersWithInformationList$ = this.proof$.pipe( + + readonly providersWithImportantInformation$ = this.proof$.pipe( switchMap(proof => this.informationRepository.getByProof$(proof)), map(informationList => { const providers = new Set(informationList.map(information => information.provider)); return [...providers].map(provider => ({ provider, - informationList: informationList.filter(information => information.provider === provider) + informationList: informationList.filter( + information => information.provider === provider && information.importance === Importance.High + ) })); }) ); + readonly signatures$ = this.proof$.pipe( switchMap(proof => this.signatureRepository.getByProof$(proof)) ); diff --git a/src/app/services/collector/information/capacitor-provider/capacitor-provider.ts b/src/app/services/collector/information/capacitor-provider/capacitor-provider.ts index 88ca9d5ef..2005d8726 100644 --- a/src/app/services/collector/information/capacitor-provider/capacitor-provider.ts +++ b/src/app/services/collector/information/capacitor-provider/capacitor-provider.ts @@ -2,7 +2,7 @@ import { Plugins } from '@capacitor/core'; import { TranslocoService } from '@ngneat/transloco'; import { defer, Observable, of, zip } from 'rxjs'; import { first, map, switchMap } from 'rxjs/operators'; -import { Information } from 'src/app/services/data/information/information'; +import { Importance, Information, InformationType } from 'src/app/services/data/information/information'; import { InformationRepository } from 'src/app/services/data/information/information-repository.service'; import { Proof } from 'src/app/services/data/proof/proof'; import { PreferenceManager } from 'src/app/utils/preferences/preference-manager'; @@ -64,68 +64,94 @@ export class CapacitorProvider extends InformationProvider { informationList.push({ proofHash: proof.hash, provider: this.name, - name: this.translocoService.translate('deviceName'), - value: String(deviceInfo.name) + name: this.translocoService.translate('uuid'), + value: String(deviceInfo.uuid), + importance: Importance.High, + type: InformationType.Other }, { proofHash: proof.hash, provider: this.name, - name: this.translocoService.translate('deviceModel'), - value: String(deviceInfo.model) + name: this.translocoService.translate('deviceName'), + value: String(deviceInfo.name), + importance: Importance.Low, + type: InformationType.Device }, { proofHash: proof.hash, provider: this.name, - name: this.translocoService.translate('devicePlatform'), - value: String(deviceInfo.platform) + name: this.translocoService.translate('deviceModel'), + value: String(deviceInfo.model), + importance: Importance.Low, + type: InformationType.Device }, { proofHash: proof.hash, provider: this.name, - name: this.translocoService.translate('uuid'), - value: String(deviceInfo.uuid) + name: this.translocoService.translate('devicePlatform'), + value: String(deviceInfo.platform), + importance: Importance.Low, + type: InformationType.Device }, { proofHash: proof.hash, provider: this.name, name: this.translocoService.translate('appVersion'), - value: String(deviceInfo.appVersion) + value: String(deviceInfo.appVersion), + importance: Importance.Low, + type: InformationType.Device }, { proofHash: proof.hash, provider: this.name, name: this.translocoService.translate('appVersionCode'), - value: String(deviceInfo.appBuild) + value: String(deviceInfo.appBuild), + importance: Importance.Low, + type: InformationType.Device }, { proofHash: proof.hash, provider: this.name, name: this.translocoService.translate('operatingSystem'), - value: String(deviceInfo.operatingSystem) + value: String(deviceInfo.operatingSystem), + importance: Importance.Low, + type: InformationType.Device }, { proofHash: proof.hash, provider: this.name, name: this.translocoService.translate('osVersion'), - value: String(deviceInfo.osVersion) + value: String(deviceInfo.osVersion), + importance: Importance.Low, + type: InformationType.Device }, { proofHash: proof.hash, provider: this.name, name: this.translocoService.translate('deviceManufacturer'), - value: String(deviceInfo.manufacturer) + value: String(deviceInfo.manufacturer), + importance: Importance.Low, + type: InformationType.Device }, { proofHash: proof.hash, provider: this.name, name: this.translocoService.translate('runningOnVm'), - value: String(deviceInfo.isVirtual) + value: String(deviceInfo.isVirtual), + importance: Importance.Low, + type: InformationType.Device }, { proofHash: proof.hash, provider: this.name, name: this.translocoService.translate('usedMemory'), - value: String(deviceInfo.memUsed) + value: String(deviceInfo.memUsed), + importance: Importance.Low, + type: InformationType.Device }, { proofHash: proof.hash, provider: this.name, name: this.translocoService.translate('freeDiskSpace'), - value: String(deviceInfo.diskFree) + value: String(deviceInfo.diskFree), + importance: Importance.Low, + type: InformationType.Device }, { proofHash: proof.hash, provider: this.name, name: this.translocoService.translate('totalDiskSpace'), - value: String(deviceInfo.diskTotal) + value: String(deviceInfo.diskTotal), + importance: Importance.Low, + type: InformationType.Device }); } if (batteryInfo !== undefined) { @@ -133,12 +159,16 @@ export class CapacitorProvider extends InformationProvider { proofHash: proof.hash, provider: this.name, name: this.translocoService.translate('batteryLevel'), - value: String(batteryInfo.batteryLevel) + value: String(batteryInfo.batteryLevel), + importance: Importance.Low, + type: InformationType.Device }, { proofHash: proof.hash, provider: this.name, name: this.translocoService.translate('batteryCharging'), - value: String(batteryInfo.isCharging) + value: String(batteryInfo.isCharging), + importance: Importance.Low, + type: InformationType.Device }); } if (languageCode !== undefined) { @@ -146,7 +176,9 @@ export class CapacitorProvider extends InformationProvider { proofHash: proof.hash, provider: this.name, name: this.translocoService.translate('deviceLanguageCode'), - value: String(languageCode.value) + value: String(languageCode.value), + importance: Importance.Low, + type: InformationType.Device }); } if (geolocationPosition !== undefined) { @@ -154,7 +186,9 @@ export class CapacitorProvider extends InformationProvider { proofHash: proof.hash, provider: this.name, name: this.translocoService.translate('location'), - value: `(${geolocationPosition.coords.latitude}, ${geolocationPosition.coords.longitude})` + value: `(${geolocationPosition.coords.latitude}, ${geolocationPosition.coords.longitude})`, + importance: Importance.High, + type: InformationType.Location }); } return informationList; diff --git a/src/app/services/data/information/information.ts b/src/app/services/data/information/information.ts index 8cd23f736..99579c40e 100644 --- a/src/app/services/data/information/information.ts +++ b/src/app/services/data/information/information.ts @@ -1,6 +1,19 @@ +export const enum Importance { + Low = 'low', + High = 'high' +} + +export const enum InformationType { + Device = 'device', + Location = 'location', + Other = 'other' +} + export interface Information { readonly proofHash: string; readonly provider: string; readonly name: string; readonly value: string; + readonly importance: Importance; + readonly type: InformationType; } diff --git a/src/assets/i18n/en-us.json b/src/assets/i18n/en-us.json index 0745823ce..13e95a7dd 100644 --- a/src/assets/i18n/en-us.json +++ b/src/assets/i18n/en-us.json @@ -2,8 +2,10 @@ "capture": "Capture", "settings": "Settings", "proofDetails": "Proof Details", + "informationDetails": "Information Details", "caption": "Caption", "hash": "Hash", + "other": "Other", "mimeType": "MIME Type", "timestamp": "Timestamp", "information": "Information", @@ -48,6 +50,9 @@ "collectDeviceInfo": "Collect Device Info", "collectLocationInfo": "Collect Location Info", "unknownError": "Unknown Error", + "low": "Low", + "high": "High", + "device": "Device", "message": { "areYouSure": "This action cannot be undone.", "publishingProof": "Publishing proof {{hash}} to {{publisherName}}.",