Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rebuild Capture G1 #474

Merged
merged 6 commits into from
Jan 22, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { combineLatest, defer } from 'rxjs';
import {
concatMap,
concatMapTo,
first,
map,
shareReplay,
switchMap,
Expand Down Expand Up @@ -88,14 +89,16 @@ export class CaptureDetailsPage {
data: { email: '' },
});
const contact$ = dialogRef.afterClosed().pipe(isNonNullable());
combineLatest([contact$, this.proof$]).subscribe(([contact, proof]) =>
this.router.navigate(
['sending-post-capture', { contact, id: proof.diaBackendAssetId }],
{
relativeTo: this.route,
}
)
);
combineLatest([contact$, this.proof$])
.pipe(first(), untilDestroyed(this))
.subscribe(([contact, proof]) =>
this.router.navigate(
['sending-post-capture', { contact, id: proof.diaBackendAssetId }],
{
relativeTo: this.route,
}
)
);
}

openOptionsMenu() {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,5 @@
<mat-toolbar color="primary" *transloco="let t">
<button
[routerLink]="isPreview ? null : '..'"
routerDirection="back"
(click)="isPreview ? (isPreview = false) : null"
mat-icon-button
>
<button (click)="onBackButtonClick()" mat-icon-button>
<mat-icon>arrow_back</mat-icon>
</button>
<span>{{ t(isPreview ? 'preview' : 'sendPostCapture') }}</span>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,14 @@ export class SendingPostCapturePage {
this.isPreview = true;
}

onBackButtonClick() {
if (this.isPreview) {
this.isPreview = false;
} else {
this.router.navigate(['..'], { relativeTo: this.route });
}
}

async send(captionText: string) {
const action$ = combineLatest([this.asset$, this.contact$]).pipe(
first(),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
.tab-content-post {
margin-bottom: 128px;

virtual-scroller {
width: 100vw;
height: 100vh;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
import { Component, Input, OnInit } from '@angular/core';
import { NavigationEnd, Router } from '@angular/router';
import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy';
import { IPageInfo } from 'ngx-virtual-scroller';
import { BehaviorSubject } from 'rxjs';
import { BehaviorSubject, combineLatest, of, Subject } from 'rxjs';
import {
catchError,
concatMap,
distinctUntilChanged,
filter,
first,
map,
startWith,
switchMap,
tap,
} from 'rxjs/operators';
import {
Expand All @@ -16,6 +20,7 @@ import {
} from '../../../shared/services/dia-backend/asset/dia-backend-asset-repository.service';
import { Pagination } from '../../../shared/services/dia-backend/pagination/pagination';
import { NetworkService } from '../../../shared/services/network/network.service';
import { isNonNullable, VOID$ } from '../../../utils/rx-operators/rx-operators';

@UntilDestroy({ checkProperties: true })
@Component({
Expand All @@ -34,22 +39,54 @@ export class PostCaptureTabComponent implements OnInit {
private readonly _pagination$ = new BehaviorSubject<
Pagination<DiaBackendAsset> | undefined
>(undefined);
// tslint:disable-next-line: rxjs-no-explicit-generics
private readonly _loadNextPageEvent$ = new Subject<IPageInfo>();
private readonly _isLoadingNextPage$ = new BehaviorSubject(false);
readonly focus$ = this._focus$.asObservable().pipe(distinctUntilChanged());
readonly postCaptures$ = this._postCaptures$.asObservable();
readonly pagination$ = this._pagination$.asObservable();
readonly postCaptures$ = this._postCaptures$
.asObservable()
.pipe(distinctUntilChanged());
readonly pagination$ = this._pagination$
.asObservable()
.pipe(distinctUntilChanged());
readonly loadNextPageEvent$ = this._loadNextPageEvent$.asObservable().pipe(
isNonNullable(),
concatMap(event =>
combineLatest([of(event), this.postCaptures$, this.pagination$]).pipe(
first()
)
),
filter(
([event, postCaptures, pagination]) =>
event.endIndex === postCaptures.length - 1 && !!pagination?.next
),
map(([e, p, pagination]) => pagination)
);
readonly isLoadingNextPage$ = this._isLoadingNextPage$
.asObservable()
.pipe(distinctUntilChanged());
readonly networkConnected$ = this.networkService.connected$;
readonly onDidNavigate$ = this.router.events.pipe(
filter(event => event instanceof NavigationEnd && event?.url === '/home'),
startWith(undefined)
);

constructor(
private readonly diaBackendAssetRepository: DiaBackendAssetRepository,
private readonly networkService: NetworkService
private readonly networkService: NetworkService,
private readonly router: Router
) {}

ngOnInit() {
this.focus$
combineLatest([this.focus$, this.onDidNavigate$])
.pipe(
filter(focus => !!focus),
concatMap(() => this.fetchPostCaptures$()),
tap(postCapture => this._postCaptures$.next(postCapture)),
filter(([focus, _]) => !!focus),
switchMap(() =>
this.fetchPostCaptures$().pipe(
tap(postCapture => this._postCaptures$.next(postCapture)),
concatMap(() => this.loadNextPageEventHandler$())
)
),
untilDestroyed(this)
)
.subscribe();
Expand All @@ -61,23 +98,16 @@ export class PostCaptureTabComponent implements OnInit {
.pipe(
tap(pagination => this._pagination$.next(pagination)),
map(pagination => pagination.results),
map(assets => assets.filter(asset => !!asset.source_transaction))
map(assets => assets.filter(asset => !!asset.source_transaction)),
catchError(err => {
console.error(err);
return of([]);
})
);
}

loadNextPage(event: IPageInfo) {
if (event.endIndex !== this._postCaptures$.value.length - 1) {
return;
}
this.pagination$
.pipe(
first(),
filter(pagination => !!pagination?.next),
concatMap(pagination => this.fetchPostCaptures$(pagination?.next)),
tap(newPostCaptures => this.concatPostCaptures(newPostCaptures)),
untilDestroyed(this)
)
.subscribe();
this._loadNextPageEvent$.next(event);
}

// tslint:disable-next-line: prefer-function-over-method
Expand All @@ -88,4 +118,25 @@ export class PostCaptureTabComponent implements OnInit {
private concatPostCaptures(postCaptures: DiaBackendAsset[]) {
this._postCaptures$.next(this._postCaptures$.value.concat(postCaptures));
}

private loadNextPageEventHandler$() {
const loadData$ = this.pagination$.pipe(
first(),
concatMap(pagination => this.fetchPostCaptures$(pagination?.next)),
tap(newPostCaptures => this.concatPostCaptures(newPostCaptures))
);
return this.loadNextPageEvent$.pipe(
concatMap(() => this.isLoadingNextPage$.pipe(first())),
filter(isLoadingNextPage => !isLoadingNextPage),
tap(() => this._isLoadingNextPage$.next(true)),
concatMap(() => loadData$),
tap(() => this._isLoadingNextPage$.next(false)),
catchError(err => {
this._isLoadingNextPage$.next(false);
console.error(err);
return VOID$;
}),
untilDestroyed(this)
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,14 @@ import { MatBottomSheet } from '@angular/material/bottom-sheet';
import { Plugins } from '@capacitor/core';
import { TranslocoService } from '@ngneat/transloco';
import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy';
import mergeImages from 'merge-images';
import { BehaviorSubject } from 'rxjs';
import { concatMap, first, map, tap } from 'rxjs/operators';
import {
DiaBackendAsset,
DiaBackendAssetRepository,
} from '../../../shared/services/dia-backend/asset/dia-backend-asset-repository.service';
import { ImageStore } from '../../../shared/services/image-store/image-store.service';
import { isNonNullable } from '../../../utils/rx-operators/rx-operators';
import { ShareService } from '../../services/share/share.service';
import {
Option,
OptionsMenuComponent,
Expand Down Expand Up @@ -46,8 +45,8 @@ export class PostCaptureCardComponent implements OnInit {
constructor(
private readonly diaBackendAssetRepository: DiaBackendAssetRepository,
private readonly translocoService: TranslocoService,
private readonly imageStore: ImageStore,
private readonly bottomSheet: MatBottomSheet
private readonly bottomSheet: MatBottomSheet,
private readonly shareService: ShareService
) {}

ngOnInit() {
Expand Down Expand Up @@ -85,24 +84,7 @@ export class PostCaptureCardComponent implements OnInit {
return this.postCapture$
.pipe(
first(),
concatMap(postCapture =>
mergeImages(
[postCapture.sharable_copy, '/assets/image/new-year-frame.png'],
// @ts-ignore
{ format: 'image/jpeg', crossOrigin: 'Anonymous' }
)
),
concatMap(async watermarkedUrl => {
const base64 = watermarkedUrl.split(',')[1];
return this.imageStore.write(base64, 'image/jpeg');
}),
concatMap(index => this.imageStore.getUri(index)),
concatMap(watermarkedUri =>
Share.share({
text: '#CaptureApp #OnlyTruePhotos',
url: watermarkedUri,
})
),
concatMap(postCapture => this.shareService.share(postCapture)),
untilDestroyed(this)
)
.subscribe();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,20 @@ export class DiaBackendAssetRepository {
);
}

downloadFile$(id: string, field: AssetDownloadField) {
const formData = new FormData();
formData.append('field', field);
return defer(() => this.authService.getAuthHeaders()).pipe(
concatMap(headers =>
this.httpClient.post(
`${BASE_URL}/api/v2/assets/${id}/download/`,
formData,
{ headers, responseType: 'blob' }
)
)
);
}

add$(proof: Proof) {
return forkJoin([
defer(() => this.authService.getAuthHeaders()),
Expand Down Expand Up @@ -167,6 +181,11 @@ interface ListAssetResponse {
results: DiaBackendAsset[];
}

export type AssetDownloadField =
| 'asset_file'
| 'asset_file_thumbnail'
| 'sharable_copy';

type CreateAssetResponse = DiaBackendAsset;

// tslint:disable-next-line: no-empty-interface
Expand Down
16 changes: 16 additions & 0 deletions src/app/shared/services/share/share.service.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { TestBed } from '@angular/core/testing';
import { SharedTestingModule } from '../../shared-testing.module';
import { ShareService } from './share.service';

describe('ShareService', () => {
let service: ShareService;

beforeEach(() => {
TestBed.configureTestingModule({ imports: [SharedTestingModule] });
service = TestBed.inject(ShareService);
});

it('should be created', () => {
expect(service).toBeTruthy();
});
});
66 changes: 66 additions & 0 deletions src/app/shared/services/share/share.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { Injectable } from '@angular/core';
import { Plugins } from '@capacitor/core';
import mergeImages from 'merge-images';
import { concatMap, map } from 'rxjs/operators';
import { blobToBase64 } from '../../../utils/encoding/encoding';
import {
DiaBackendAsset,
DiaBackendAssetRepository,
} from '../dia-backend/asset/dia-backend-asset-repository.service';
import { ImageStore } from '../image-store/image-store.service';
const { Share } = Plugins;

@Injectable({
providedIn: 'root',
})
export class ShareService {
private readonly defaultSharingFrame = '/assets/image/new-year-frame.png';
private readonly defaultMimetype = 'image/jpeg';
private readonly defaultShareText = '#CaptureApp #OnlyTruePhotos';

constructor(
private readonly diaBackendAssetRepository: DiaBackendAssetRepository,
private readonly imageStore: ImageStore
) {}

async share(asset: DiaBackendAsset) {
const dataUri = await this.getSharableCopy(asset).catch(() =>
this.getSharableCopyFallback(asset)
);
const fileUrl = await this.createFileUrl(dataUri);
return Share.share({
text: this.defaultShareText,
url: fileUrl,
});
}

private async createFileUrl(dataUri: string) {
const base64 = dataUri.split(',')[1];
const index = await this.imageStore.write(base64, this.defaultMimetype);
return this.imageStore.getUri(index);
}

private async getSharableCopy(asset: DiaBackendAsset) {
return mergeImages(
[asset.sharable_copy, this.defaultSharingFrame],
// @ts-ignore
{ format: this.defaultMimetype, crossOrigin: 'Anonymous' }
);
}

// WORKAROUND: Use this fallback as a workaround for S3 CORS issue
private async getSharableCopyFallback(asset: DiaBackendAsset) {
const dataUri = await this.diaBackendAssetRepository
.downloadFile$(asset.id, 'sharable_copy')
.pipe(
concatMap(blobToBase64),
map(imageBase64 => `data:image/jpeg;base64,${imageBase64}`)
)
.toPromise();
return mergeImages(
[dataUri, this.defaultSharingFrame],
// @ts-ignore
{ format: this.defaultMimetype, crossOrigin: 'Anonymous' }
);
}
}