diff --git a/CHANGELOG.md b/CHANGELOG.md index 2a46626da..c8fff45e5 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.57.0 - 2022-05-18 + +### Added + +- Add a capture app beginner guide + ## 0.56.3 - 2022-05-13 ### Changed diff --git a/android/app/build.gradle b/android/app/build.gradle index 38560089f..6145d5825 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 393 - versionName "0.56.3" + versionCode 340 + versionName "0.57.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } buildFeatures { diff --git a/package-lock.json b/package-lock.json index 81e21e28d..22e6da6ae 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "capture-lite", - "version": "0.56.3", + "version": "0.57.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "capture-lite", - "version": "0.56.3", + "version": "0.57.0", "dependencies": { "@angular/animations": "^12.2.4", "@angular/cdk": "^12.2.4", @@ -56,6 +56,7 @@ "lodash-es": "^4.17.21", "material-design-icons-iconfont": "^6.1.0", "ng-circle-progress": "^1.6.0", + "ngx-joyride": "^2.5.0", "ngx-long-press2": "^2.0.0", "ngx-pinch-zoom": "^2.6.0", "process": "^0.11.10", @@ -14759,6 +14760,18 @@ "rxjs": ">=6.4.0" } }, + "node_modules/ngx-joyride": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/ngx-joyride/-/ngx-joyride-2.5.0.tgz", + "integrity": "sha512-C/J8C4uWZjKl9aMmRBt9egVjuIpwWFplJgBZDl1EfqNVTJkdEC51nt9DpAOuDwOgkbArhJ9sZIk3bZT4vkud/w==", + "dependencies": { + "tslib": "^2.0.0" + }, + "peerDependencies": { + "@angular/common": ">=8.2.14", + "@angular/core": ">=8.2.14" + } + }, "node_modules/ngx-long-press2": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ngx-long-press2/-/ngx-long-press2-2.0.0.tgz", @@ -36571,6 +36584,14 @@ "tslib": "^2.0.0" } }, + "ngx-joyride": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/ngx-joyride/-/ngx-joyride-2.5.0.tgz", + "integrity": "sha512-C/J8C4uWZjKl9aMmRBt9egVjuIpwWFplJgBZDl1EfqNVTJkdEC51nt9DpAOuDwOgkbArhJ9sZIk3bZT4vkud/w==", + "requires": { + "tslib": "^2.0.0" + } + }, "ngx-long-press2": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ngx-long-press2/-/ngx-long-press2-2.0.0.tgz", diff --git a/package.json b/package.json index 1fd2a68d6..6dffa8365 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "capture-lite", - "version": "0.56.3", + "version": "0.57.0", "author": "numbersprotocol", "homepage": "https://numbersprotocol.io/", "scripts": { @@ -67,6 +67,7 @@ "lodash-es": "^4.17.21", "material-design-icons-iconfont": "^6.1.0", "ng-circle-progress": "^1.6.0", + "ngx-joyride": "^2.5.0", "ngx-long-press2": "^2.0.0", "ngx-pinch-zoom": "^2.6.0", "process": "^0.11.10", diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 034a47035..ae93490a6 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -6,6 +6,7 @@ import { RouteReuseStrategy } from '@angular/router'; import { IonicModule, IonicRouteStrategy } from '@ionic/angular'; import { FormlyModule } from '@ngx-formly/core'; import { FormlyMaterialModule } from '@ngx-formly/material'; +import { JoyrideModule } from 'ngx-joyride'; import { AppRoutingModule } from './app-routing.module'; import { AppComponent } from './app.component'; import { TranslocoRootModule } from './shared/language/transloco/transloco-root.module'; @@ -23,6 +24,7 @@ import { SharedModule } from './shared/shared.module'; TranslocoRootModule, FormlyModule.forRoot({ extras: { lazyRender: true } }), FormlyMaterialModule, + JoyrideModule.forRoot(), ], providers: [ { diff --git a/src/app/features/home/activities/activities.module.ts b/src/app/features/home/activities/activities.module.ts index ba545778f..bc72f37ef 100644 --- a/src/app/features/home/activities/activities.module.ts +++ b/src/app/features/home/activities/activities.module.ts @@ -1,4 +1,5 @@ import { NgModule } from '@angular/core'; +import { JoyrideModule } from 'ngx-joyride'; import { SharedModule } from '../../../shared/shared.module'; import { ActivitiesPageRoutingModule } from './activities-routing.module'; import { ActivitiesPage } from './activities.page'; @@ -6,7 +7,11 @@ import { CaptureTransactionsComponent } from './capture-transactions/capture-tra import { NetworkActionOrdersComponent } from './network-action-orders/network-action-orders.component'; @NgModule({ - imports: [SharedModule, ActivitiesPageRoutingModule], + imports: [ + SharedModule, + ActivitiesPageRoutingModule, + JoyrideModule.forChild(), + ], declarations: [ ActivitiesPage, CaptureTransactionsComponent, diff --git a/src/app/features/home/activities/activities.page.html b/src/app/features/home/activities/activities.page.html index fa9d3b0aa..a6c3f0698 100644 --- a/src/app/features/home/activities/activities.page.html +++ b/src/app/features/home/activities/activities.page.html @@ -7,11 +7,25 @@ - - {{ t('captureTransactions') }} + + + {{ t('captureTransactions') }} + - - {{ t('networkActions') }} + + + {{ t('networkActions') }} + diff --git a/src/app/features/home/activities/activities.page.ts b/src/app/features/home/activities/activities.page.ts index 3a69610a5..52c386e0a 100644 --- a/src/app/features/home/activities/activities.page.ts +++ b/src/app/features/home/activities/activities.page.ts @@ -1,5 +1,6 @@ import { Component } from '@angular/core'; import { UntilDestroy } from '@ngneat/until-destroy'; +import { UserGuideService } from '../../../shared/user-guide/user-guide.service'; @UntilDestroy({ checkProperties: true }) @Component({ @@ -7,4 +8,11 @@ import { UntilDestroy } from '@ngneat/until-destroy'; templateUrl: './activities.page.html', styleUrls: ['./activities.page.scss'], }) -export class ActivitiesPage {} +export class ActivitiesPage { + constructor(private readonly userGuideService: UserGuideService) {} + + async ionViewDidEnter() { + await this.userGuideService.showUserGuidesOnActivitiesPage(); + await this.userGuideService.setHasOpenedActivitiesPage(true); + } +} 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 396956bbd..36825e681 100644 --- a/src/app/features/home/capture-tab/capture-tab.component.html +++ b/src/app/features/home/capture-tab/capture-tab.component.html @@ -14,18 +14,28 @@ -
+
{{ group.key | date: 'longDate' }}
diff --git a/src/app/features/home/custom-camera/custom-camera.module.ts b/src/app/features/home/custom-camera/custom-camera.module.ts index 2b8996205..c4b4c14b7 100644 --- a/src/app/features/home/custom-camera/custom-camera.module.ts +++ b/src/app/features/home/custom-camera/custom-camera.module.ts @@ -1,5 +1,6 @@ import { NgModule } from '@angular/core'; import { NgCircleProgressModule } from 'ng-circle-progress'; +import { JoyrideModule } from 'ngx-joyride'; import { NgxLongPress2Module } from 'ngx-long-press2'; import { SharedModule } from '../../../shared/shared.module'; import { CustomCameraPageRoutingModule } from './custom-camera-routing.module'; @@ -12,6 +13,7 @@ import { CustomCameraService } from './custom-camera.service'; CustomCameraPageRoutingModule, NgxLongPress2Module, NgCircleProgressModule.forRoot({}), + JoyrideModule.forChild(), ], providers: [CustomCameraService], declarations: [CustomCameraPage], diff --git a/src/app/features/home/custom-camera/custom-camera.page.html b/src/app/features/home/custom-camera/custom-camera.page.html index 0b25da128..57706bb43 100644 --- a/src/app/features/home/custom-camera/custom-camera.page.html +++ b/src/app/features/home/custom-camera/custom-camera.page.html @@ -1,4 +1,8 @@ - +
featured_video
- + close @@ -35,8 +45,18 @@ [showSubtitle]="false" [animation]="false" [animationDuration]="0" + joyrideStep="highlightCustomCameraCaptureButton" + [title]="t('userGuide.cameraUsageGuide')" + [text]="t('userGuide.tapToTakeAPhotoAndLongPressToRecordVideo')" > - flip_camera_android + + flip_camera_android +
diff --git a/src/app/features/home/custom-camera/custom-camera.page.ts b/src/app/features/home/custom-camera/custom-camera.page.ts index 2efeb98d6..fb755ccdf 100644 --- a/src/app/features/home/custom-camera/custom-camera.page.ts +++ b/src/app/features/home/custom-camera/custom-camera.page.ts @@ -6,6 +6,7 @@ import { PluginListenerHandle } from '@capacitor/core'; import { UntilDestroy } from '@ngneat/until-destroy'; import { CaptureResult, PreviewCamera } from '@numbersprotocol/preview-camera'; import { ErrorService } from '../../../shared/error/error.service'; +import { UserGuideService } from '../../../shared/user-guide/user-guide.service'; import { GoProBluetoothService } from '../../settings/go-pro/services/go-pro-bluetooth.service'; import { CustomCameraMediaItem, @@ -40,10 +41,11 @@ export class CustomCameraPage implements OnInit, OnDestroy { private readonly router: Router, private readonly customCameraService: CustomCameraService, private readonly goProBluetoothService: GoProBluetoothService, - private readonly errorService: ErrorService + private readonly errorService: ErrorService, + private readonly userGuideService: UserGuideService ) {} - ngOnInit(): void { + ngOnInit() { this.debugOnlyPreventContextMenuFromLongPressContextMenu(); PreviewCamera.addListener( @@ -59,6 +61,11 @@ export class CustomCameraPage implements OnInit, OnDestroy { this.startPreviewCamera(); } + async ionViewDidEnter() { + await this.userGuideService.showUserGuidesOnCustomCameraPage(); + await this.userGuideService.setHasOpenedCustomCameraPage(true); + } + ngOnDestroy(): void { this.capturePhotoFinishedListener?.remove(); this.captureVideoFinishedListener?.remove(); @@ -96,10 +103,12 @@ export class CustomCameraPage implements OnInit, OnDestroy { } onPress() { + this.userGuideService.setHasCapturedPhotoWithCustomCamera(true); this.customCameraService.takePhoto(); } onLongPress() { + this.userGuideService.setHasCapturedVideoWithCustomCamera(true); this.customCameraService.startRecord(); } diff --git a/src/app/features/home/details/details.module.ts b/src/app/features/home/details/details.module.ts index 2da4378e6..894aea20c 100644 --- a/src/app/features/home/details/details.module.ts +++ b/src/app/features/home/details/details.module.ts @@ -1,11 +1,17 @@ import { NgModule } from '@angular/core'; +import { JoyrideModule } from 'ngx-joyride'; import { SwiperModule } from 'swiper/angular'; import { SharedModule } from '../../../shared/shared.module'; import { DetailsPageRoutingModule } from './details-routing.module'; import { DetailsPage } from './details.page'; @NgModule({ - imports: [SharedModule, DetailsPageRoutingModule, SwiperModule], + imports: [ + SharedModule, + DetailsPageRoutingModule, + SwiperModule, + JoyrideModule.forChild(), + ], declarations: [DetailsPage], }) export class DetailsPageModule {} diff --git a/src/app/features/home/details/details.page.html b/src/app/features/home/details/details.page.html index 08770970f..854048e23 100644 --- a/src/app/features/home/details/details.page.html +++ b/src/app/features/home/details/details.page.html @@ -16,6 +16,9 @@ *ngIf="(isFromSeriesPage$ | ngrxPush) === false" (click)="openOptionsMenu()" mat-icon-button + joyrideStep="highlightDetailsPageOptionsMenu" + [title]="t('userGuide.optionsMenu')" + [text]="t('userGuide.clickTheOptionsMenuToUseNetworkActions')" > more_vert diff --git a/src/app/features/home/details/details.page.ts b/src/app/features/home/details/details.page.ts index ad24ce68d..fd472a92c 100644 --- a/src/app/features/home/details/details.page.ts +++ b/src/app/features/home/details/details.page.ts @@ -32,6 +32,7 @@ import { ErrorService } from '../../../shared/error/error.service'; import { MediaStore } from '../../../shared/media/media-store/media-store.service'; import { ProofRepository } from '../../../shared/repositories/proof/proof-repository.service'; import { ShareService } from '../../../shared/share/share.service'; +import { UserGuideService } from '../../../shared/user-guide/user-guide.service'; import { isNonNullable, switchTap, @@ -196,13 +197,19 @@ export class DetailsPage { private readonly snackBar: MatSnackBar, private readonly diaBackendWorkflowService: DiaBackendWorkflowService, private readonly alertController: AlertController, - private readonly changeDetectorRef: ChangeDetectorRef + private readonly changeDetectorRef: ChangeDetectorRef, + private readonly userGuideService: UserGuideService ) { this.initializeActiveDetailedCapture$ .pipe(untilDestroyed(this)) .subscribe(); } + async ionViewDidEnter() { + await this.userGuideService.showUserGuidesOnDetailsPage(); + await this.userGuideService.setHasOpenedDetailsPage(true); + } + // eslint-disable-next-line class-methods-use-this trackDetailedCapture(_: number, item: DetailedCapture) { return item.id; @@ -306,6 +313,7 @@ export class DetailsPage { } openOptionsMenu() { + this.userGuideService.setHasClickedDetailsPageOptionsMenu(true); combineLatest([ this.activeDetailedCapture$, this.activeDetailedCapture$.pipe(switchMap(c => c.diaBackendAsset$)), diff --git a/src/app/features/home/home.module.ts b/src/app/features/home/home.module.ts index 4ba769604..e54a06671 100644 --- a/src/app/features/home/home.module.ts +++ b/src/app/features/home/home.module.ts @@ -1,4 +1,5 @@ import { NgModule } from '@angular/core'; +import { JoyrideModule } from 'ngx-joyride'; import { SharedModule } from '../../shared/shared.module'; import { CaptureItemComponent } from './capture-tab/capture-item/capture-item.component'; import { CaptureTabComponent } from './capture-tab/capture-tab.component'; @@ -17,6 +18,6 @@ import { PostCaptureTabComponent } from './post-capture-tab/post-capture-tab.com UploadingBarComponent, CaptureItemComponent, ], - imports: [SharedModule, HomePageRoutingModule], + imports: [SharedModule, HomePageRoutingModule, JoyrideModule.forChild()], }) export class HomePageModule {} diff --git a/src/app/features/home/home.page.html b/src/app/features/home/home.page.html index 603b26922..224ef492f 100644 --- a/src/app/features/home/home.page.html +++ b/src/app/features/home/home.page.html @@ -57,7 +57,16 @@ - move_to_inbox + diff --git a/src/app/features/home/home.page.ts b/src/app/features/home/home.page.ts index 1b660c59d..6876be7ff 100644 --- a/src/app/features/home/home.page.ts +++ b/src/app/features/home/home.page.ts @@ -25,6 +25,7 @@ import { DiaBackendWalletService } from '../../shared/dia-backend/wallet/dia-bac import { ErrorService } from '../../shared/error/error.service'; import { MigrationService } from '../../shared/migration/service/migration.service'; 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 { GoProBluetoothService } from '../settings/go-pro/services/go-pro-bluetooth.service'; import { PrefetchingDialogComponent } from './onboarding/prefetching-dialog/prefetching-dialog.component'; @@ -69,7 +70,8 @@ export class HomePage { private readonly actionSheetController: ActionSheetController, private readonly alertController: AlertController, private readonly goProBluetoothService: GoProBluetoothService, - private readonly diaBackendWalletService: DiaBackendWalletService + private readonly diaBackendWalletService: DiaBackendWalletService, + private readonly userGuideService: UserGuideService ) { this.downloadExpiredPostCaptures(); } @@ -80,6 +82,9 @@ export class HomePage { concatMap(isNewLogin => this.migrationService.migrate$(isNewLogin)), catchError(() => VOID$), switchTapTo(defer(() => this.onboardingRedirect())), + switchTapTo( + defer(() => this.userGuideService.showUserGuidesOnHomePage()) + ), catchError((err: unknown) => this.errorService.toastError$(err)), untilDestroyed(this) ) @@ -87,11 +92,6 @@ export class HomePage { } private async onboardingRedirect() { - if ((await this.onboardingService.hasShownTutorialVersion()) === '') { - return this.router.navigate(['tutorial'], { - relativeTo: this.route, - }); - } this.onboardingService.isNewLogin = false; if (!(await this.onboardingService.hasCreatedOrImportedIntegrityWallet())) { @@ -250,4 +250,9 @@ export class HomePage { ) .subscribe(); } + + async navigateToInboxTab() { + await this.userGuideService.showUserGuidesOnInboxTab(); + await this.userGuideService.setHasOpenedInboxTab(true); + } } diff --git a/src/app/features/home/post-capture-tab/post-capture-tab.component.html b/src/app/features/home/post-capture-tab/post-capture-tab.component.html index 71595dde5..621eb4331 100644 --- a/src/app/features/home/post-capture-tab/post-capture-tab.component.html +++ b/src/app/features/home/post-capture-tab/post-capture-tab.component.html @@ -1,9 +1,20 @@ -
+
- + - + diff --git a/src/app/features/settings/settings-routing.module.ts b/src/app/features/settings/settings-routing.module.ts index 76088fcab..9ed9bdb57 100644 --- a/src/app/features/settings/settings-routing.module.ts +++ b/src/app/features/settings/settings-routing.module.ts @@ -12,6 +12,11 @@ const routes: Routes = [ loadChildren: () => import('./go-pro/go-pro.module').then(m => m.GoProPageModule), }, + { + path: 'user-guide', + loadChildren: () => + import('./user-guide/user-guide.module').then(m => m.UserGuidePageModule), + }, ]; @NgModule({ diff --git a/src/app/features/settings/settings.page.html b/src/app/features/settings/settings.page.html index 5ac49ddc1..d0266ba40 100644 --- a/src/app/features/settings/settings.page.html +++ b/src/app/features/settings/settings.page.html @@ -31,5 +31,13 @@ GoPro + + + + User Guide Preferences + diff --git a/src/app/features/settings/user-guide/user-guide-routing.module.ts b/src/app/features/settings/user-guide/user-guide-routing.module.ts new file mode 100644 index 000000000..ebfcd0e7a --- /dev/null +++ b/src/app/features/settings/user-guide/user-guide-routing.module.ts @@ -0,0 +1,17 @@ +import { NgModule } from '@angular/core'; +import { Routes, RouterModule } from '@angular/router'; + +import { UserGuidePage } from './user-guide.page'; + +const routes: Routes = [ + { + path: '', + component: UserGuidePage, + }, +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule], +}) +export class UserGuidePageRoutingModule {} diff --git a/src/app/features/settings/user-guide/user-guide.module.ts b/src/app/features/settings/user-guide/user-guide.module.ts new file mode 100644 index 000000000..834607b64 --- /dev/null +++ b/src/app/features/settings/user-guide/user-guide.module.ts @@ -0,0 +1,11 @@ +import { NgModule } from '@angular/core'; +import { JoyrideModule } from 'ngx-joyride'; +import { SharedModule } from '../../../shared/shared.module'; +import { UserGuidePageRoutingModule } from './user-guide-routing.module'; +import { UserGuidePage } from './user-guide.page'; + +@NgModule({ + imports: [SharedModule, UserGuidePageRoutingModule, JoyrideModule.forChild()], + declarations: [UserGuidePage], +}) +export class UserGuidePageModule {} diff --git a/src/app/features/settings/user-guide/user-guide.page.html b/src/app/features/settings/user-guide/user-guide.page.html new file mode 100644 index 000000000..c6a909220 --- /dev/null +++ b/src/app/features/settings/user-guide/user-guide.page.html @@ -0,0 +1,68 @@ + + + User guide Preferences + + + + + + Has Opened Camera Page + + + + Has Captured Photo With Custom Camera + + + + Has Captured Video With Custom Camera + + + + Has Opened Details Page + + + + Has Clicked Details Page Options Menu + + + + Has Opened Activities Page + + + + Has Opened Inbox Tab + + + + Reset All + diff --git a/src/app/features/settings/user-guide/user-guide.page.scss b/src/app/features/settings/user-guide/user-guide.page.scss new file mode 100644 index 000000000..d60274cb8 --- /dev/null +++ b/src/app/features/settings/user-guide/user-guide.page.scss @@ -0,0 +1,18 @@ +mat-toolbar { + span { + padding-right: 40px; + } +} + +ion-button { + --box-shadow: 0; + + margin-bottom: 16px; + color: white; +} + +ion-checkbox { + --border-radius: 50%; + --checkmark-color: white; + --size: 24px; +} diff --git a/src/app/features/settings/user-guide/user-guide.page.spec.ts b/src/app/features/settings/user-guide/user-guide.page.spec.ts new file mode 100644 index 000000000..e16c4d34e --- /dev/null +++ b/src/app/features/settings/user-guide/user-guide.page.spec.ts @@ -0,0 +1,26 @@ +import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; +import { JoyrideModule } from 'ngx-joyride'; +import { SharedTestingModule } from '../../../shared/shared-testing.module'; +import { UserGuidePage } from './user-guide.page'; + +describe('UserGuidePage', () => { + let component: UserGuidePage; + let fixture: ComponentFixture; + + beforeEach( + waitForAsync(() => { + TestBed.configureTestingModule({ + declarations: [UserGuidePage], + imports: [SharedTestingModule, JoyrideModule.forChild()], + }).compileComponents(); + + fixture = TestBed.createComponent(UserGuidePage); + component = fixture.componentInstance; + fixture.detectChanges(); + }) + ); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/features/settings/user-guide/user-guide.page.ts b/src/app/features/settings/user-guide/user-guide.page.ts new file mode 100644 index 000000000..8582cb0ff --- /dev/null +++ b/src/app/features/settings/user-guide/user-guide.page.ts @@ -0,0 +1,76 @@ +import { Component } from '@angular/core'; +import { UserGuideService } from '../../../shared/user-guide/user-guide.service'; + +@Component({ + selector: 'app-user-guide', + templateUrl: './user-guide.page.html', + styleUrls: ['./user-guide.page.scss'], +}) +export class UserGuidePage { + readonly hasOpenedCustomCameraPage$ = + this.userGuideService.hasOpenedCustomCameraPage$(); + + readonly hasCapturedPhotoWithCustomCamera$ = + this.userGuideService.hasCapturedPhotoWithCustomCamera$(); + + readonly hasCapturedVideoWithCustomCamera$ = + this.userGuideService.hasCapturedVideoWithCustomCamera$(); + + readonly hasOpenedDetailsPage$ = + this.userGuideService.hasOpenedDetailsPage$(); + + readonly hasClickedDetailsPageOptionsMenu$ = + this.userGuideService.hasClickedDetailsPageOptionsMenu$(); + + readonly hasOpenedActivitiesPage$ = + this.userGuideService.hasOpenedActivitiesPage$(); + + readonly hasOpenedInboxTab$ = this.userGuideService.hasOpenedInboxTab$(); + + constructor(private readonly userGuideService: UserGuideService) {} + + setHasOpenedCustomCameraPage(event: any) { + this.userGuideService.setHasOpenedCustomCameraPage(event.detail.checked); + } + + setHasCapturedPhotoWithCustomCamera(event: any) { + this.userGuideService.setHasCapturedPhotoWithCustomCamera( + event.detail.checked + ); + } + + setHasCapturedVideoWithCustomCamera(event: any) { + this.userGuideService.setHasCapturedVideoWithCustomCamera( + event.detail.checked + ); + } + + setHasOpenedDetailsPage(event: any) { + this.userGuideService.setHasOpenedDetailsPage(event.detail.checked); + } + + setHasClickedDetailsPageOptionsMenu(event: any) { + this.userGuideService.setHasClickedDetailsPageOptionsMenu( + event.detail.checked + ); + } + + setHasOpenedActivitiesPage(event: any) { + this.userGuideService.setHasOpenedActivitiesPage(event.detail.checked); + } + + setHasOpenedInboxTab(event: any) { + this.userGuideService.setHasOpenedInboxTab(event.detail.checked); + } + + resetAll() { + this.userGuideService.setHasOpenedCustomCameraPage(false); + this.userGuideService.setHasOpenedCustomCameraPage(false); + this.userGuideService.setHasCapturedPhotoWithCustomCamera(false); + this.userGuideService.setHasCapturedVideoWithCustomCamera(false); + this.userGuideService.setHasOpenedDetailsPage(false); + this.userGuideService.setHasClickedDetailsPageOptionsMenu(false); + this.userGuideService.setHasOpenedActivitiesPage(false); + this.userGuideService.setHasOpenedInboxTab(false); + } +} diff --git a/src/app/shared/shared-testing.module.ts b/src/app/shared/shared-testing.module.ts index 4366b3ec4..beaa2a767 100644 --- a/src/app/shared/shared-testing.module.ts +++ b/src/app/shared/shared-testing.module.ts @@ -6,6 +6,7 @@ import { CapacitorPluginsTestingModule } from './capacitor-plugins/capacitor-plu import { getTranslocoTestingModule } from './language/transloco/transloco-testing.module'; import { MaterialTestingModule } from './material/material-testing.module'; import { SharedModule } from './shared.module'; +import { getJoyrideModuleForRoot } from './user-guide/user-guide.module'; @NgModule({ imports: [ @@ -15,6 +16,7 @@ import { SharedModule } from './shared.module'; getTranslocoTestingModule(), MaterialTestingModule, CapacitorPluginsTestingModule, + getJoyrideModuleForRoot(), ], exports: [ SharedModule, diff --git a/src/app/shared/shared.module.ts b/src/app/shared/shared.module.ts index 7094cc167..eab91ed03 100644 --- a/src/app/shared/shared.module.ts +++ b/src/app/shared/shared.module.ts @@ -22,6 +22,7 @@ import { MediaComponent } from './media/component/media.component'; import { MigratingDialogComponent } from './migration/migrating-dialog/migrating-dialog.component'; import { OrderDetailDialogComponent } from './order-detail-dialog/order-detail-dialog.component'; import { StartsWithPipe } from './pipes/starts-with/starts-with.pipe'; +import { UserGuideService } from './user-guide/user-guide.service'; const declarations = [ MigratingDialogComponent, @@ -50,7 +51,12 @@ const imports = [ FormlyMaterialModule, ]; -const providers = [GoProBluetoothService, GoProWifiService, GoProMediaService]; +const providers = [ + GoProBluetoothService, + GoProWifiService, + GoProMediaService, + UserGuideService, +]; @NgModule({ declarations, diff --git a/src/app/shared/user-guide/user-guide.module.ts b/src/app/shared/user-guide/user-guide.module.ts new file mode 100644 index 000000000..aca2d9125 --- /dev/null +++ b/src/app/shared/user-guide/user-guide.module.ts @@ -0,0 +1,9 @@ +import { JoyrideModule } from 'ngx-joyride'; + +export function getJoyrideModuleForRoot() { + return JoyrideModule.forRoot(); +} + +export function getJoyrideModuleForChild() { + return JoyrideModule.forChild(); +} diff --git a/src/app/shared/user-guide/user-guide.service.spec.ts b/src/app/shared/user-guide/user-guide.service.spec.ts new file mode 100644 index 000000000..efd46e385 --- /dev/null +++ b/src/app/shared/user-guide/user-guide.service.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from '@angular/core/testing'; +import { SharedTestingModule } from '../shared-testing.module'; +import { UserGuideService } from './user-guide.service'; + +describe('UserGuideService', () => { + let service: UserGuideService; + + beforeEach(() => { + TestBed.configureTestingModule({ imports: [SharedTestingModule] }); + service = TestBed.inject(UserGuideService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/src/app/shared/user-guide/user-guide.service.ts b/src/app/shared/user-guide/user-guide.service.ts new file mode 100644 index 000000000..36ecf44be --- /dev/null +++ b/src/app/shared/user-guide/user-guide.service.ts @@ -0,0 +1,307 @@ +import { Injectable } from '@angular/core'; +import { TranslocoService } from '@ngneat/transloco'; +import { JoyrideService } from 'ngx-joyride'; +import { PreferenceManager } from '../preference-manager/preference-manager.service'; + +@Injectable({ + providedIn: 'root', +}) +export class UserGuideService { + private readonly preferences = + this.preferenceManager.getPreferences('UserGuideService'); + + private readonly showCounter = false; + + constructor( + private readonly preferenceManager: PreferenceManager, + private readonly joyrideService: JoyrideService, + private readonly translocoService: TranslocoService + ) {} + + // eslint-disable-next-line class-methods-use-this + private async delayBeforeStartTour(delayInMilliseconds = 700) { + return new Promise(resolve => setTimeout(resolve, delayInMilliseconds)); + } + + private get customTexts() { + const done = this.translocoService.translate('userGuide.okIGotIt'); + const prev = this.translocoService.translate('userGuide.previous'); + const next = this.translocoService.translate('userGuide.next'); + return { prev, next, done }; + } + + async showUserGuidesOnHomePage() { + if ( + (await this.hasOpenedCustomCameraPage()) === false || + (await this.hasCapturePhotoOrVideoWithCustomCamera()) === false + ) { + await this.delayBeforeStartTour(); + this.joyrideService.startTour({ + steps: ['highlightCaptureButton'], + showCounter: this.showCounter, + customTexts: this.customTexts, + }); + } else if ( + (await this.hasOpenedDetailsPage()) === false && + (await this.hasCapturePhotoOrVideoWithCustomCamera()) === true + ) { + await this.delayBeforeStartTour(); + this.joyrideService.startTour({ + steps: ['highlightFirstCapture'], + showCounter: this.showCounter, + customTexts: this.customTexts, + }); + } else if ( + (await this.hasClickedDetailsPageOptionsMenu()) === true && + (await this.hasOpenedActivitiesPage()) === false + ) { + await this.delayBeforeStartTour(); + this.joyrideService.startTour({ + steps: ['highlightActivityButton'], + showCounter: this.showCounter, + customTexts: this.customTexts, + }); + } else if ( + (await this.hasClickedDetailsPageOptionsMenu()) === true && + (await this.hasOpenedInboxTab()) === false + ) { + await this.delayBeforeStartTour(); + this.joyrideService.startTour({ + steps: ['highlightInboxTab'], + showCounter: this.showCounter, + customTexts: this.customTexts, + }); + } + } + + async showUserGuidesOnCustomCameraPage() { + if ((await this.hasOpenedCustomCameraPage()) === false) { + const avarageTimeToGetCameraPermissions = 1400; + await this.delayBeforeStartTour(avarageTimeToGetCameraPermissions); + this.joyrideService.startTour({ + steps: [ + 'highlightCustomCameraCaptureButton', + 'highlightCustomCameraFlipButton', + 'highlightCustomCameraCloseButton', + ], + customTexts: this.customTexts, + }); + } else if (!(await this.hasCapturePhotoOrVideoWithCustomCamera())) { + await this.delayBeforeStartTour(); + this.joyrideService.startTour({ + steps: ['highlightCustomCameraCaptureButton'], + showCounter: this.showCounter, + customTexts: this.customTexts, + }); + } + } + + async showUserGuidesOnActivitiesPage() { + if ((await this.hasOpenedActivitiesPage()) === false) { + await this.delayBeforeStartTour(); + this.joyrideService.startTour({ + customTexts: this.customTexts, + + steps: [ + 'highlightCaptureTransactionsTab', + 'highlightNetworkActionsTab', + ], + }); + } + } + + async showUserGuidesOnDetailsPage() { + if ((await this.hasClickedDetailsPageOptionsMenu()) === false) { + await this.delayBeforeStartTour(); + this.joyrideService.startTour({ + customTexts: this.customTexts, + + steps: ['highlightDetailsPageOptionsMenu'], + showCounter: this.showCounter, + }); + } + } + + async showUserGuidesOnInboxTab() { + if ((await this.hasOpenedInboxTab()) === false) { + await this.delayBeforeStartTour(); + this.joyrideService.startTour({ + customTexts: this.customTexts, + + steps: ['highlightImageView', 'highlightCollectionView'], + }); + } + } + + hasOpenedCustomCameraPage$() { + return this.preferences.getBoolean$( + PrefKeys.HAS_OPENED_CUSTOM_CAMERA_PAGE, + false + ); + } + + async hasOpenedCustomCameraPage() { + return this.preferences.getBoolean( + PrefKeys.HAS_OPENED_CUSTOM_CAMERA_PAGE, + false + ); + } + + async setHasOpenedCustomCameraPage(value: boolean) { + return this.preferences.setBoolean( + PrefKeys.HAS_OPENED_CUSTOM_CAMERA_PAGE, + value + ); + } + + hasCapturedPhotoWithCustomCamera$() { + return this.preferences.getBoolean$( + PrefKeys.HAS_CAPTURED_PHOTO_WITH_CUSTOM_CAMERA, + false + ); + } + + async hasCapturedPhotoWithCustomCamera() { + return await this.preferences.getBoolean( + PrefKeys.HAS_CAPTURED_PHOTO_WITH_CUSTOM_CAMERA, + false + ); + } + + async setHasCapturedPhotoWithCustomCamera(value: boolean) { + return this.preferences.setBoolean( + PrefKeys.HAS_CAPTURED_PHOTO_WITH_CUSTOM_CAMERA, + value + ); + } + + hasCapturedVideoWithCustomCamera$() { + return this.preferences.getBoolean$( + PrefKeys.HAS_CAPTURED_VIDEO_WITH_CUSTOM_CAMERA, + false + ); + } + + async hasCapturedVideoWithCustomCamera() { + return await this.preferences.getBoolean( + PrefKeys.HAS_CAPTURED_VIDEO_WITH_CUSTOM_CAMERA, + false + ); + } + + async setHasCapturedVideoWithCustomCamera(value: boolean) { + return this.preferences.setBoolean( + PrefKeys.HAS_CAPTURED_VIDEO_WITH_CUSTOM_CAMERA, + value + ); + } + + private async hasCapturePhotoOrVideoWithCustomCamera() { + return ( + (await this.hasCapturedVideoWithCustomCamera()) === true || + (await this.hasCapturedPhotoWithCustomCamera()) === true + ); + } + + hasOpenedDetailsPage$() { + return this.preferences.getBoolean$( + PrefKeys.HAS_OPENED_DETAILS_PAGE, + false + ); + } + + async hasOpenedDetailsPage() { + return await this.preferences.getBoolean( + PrefKeys.HAS_OPENED_DETAILS_PAGE, + false + ); + } + + async setHasOpenedDetailsPage(value: boolean) { + return await this.preferences.setBoolean( + PrefKeys.HAS_OPENED_DETAILS_PAGE, + value + ); + } + + hasClickedDetailsPageOptionsMenu$() { + return this.preferences.getBoolean$( + PrefKeys.HAS_CLICKED_DETAILS_PAGE_OPTIONS_MENU, + false + ); + } + + async hasClickedDetailsPageOptionsMenu() { + return await this.preferences.getBoolean( + PrefKeys.HAS_CLICKED_DETAILS_PAGE_OPTIONS_MENU, + false + ); + } + + async setHasClickedDetailsPageOptionsMenu(value: boolean) { + return this.preferences.setBoolean( + PrefKeys.HAS_CLICKED_DETAILS_PAGE_OPTIONS_MENU, + value + ); + } + hasOpenedActivitiesPage$() { + return this.preferences.getBoolean$( + PrefKeys.HAS_OPENED_ACTIVITIES_PAGE, + false + ); + } + + async hasOpenedActivitiesPage() { + return this.preferences.getBoolean( + PrefKeys.HAS_OPENED_ACTIVITIES_PAGE, + false + ); + } + async setHasOpenedActivitiesPage(value: boolean) { + return this.preferences.setBoolean( + PrefKeys.HAS_OPENED_ACTIVITIES_PAGE, + value + ); + } + + hasOpenedInboxTab$() { + return this.preferences.getBoolean$(PrefKeys.HAS_OPENED_INBOX_TAB, false); + } + + async hasOpenedInboxTab() { + return await this.preferences.getBoolean( + PrefKeys.HAS_OPENED_INBOX_TAB, + false + ); + } + + async setHasOpenedInboxTab(value: boolean) { + return this.preferences.setBoolean(PrefKeys.HAS_OPENED_INBOX_TAB, value); + } +} + +export interface UserGuideBasicCaptureFlow { + openCamera: boolean; + takePhoto: boolean; + takeVideo: boolean; + closeCamera: boolean; + openAnyCapturedItem: boolean; + openNetworkActions: boolean; +} + +export interface UserGuide { + joyrideStep: string; + title: string; + text: string; + expectedUrlPath: string; +} + +const enum PrefKeys { + HAS_OPENED_CUSTOM_CAMERA_PAGE = 'HAS_OPENED_CUSTOM_CAMERA_PAGE', + HAS_CAPTURED_PHOTO_WITH_CUSTOM_CAMERA = 'HAS_CAPTURED_PHOTO_WITH_CUSTOM_CAMERA', + HAS_CAPTURED_VIDEO_WITH_CUSTOM_CAMERA = 'HAS_CAPTURED_VIDEO_WITH_CUSTOM_CAMERA', + HAS_OPENED_DETAILS_PAGE = 'HAS_OPENED_DETAILS_PAGE', + HAS_CLICKED_DETAILS_PAGE_OPTIONS_MENU = 'HAS_CLICKED_DETAILS_PAGE_OPTIONS_MENU', + HAS_OPENED_ACTIVITIES_PAGE = 'HAS_OPENED_ACTIVITIES_PAGE', + HAS_OPENED_INBOX_TAB = 'HAS_OPENED_INBOX_TAB', +} diff --git a/src/assets/i18n/en-us.json b/src/assets/i18n/en-us.json index 7f61e7e90..c46a93ae8 100644 --- a/src/assets/i18n/en-us.json +++ b/src/assets/i18n/en-us.json @@ -300,5 +300,32 @@ "fetchingFiles": "Fetching Files", "stayOnThisScreenWithTheAppOpenToEnsureYourDownloadsAreComplete": "Stay On This Screen With The App Open To Ensure Your Downloads Are Complete", "loadingFilesForUploadToCapture": "Loading {{count}} File(s) For Upload To Capture" + }, + "userGuide": { + "capture": "Capture", + "createCapturesByTakingPhotosOrRecordingVideos": "Create Captures by taking photos or recording videos", + "cameraUsageGuide": "Camera Usage Guide", + "tapToTakeAPhotoAndLongPressToRecordVideo": "Tap to take a photo, and long press to record video", + "flipTheCameraToSwitchBetweenFrontAndBackCameras": "Flip the camera to switch between front and back cameras", + "afterTakingPhotosOrRecordingVideosCloseAndGoBackHome": "After taking photos or recording videos, close and go back Home", + "capturedItem": "Captured Item", + "openToSeeDetailsAndMoreActionItems": "Open to see details and more action items", + "optionsMenu": "Options Menu", + "clickTheOptionsMenuToUseNetworkActions": "Click the options menu to use Network Actions", + "activities": "Activities", + "viewTheHistoryOfYourCaptureAndNetworkActionTransactions": "View the history of your Capture and Network Action transactions", + "activityPage": "Activity Page", + "viewYourCaptureTransactions": "View your Capture transactions", + "activityPage2": "Activity Page", + "viewNetworkActionsHistory": "View Network Actions history", + "inboxTab": "Inbox", + "visitInboxForPurchasedItemsAndGiftsReceived": "Visit Inbox for purchased items and gifts received", + "galleryView": "Gallery View", + "browseInGalleryView": "Browse in Gallery view", + "collectionView": "Collection View", + "browseInCollectionView": "Browse in Collection view", + "previous": "Prev", + "next": "Next", + "okIGotIt": "Ok, I got it" } } diff --git a/src/assets/i18n/zh-tw.json b/src/assets/i18n/zh-tw.json index ef9b4b11c..dbe318404 100644 --- a/src/assets/i18n/zh-tw.json +++ b/src/assets/i18n/zh-tw.json @@ -300,5 +300,32 @@ "fetchingFiles": "獲取檔案", "stayOnThisScreenWithTheAppOpenToEnsureYourDownloadsAreComplete": "請勿關閉此畫面以確保您的下載完成", "loadingFilesForUploadToCapture": "加載 {{count}} 個檔案以供上傳至 Capture" + }, + "userGuide": { + "capture": "瞬時影像", + "createCapturesByTakingPhotosOrRecordingVideos": "透過拍照或錄製影片建立瞬時影像", + "cameraUsageGuide": "相機使用指南", + "tapToTakeAPhotoAndLongPressToRecordVideo": "輕點拍照,長按錄製影片", + "flipTheCameraToSwitchBetweenFrontAndBackCameras": "切換前後鏡頭", + "afterTakingPhotosOrRecordingVideosCloseAndGoBackHome": "結束拍攝後,請點選此處關閉相機", + "capturedItem": "已建立的瞬時影像", + "openToSeeDetailsAndMoreActionItems": "打開查看詳細資訊和操作項目", + "optionsMenu": "操作項目選單", + "clickTheOptionsMenuToUseNetworkActions": "點擊選單,瀏覽可操作的去中心化影像網絡應用項目", + "activities": "活動紀錄", + "viewTheHistoryOfYourCaptureAndNetworkActionTransactions": "查看瞬時影像所有權與網絡應用紀錄", + "activityPage": "瞬時影像轉移紀錄", + "viewYourCaptureTransactions": "查看瞬時影像的所有權轉移紀錄", + "activityPage2": "去中心化影像網絡應用交易紀錄", + "viewNetworkActionsHistory": "在此處查看去中心化影像網絡應用的交易紀錄", + "inboxTab": "瞬時影像收藏匣", + "visitInboxForPurchasedItemsAndGiftsReceived": "查詢收到的瞬時影像禮物與購買的瞬時影像 NFT", + "galleryView": "藝廊模式", + "browseInGalleryView": "以藝廊模式瀏覽全部的收藏品", + "collectionView": "系列模式", + "browseInCollectionView": "瀏覽以系列發行的收藏品", + "previous": "上一步", + "next": "下一步", + "okIGotIt": "好,我知道了" } } diff --git a/src/global.scss b/src/global.scss index 75857ba72..622c5f375 100644 --- a/src/global.scss +++ b/src/global.scss @@ -141,3 +141,31 @@ body.dark .mat-tab-group { .custom-camera-transparent-background { background-color: transparent; } + +// styling for ngx-joyride START +.joyride-step__container { + border-radius: 15px; + max-height: 200px; +} + +.joyride-button { + background-color: #0000d8 !important; /* stylelint-disable-line declaration-no-important */ + border-radius: 15px; +} + +.joyride-step__close { + display: none; +} + +.joyride-step__header { + padding: 0 8px 8px !important; /* stylelint-disable-line declaration-no-important */ +} + +.joyride-step__title { + color: #0000d8 !important; /* stylelint-disable-line declaration-no-important */ +} + +.joyride-step__body { + padding: 0 8px 10px !important; /* stylelint-disable-line declaration-no-important */ +} +// styling for ngx-joyride END