+ Open With...
+
+
+
PICSA
+
PICSA App
+
+
+
+
+ language
+
Browser
+
+
+
+
+ `,
+ styles: [
+ `
+ .picsa-app-icon {
+ background: #8a2644;
+ border-radius: 10px;
+ color: white;
+ padding: 4px;
+ line-height: 48px;
+ height: 48px;
+ width: 48px;
+ text-align: center;
+ font-size: 16px;
+ }
+ .open-option {
+ display: flex;
+ align-items: center;
+ margin-bottom: 1rem;
+ }
+ h3 {
+ flex: 1;
+ text-align: left;
+ margin-left: 1rem;
+ }
+ a {
+ text-decoration: none;
+ color: unset;
+ }
+ button {
+ width: 90px;
+ }
+ .open-icon {
+ font-size: 48px;
+ height: 48px;
+ width: 48px;
+ padding: 4px;
+ }
+ .spacer {
+ height: 1rem;
+ }
+ `,
+ ],
+})
+export class AppOpenPromptComponent {
+ appDynamicLink: string;
+ constructor(
+ deepLinksService: DeepLinksService,
+ private bottomSheet: MatBottomSheet
+ ) {
+ this.appDynamicLink = deepLinksService.config.appDynamicLink;
+ }
+
+ dismiss() {
+ this.bottomSheet.dismiss();
+ }
+}
diff --git a/libs/shared/src/modules/deep-links/deep-links.module.ts b/libs/shared/src/modules/deep-links/deep-links.module.ts
new file mode 100644
index 000000000..827d7e339
--- /dev/null
+++ b/libs/shared/src/modules/deep-links/deep-links.module.ts
@@ -0,0 +1,26 @@
+import { ModuleWithProviders, NgModule } from '@angular/core';
+import { MatBottomSheetModule } from '@angular/material/bottom-sheet';
+import { MatButtonModule } from '@angular/material/button';
+import { MatIconModule } from '@angular/material/icon';
+import { AppOpenPromptComponent } from './app-open-prompt.component';
+import { DeepLinksService, DeepLinksServiceConfig } from './deep-links.service';
+
+@NgModule({
+ imports: [MatIconModule, MatBottomSheetModule, MatButtonModule],
+ exports: [],
+ declarations: [AppOpenPromptComponent],
+})
+export class PicsaDeepLinksModule {
+ constructor(deepLinksService: DeepLinksService) {
+ deepLinksService.init();
+ }
+ // https://angular.io/guide/singleton-services#providing-a-singleton-service
+ static forRoot(
+ config: DeepLinksServiceConfig
+ ): ModuleWithProviders {
+ return {
+ ngModule: PicsaDeepLinksModule,
+ providers: [{ provide: DeepLinksServiceConfig, useValue: config }],
+ };
+ }
+}
diff --git a/libs/shared/src/modules/deep-links/deep-links.service.ts b/libs/shared/src/modules/deep-links/deep-links.service.ts
new file mode 100644
index 000000000..94e9aff64
--- /dev/null
+++ b/libs/shared/src/modules/deep-links/deep-links.service.ts
@@ -0,0 +1,62 @@
+import { Injectable, NgZone, Optional } from '@angular/core';
+import { MatBottomSheet } from '@angular/material/bottom-sheet';
+import { Router } from '@angular/router';
+import { App, URLOpenListenerEvent } from '@capacitor/app';
+import { Capacitor } from '@capacitor/core';
+import { AppOpenPromptComponent } from './app-open-prompt.component';
+
+export class DeepLinksServiceConfig {
+ /** Web url associated with deep links */
+ baseUrl: string;
+ /** E.g. firebase dynamic link */
+ appDynamicLink: string;
+}
+
+@Injectable({ providedIn: 'root' })
+/**
+ * Provide support for opening deep links within app
+ * https://capacitorjs.com/docs/guides/deep-links#angular
+ */
+export class DeepLinksService {
+ constructor(
+ @Optional() public config: DeepLinksServiceConfig,
+ private zone: NgZone,
+ private router: Router,
+ private bottomSheet: MatBottomSheet
+ ) {}
+
+ public init() {
+ if (Capacitor.isNativePlatform()) {
+ const baseUrl = this.config?.baseUrl || location.origin;
+ App.addListener('appUrlOpen', (event: URLOpenListenerEvent) => {
+ this.zone.run(() => {
+ const slug = event.url.replace(`${baseUrl}/`, '');
+ if (slug) {
+ this.router.navigateByUrl(slug);
+ }
+ });
+ });
+ } else {
+ if (this.isMobile()) {
+ // open prompt after slight delay to allow time for app to render
+ setTimeout(() => {
+ this.toggleAppOpenTargetSheet();
+ }, 2500);
+ }
+ }
+ }
+
+ private isMobile() {
+ return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(
+ navigator.userAgent
+ );
+ }
+
+ /**
+ * Present a bottom sheet to encourage user to use native version of app if running
+ * on mobile
+ */
+ private toggleAppOpenTargetSheet() {
+ this.bottomSheet.open(AppOpenPromptComponent);
+ }
+}
diff --git a/libs/shared/src/modules/index.ts b/libs/shared/src/modules/index.ts
index 0a53d674d..d5965fc1c 100644
--- a/libs/shared/src/modules/index.ts
+++ b/libs/shared/src/modules/index.ts
@@ -1,5 +1,12 @@
-import {PicsaNativeModule} from './native';
-import {PicsaDbModule} from './db.module';
-import {PicsaTranslateModule, PicsaTranslateService} from './translate'
+import { PicsaNativeModule } from './native';
+import { PicsaDbModule } from './db.module';
+import { PicsaTranslateModule, PicsaTranslateService } from './translate';
+import { PicsaDeepLinksModule } from './deep-links/deep-links.module';
-export {PicsaNativeModule, PicsaDbModule, PicsaTranslateModule, PicsaTranslateService}
\ No newline at end of file
+export {
+ PicsaNativeModule,
+ PicsaDbModule,
+ PicsaTranslateModule,
+ PicsaTranslateService,
+ PicsaDeepLinksModule,
+};
diff --git a/package.json b/package.json
index 03762cac3..c85c52ad9 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "picsa-apps",
- "version": "3.11.0",
+ "version": "3.12.0",
"license": "See LICENSE",
"scripts": {
"ng": "nx",