diff --git a/android/app/build.gradle b/android/app/build.gradle index 874c579a3..1f74d8a69 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -10,6 +10,9 @@ android { versionName "0.54.1" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } + buildFeatures { + dataBinding true + } buildTypes { release { minifyEnabled false diff --git a/android/app/capacitor.build.gradle b/android/app/capacitor.build.gradle index e390d9e4a..8dc57e7fc 100644 --- a/android/app/capacitor.build.gradle +++ b/android/app/capacitor.build.gradle @@ -25,6 +25,7 @@ dependencies { implementation project(':capacitor-share') implementation project(':capacitor-splash-screen') implementation project(':capacitor-storage') + implementation project(':numbersprotocol-preview-video') implementation project(':capacitor-blob-writer') } diff --git a/android/app/src/main/assets/capacitor.plugins.json b/android/app/src/main/assets/capacitor.plugins.json index aa76ce170..c2efb9086 100644 --- a/android/app/src/main/assets/capacitor.plugins.json +++ b/android/app/src/main/assets/capacitor.plugins.json @@ -63,6 +63,10 @@ "pkg": "@capacitor/storage", "classpath": "com.capacitorjs.plugins.storage.StoragePlugin" }, + { + "pkg": "@numbersprotocol/preview-video", + "classpath": "io.numbersprotocol.capturelite.plugins.previewvideo.PreviewVideoPlugin" + }, { "pkg": "capacitor-blob-writer", "classpath": "com.equimaps.capacitorblobwriter.BlobWriter" diff --git a/android/capacitor.settings.gradle b/android/capacitor.settings.gradle index 72c6a0451..00fd4567c 100644 --- a/android/capacitor.settings.gradle +++ b/android/capacitor.settings.gradle @@ -50,5 +50,8 @@ project(':capacitor-splash-screen').projectDir = new File('../node_modules/@capa include ':capacitor-storage' project(':capacitor-storage').projectDir = new File('../node_modules/@capacitor/storage/android') +include ':numbersprotocol-preview-video' +project(':numbersprotocol-preview-video').projectDir = new File('../node_modules/@numbersprotocol/preview-video/android') + include ':capacitor-blob-writer' project(':capacitor-blob-writer').projectDir = new File('../node_modules/capacitor-blob-writer/android') diff --git a/ios/App/Podfile b/ios/App/Podfile index 39afafc75..4af80fd62 100644 --- a/ios/App/Podfile +++ b/ios/App/Podfile @@ -25,6 +25,7 @@ def capacitor_pods pod 'CapacitorShare', :path => '../../node_modules/@capacitor/share' pod 'CapacitorSplashScreen', :path => '../../node_modules/@capacitor/splash-screen' pod 'CapacitorStorage', :path => '../../node_modules/@capacitor/storage' + pod 'NumbersprotocolPreviewVideo', :path => '../../node_modules/@numbersprotocol/preview-video' pod 'CapacitorBlobWriter', :path => '../../node_modules/capacitor-blob-writer' end diff --git a/package-lock.json b/package-lock.json index c503a8b22..fc1c8d8ba 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,7 +18,7 @@ "@angular/platform-browser-dynamic": "^12.2.4", "@angular/router": "^12.2.4", "@capacitor-community/bluetooth-le": "^1.7.0", - "@capacitor-community/http": "^1.4.1", + "@capacitor-community/http": "github:numbersprotocol/http#fix-catch-disabled-Local-Network-case-on-iOS", "@capacitor-community/wifi": "github:numbersprotocol/community-capacitor-wifi#capacitor3", "@capacitor/android": "https://gitpkg.now.sh/numbersprotocol/capacitor/android?release-3.4.1-range-request-fix", "@capacitor/app": "^1.1.0", @@ -46,6 +46,7 @@ "@ngx-formly/core": "^5.10.22", "@ngx-formly/material": "^5.10.22", "@ngx-formly/schematics": "^5.10.22", + "@numbersprotocol/preview-video": "github:numbersprotocol/preview-video", "async-mutex": "^0.3.2", "buffer": "^5.7.1", "capacitor-blob-writer": "^1.0.4", @@ -2561,8 +2562,8 @@ }, "node_modules/@capacitor-community/http": { "version": "1.4.1", - "resolved": "https://registry.npmjs.org/@capacitor-community/http/-/http-1.4.1.tgz", - "integrity": "sha512-+pCkBXrwfm97UfjOgjV950H/qZ8SE36Mrcb46BlL1ps3VIsGuIO+AulL8GqTC6LewheRVtGJpRspNtneXQotNA==", + "resolved": "git+ssh://git@github.com/numbersprotocol/http.git#a67d9fac94f8455a7fb00fc4b934ebff724afe9d", + "license": "MIT", "dependencies": { "@capacitor/android": "^3.0.0", "@capacitor/core": "^3.0.0", @@ -4116,6 +4117,14 @@ "node": ">=10" } }, + "node_modules/@numbersprotocol/preview-video": { + "version": "0.0.1", + "resolved": "git+ssh://git@github.com/numbersprotocol/preview-video.git#2d19456109c8c9d364324d9c08e766580256f1f7", + "license": "MIT", + "peerDependencies": { + "@capacitor/core": "^3.0.0" + } + }, "node_modules/@schematics/angular": { "version": "12.2.4", "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-12.2.4.tgz", @@ -27083,9 +27092,8 @@ } }, "@capacitor-community/http": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/@capacitor-community/http/-/http-1.4.1.tgz", - "integrity": "sha512-+pCkBXrwfm97UfjOgjV950H/qZ8SE36Mrcb46BlL1ps3VIsGuIO+AulL8GqTC6LewheRVtGJpRspNtneXQotNA==", + "version": "git+ssh://git@github.com/numbersprotocol/http.git#a67d9fac94f8455a7fb00fc4b934ebff724afe9d", + "from": "@capacitor-community/http@numbersprotocol/http#fix-catch-disabled-Local-Network-case-on-iOS", "requires": { "@capacitor/android": "^3.0.0", "@capacitor/core": "^3.0.0", @@ -28213,6 +28221,11 @@ } } }, + "@numbersprotocol/preview-video": { + "version": "git+ssh://git@github.com/numbersprotocol/preview-video.git#2d19456109c8c9d364324d9c08e766580256f1f7", + "from": "@numbersprotocol/preview-video@github:numbersprotocol/preview-video", + "requires": {} + }, "@schematics/angular": { "version": "12.2.4", "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-12.2.4.tgz", diff --git a/package.json b/package.json index 8c8f20238..f26844384 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,7 @@ "@angular/platform-browser-dynamic": "^12.2.4", "@angular/router": "^12.2.4", "@capacitor-community/bluetooth-le": "^1.7.0", - "@capacitor-community/http": "^1.4.1", + "@capacitor-community/http": "github:numbersprotocol/http#fix-catch-disabled-Local-Network-case-on-iOS", "@capacitor-community/wifi": "github:numbersprotocol/community-capacitor-wifi#capacitor3", "@capacitor/android": "https://gitpkg.now.sh/numbersprotocol/capacitor/android?release-3.4.1-range-request-fix", "@capacitor/app": "^1.1.0", @@ -57,6 +57,7 @@ "@ngx-formly/core": "^5.10.22", "@ngx-formly/material": "^5.10.22", "@ngx-formly/schematics": "^5.10.22", + "@numbersprotocol/preview-video": "github:numbersprotocol/preview-video", "async-mutex": "^0.3.2", "buffer": "^5.7.1", "capacitor-blob-writer": "^1.0.4", diff --git a/src/app/features/home/home.page.ts b/src/app/features/home/home.page.ts index 685e67838..9015fabe2 100644 --- a/src/app/features/home/home.page.ts +++ b/src/app/features/home/home.page.ts @@ -172,7 +172,7 @@ export class HomePage { takePicture: null, recordVideo: null, }), - this.goProBluetoothService.connectedDevice$, + this.goProBluetoothService.lastConnectedDevice$, ]).pipe( first(), concatMap(([translations, connectedDevice]) => { diff --git a/src/app/features/settings/go-pro/go-pro-media-item-detail-on-camera/go-pro-media-item-detail-on-camera.component.html b/src/app/features/settings/go-pro/go-pro-media-item-detail-on-camera/go-pro-media-item-detail-on-camera.component.html index 1ce9a20cf..20ebddae1 100644 --- a/src/app/features/settings/go-pro/go-pro-media-item-detail-on-camera/go-pro-media-item-detail-on-camera.component.html +++ b/src/app/features/settings/go-pro/go-pro-media-item-detail-on-camera/go-pro-media-item-detail-on-camera.component.html @@ -15,15 +15,16 @@ loading="lazy" > - +
+
+ + play_circle_outline +
{{ t('gopro.connectedWiFi') }}: {{ connectedWifiSSID }} {{ t('gopro.connectToGoProWiFi') }} @@ -40,7 +41,6 @@

{{ t('gopro.connectedWiFi') }}: {{ connectedWifiSSID }}

{{ t('gopro.uploadToCapture') }}
-

{{ t('gopro.makeSureYouAreConnectedToGoProWiFiFirst') }}

diff --git a/src/app/features/settings/go-pro/go-pro-media-list-on-camera/go-pro-media-list-on-camera.component.ts b/src/app/features/settings/go-pro/go-pro-media-list-on-camera/go-pro-media-list-on-camera.component.ts index f57c7538c..9febd7a56 100644 --- a/src/app/features/settings/go-pro/go-pro-media-list-on-camera/go-pro-media-list-on-camera.component.ts +++ b/src/app/features/settings/go-pro/go-pro-media-list-on-camera/go-pro-media-list-on-camera.component.ts @@ -4,6 +4,7 @@ import { Router } from '@angular/router'; import { AlertController, NavController, + Platform, ToastController, } from '@ionic/angular'; import { GoProFile } from '../go-pro-media-file'; @@ -25,6 +26,7 @@ export class GoProMediaListOnCameraComponent implements OnInit { connectedWifiSSID: string | null = null; isConnectedToGoProWifi: boolean | undefined; + isConnectingToGoProWifi: boolean | undefined; isScrollingContent = false; @@ -42,6 +44,7 @@ export class GoProMediaListOnCameraComponent implements OnInit { private readonly alertCtrl: AlertController, private readonly goProBluetoothService: GoProBluetoothService, private readonly goProWifiService: GoProWifiService, + private readonly platform: Platform, public toastController: ToastController ) {} @@ -64,21 +67,34 @@ export class GoProMediaListOnCameraComponent implements OnInit { this.fetchingFilesError = undefined; this.fetchingFiles = true; this.allMediaFiles = await this.goProMediaService.getFilesFromGoPro(); - this.fetchingFiles = false; } catch (error: any) { - this.fetchingFilesError = error.toString(); + this.fetchingFilesError = 'Failed to fetch media from GoPro'; + if (this.platform.is('ios')) { + this.fetchingFilesError = + 'Please check iOS Settings > Capture > Local Network, make sure the permission of Local Network is allowed Capture app.'; + } this.allMediaFiles = []; + } finally { this.fetchingFiles = false; } } async connectToGoProWifi() { try { + this.isConnectingToGoProWifi = true; + + if (!(await this.goProBluetoothService.getConnectedDevice())) { + alert('Connect to GoPro via bluetooth first'); + // I need to show alert because when catching error below for some reason it's empty + throw new Error('Connect to GoPro via bluetooth first'); + } this.connectedWifiSSID = await this.goProWifiService.connectToGoProWiFi(); this.isConnectedToGoProWifi = true; await this.fetchFilesFromGoProWiFi(); } catch (error) { this.presentToast(JSON.stringify(error)); + } finally { + this.isConnectingToGoProWifi = false; } } diff --git a/src/app/features/settings/go-pro/go-pro-media-viewer-with-native-player/go-pro-media-viewer-with-native-player.component.html b/src/app/features/settings/go-pro/go-pro-media-viewer-with-native-player/go-pro-media-viewer-with-native-player.component.html new file mode 100644 index 000000000..d76d5520c --- /dev/null +++ b/src/app/features/settings/go-pro/go-pro-media-viewer-with-native-player/go-pro-media-viewer-with-native-player.component.html @@ -0,0 +1,5 @@ + + + + +

go-pro-media-viewer-with-native-player

diff --git a/src/app/features/settings/go-pro/go-pro-media-viewer-with-native-player/go-pro-media-viewer-with-native-player.component.scss b/src/app/features/settings/go-pro/go-pro-media-viewer-with-native-player/go-pro-media-viewer-with-native-player.component.scss new file mode 100644 index 000000000..70df54079 --- /dev/null +++ b/src/app/features/settings/go-pro/go-pro-media-viewer-with-native-player/go-pro-media-viewer-with-native-player.component.scss @@ -0,0 +1,15 @@ +:host { + background-color: black; +} + +.dismiss { + position: absolute; + left: 0; + z-index: 1; + width: 48px; + height: 48px; + + --border-radius: 50%; + --padding-end: 0; + --padding-start: 0; +} diff --git a/src/app/features/settings/go-pro/go-pro-media-viewer-with-native-player/go-pro-media-viewer-with-native-player.component.spec.ts b/src/app/features/settings/go-pro/go-pro-media-viewer-with-native-player/go-pro-media-viewer-with-native-player.component.spec.ts new file mode 100644 index 000000000..cc7bfa756 --- /dev/null +++ b/src/app/features/settings/go-pro/go-pro-media-viewer-with-native-player/go-pro-media-viewer-with-native-player.component.spec.ts @@ -0,0 +1,28 @@ +import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; +import { IonicModule } from '@ionic/angular'; + +import { GoProMediaViewerWithNativePlayerComponent } from './go-pro-media-viewer-with-native-player.component'; + +describe('GoProMediaViewerWithNativePlayerComponent', () => { + let component: GoProMediaViewerWithNativePlayerComponent; + let fixture: ComponentFixture; + + beforeEach( + waitForAsync(() => { + TestBed.configureTestingModule({ + declarations: [GoProMediaViewerWithNativePlayerComponent], + imports: [IonicModule.forRoot()], + }).compileComponents(); + + fixture = TestBed.createComponent( + GoProMediaViewerWithNativePlayerComponent + ); + component = fixture.componentInstance; + fixture.detectChanges(); + }) + ); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/features/settings/go-pro/go-pro-media-viewer-with-native-player/go-pro-media-viewer-with-native-player.component.ts b/src/app/features/settings/go-pro/go-pro-media-viewer-with-native-player/go-pro-media-viewer-with-native-player.component.ts new file mode 100644 index 000000000..181740813 --- /dev/null +++ b/src/app/features/settings/go-pro/go-pro-media-viewer-with-native-player/go-pro-media-viewer-with-native-player.component.ts @@ -0,0 +1,52 @@ +import { Component, OnDestroy, OnInit } from '@angular/core'; +import { ActivatedRoute, Router } from '@angular/router'; +import { NavController } from '@ionic/angular'; +import { PreviewVideo } from '@numbersprotocol/preview-video'; +import { GoProFile } from '../go-pro-media-file'; + +@Component({ + selector: 'app-go-pro-media-viewer-with-native-player', + templateUrl: './go-pro-media-viewer-with-native-player.component.html', + styleUrls: ['./go-pro-media-viewer-with-native-player.component.scss'], +}) +export class GoProMediaViewerWithNativePlayerComponent + implements OnInit, OnDestroy +{ + mediaFile: GoProFile | undefined; + + onIOSPlayerDismissedListener?: any; + + constructor( + private readonly route: ActivatedRoute, + private readonly router: Router, + private readonly navController: NavController + ) { + this.route.queryParams.subscribe(_ => { + const state = this.router.getCurrentNavigation()?.extras.state; + if (state) { + this.mediaFile = state.goProMediaFile; + } + }); + } + + ngOnInit(): void { + if (!this.mediaFile?.url) return; + PreviewVideo.playFullScreenFromRemote({ url: this.mediaFile.url }); + PreviewVideo.addListener('iosPlayerDismissed', (_info: any) => { + // eslint-disable-next-line no-console + console.log('ITS WORKING'); + this.navController.back(); + }); + } + + ngOnDestroy(): void { + PreviewVideo.stopFullScreen(); + // TODO: check if .remove() really get called + this.onIOSPlayerDismissedListener?.remove(); + } + + dismiss() { + PreviewVideo.stopFullScreen(); + this.navController.back(); + } +} diff --git a/src/app/features/settings/go-pro/go-pro-routing.module.ts b/src/app/features/settings/go-pro/go-pro-routing.module.ts index 04bc09385..4c63210fc 100644 --- a/src/app/features/settings/go-pro/go-pro-routing.module.ts +++ b/src/app/features/settings/go-pro/go-pro-routing.module.ts @@ -2,6 +2,7 @@ import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; import { GoProMediaItemDetailOnCameraComponent } from './go-pro-media-item-detail-on-camera/go-pro-media-item-detail-on-camera.component'; import { GoProMediaListOnCameraComponent } from './go-pro-media-list-on-camera/go-pro-media-list-on-camera.component'; +import { GoProMediaViewerWithNativePlayerComponent } from './go-pro-media-viewer-with-native-player/go-pro-media-viewer-with-native-player.component'; import { GoProPage } from './go-pro.page'; const routes: Routes = [ @@ -17,6 +18,10 @@ const routes: Routes = [ path: 'media-item-detail-on-camera', component: GoProMediaItemDetailOnCameraComponent, }, + { + path: 'media-item-viewer', + component: GoProMediaViewerWithNativePlayerComponent, + }, ]; @NgModule({ diff --git a/src/app/features/settings/go-pro/go-pro.module.ts b/src/app/features/settings/go-pro/go-pro.module.ts index 8b8e739e6..b4f8d245c 100644 --- a/src/app/features/settings/go-pro/go-pro.module.ts +++ b/src/app/features/settings/go-pro/go-pro.module.ts @@ -5,6 +5,7 @@ import { GoProMediaItemDetailOnCameraComponent } from './go-pro-media-item-detai import { GoProMediaListItemOnCameraComponent } from './go-pro-media-list-item-on-camera/go-pro-media-list-item-on-camera.component'; import { GoProMediaListOnCameraComponent } from './go-pro-media-list-on-camera/go-pro-media-list-on-camera.component'; import { GoProMediaLoadingBarComponent } from './go-pro-media-loading-bar/go-pro-media-loading-bar.component'; +import { GoProMediaViewerWithNativePlayerComponent } from './go-pro-media-viewer-with-native-player/go-pro-media-viewer-with-native-player.component'; import { GoProPageRoutingModule } from './go-pro-routing.module'; import { GoProPage } from './go-pro.page'; @@ -16,6 +17,7 @@ import { GoProPage } from './go-pro.page'; GoProMediaListItemOnCameraComponent, GoProMediaItemDetailOnCameraComponent, GoProMediaLoadingBarComponent, + GoProMediaViewerWithNativePlayerComponent, ], }) export class GoProPageModule {} diff --git a/src/app/features/settings/go-pro/services/go-pro-bluetooth.service.ts b/src/app/features/settings/go-pro/services/go-pro-bluetooth.service.ts index 8294fe2bc..1749c1150 100644 --- a/src/app/features/settings/go-pro/services/go-pro-bluetooth.service.ts +++ b/src/app/features/settings/go-pro/services/go-pro-bluetooth.service.ts @@ -65,6 +65,10 @@ export class GoProBluetoothService { undefined ); + readonly lastConnectedDevice$ = this.preferences.getString$( + PrefKeys.LAST_CONNECTED_BLUETOOTH_DEVICE + ); + constructor(private readonly preferenceManager: PreferenceManager) {} private async initialize() {