nx: add more interfaces
Signed-off-by: Varun Patil <radialapps@gmail.com>pull/653/head
parent
b889c5f5f7
commit
917fccf0da
|
@ -176,6 +176,13 @@ class DaysController extends GenericApiController
|
||||||
*/
|
*/
|
||||||
private function preloadDays(array &$days)
|
private function preloadDays(array &$days)
|
||||||
{
|
{
|
||||||
|
// Do not preload anything for native clients.
|
||||||
|
// Since the contents of preloads are trusted, clients will not load locals.
|
||||||
|
if (Util::callerIsNative()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build identical transforms for sub queries
|
||||||
$transforms = $this->getTransformations(false);
|
$transforms = $this->getTransformations(false);
|
||||||
$preloaded = 0;
|
$preloaded = 0;
|
||||||
$preloadDayIds = [];
|
$preloadDayIds = [];
|
||||||
|
|
|
@ -59,8 +59,7 @@ class PageController extends Controller
|
||||||
$response->cacheFor(0);
|
$response->cacheFor(0);
|
||||||
|
|
||||||
// Check if requested from native app
|
// Check if requested from native app
|
||||||
$userAgent = $this->request->getHeader('User-Agent');
|
if (!Util::callerIsNative()) {
|
||||||
if (false === strpos($userAgent, 'memories-native')) {
|
|
||||||
$this->eventDispatcher->dispatchTyped(new LoadSidebar());
|
$this->eventDispatcher->dispatchTyped(new LoadSidebar());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
11
lib/Util.php
11
lib/Util.php
|
@ -446,6 +446,17 @@ class Util
|
||||||
return self::getSystemConfig('instanceid', 'default', true);
|
return self::getSystemConfig('instanceid', 'default', true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the API call was made from a native interface.
|
||||||
|
*/
|
||||||
|
public static function callerIsNative(): bool
|
||||||
|
{
|
||||||
|
$request = \OC::$server->get(\OCP\IRequest::class);
|
||||||
|
$userAgent = $request->getHeader('User-Agent');
|
||||||
|
|
||||||
|
return false !== strpos($userAgent, 'memories-native');
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Kill all instances of a process by name.
|
* Kill all instances of a process by name.
|
||||||
* Similar to pkill, which may not be available on all systems.
|
* Similar to pkill, which may not be available on all systems.
|
||||||
|
|
|
@ -65,6 +65,7 @@ import { translate as t } from '@nextcloud/l10n';
|
||||||
import { emit, subscribe } from '@nextcloud/event-bus';
|
import { emit, subscribe } from '@nextcloud/event-bus';
|
||||||
|
|
||||||
import * as utils from './services/Utils';
|
import * as utils from './services/Utils';
|
||||||
|
import * as nativex from './native';
|
||||||
import UserConfig from './mixins/UserConfig';
|
import UserConfig from './mixins/UserConfig';
|
||||||
import Timeline from './components/Timeline.vue';
|
import Timeline from './components/Timeline.vue';
|
||||||
import Settings from './components/Settings.vue';
|
import Settings from './components/Settings.vue';
|
||||||
|
@ -248,7 +249,7 @@ export default defineComponent({
|
||||||
);
|
);
|
||||||
|
|
||||||
// Check for native interface
|
// Check for native interface
|
||||||
if (window.nativex?.isNative()) {
|
if (nativex?.has()) {
|
||||||
document.body.classList.add('native');
|
document.body.classList.add('native');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -123,6 +123,7 @@ import TopMatter from './top-matter/TopMatter.vue';
|
||||||
import * as dav from '../services/DavRequests';
|
import * as dav from '../services/DavRequests';
|
||||||
import * as utils from '../services/Utils';
|
import * as utils from '../services/Utils';
|
||||||
import * as strings from '../services/strings';
|
import * as strings from '../services/strings';
|
||||||
|
import * as nativex from '../native';
|
||||||
|
|
||||||
import { API, DaysFilterType } from '../services/API';
|
import { API, DaysFilterType } from '../services/API';
|
||||||
|
|
||||||
|
@ -908,6 +909,16 @@ export default defineComponent({
|
||||||
dayMap.get(photo.dayid)!.push(photo);
|
dayMap.get(photo.dayid)!.push(photo);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get local images if we are running in native environment.
|
||||||
|
// Get them all together for each day here.
|
||||||
|
if (nativex.has()) {
|
||||||
|
const promises: Promise<void>[] = [];
|
||||||
|
for (const [dayId, photos] of dayMap) {
|
||||||
|
promises.push(nativex.extendDayWithLocal(dayId, photos));
|
||||||
|
}
|
||||||
|
await Promise.all(promises);
|
||||||
|
}
|
||||||
|
|
||||||
// Store cache asynchronously
|
// Store cache asynchronously
|
||||||
// Do this regardless of whether the state has
|
// Do this regardless of whether the state has
|
||||||
// changed since the data is already fetched
|
// changed since the data is already fetched
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import { API } from '../../services/API';
|
import { API } from '../../services/API';
|
||||||
import { workerImporter } from '../../worker';
|
import { workerImporter } from '../../worker';
|
||||||
|
import * as nativex from '../../native';
|
||||||
import type * as w from './XImgWorker';
|
import type * as w from './XImgWorker';
|
||||||
|
|
||||||
// Global web worker to fetch images
|
// Global web worker to fetch images
|
||||||
|
@ -65,6 +66,13 @@ export async function fetchImage(url: string) {
|
||||||
let entry = BLOB_CACHE.get(url);
|
let entry = BLOB_CACHE.get(url);
|
||||||
if (entry) return entry[1];
|
if (entry) return entry[1];
|
||||||
|
|
||||||
|
// Check if native image
|
||||||
|
if (nativex.IS_NATIVE_URL(url)) {
|
||||||
|
const dataUri = await nativex.getJpegDataUri(url);
|
||||||
|
BLOB_CACHE.set(url, [60, dataUri]);
|
||||||
|
return dataUri;
|
||||||
|
}
|
||||||
|
|
||||||
// Fetch image
|
// Fetch image
|
||||||
const blobUrl = await importer<typeof w.fetchImageSrc>('fetchImageSrc')(url);
|
const blobUrl = await importer<typeof w.fetchImageSrc>('fetchImageSrc')(url);
|
||||||
|
|
||||||
|
|
|
@ -13,7 +13,6 @@ import { getRequestToken } from '@nextcloud/auth';
|
||||||
|
|
||||||
import type { Route } from 'vue-router';
|
import type { Route } from 'vue-router';
|
||||||
import type { IPhoto } from './types';
|
import type { IPhoto } from './types';
|
||||||
import type { NativeX } from './types-native';
|
|
||||||
import type PlyrType from 'plyr';
|
import type PlyrType from 'plyr';
|
||||||
import type videojsType from 'video.js';
|
import type videojsType from 'video.js';
|
||||||
|
|
||||||
|
@ -51,8 +50,6 @@ declare global {
|
||||||
var Plyr: typeof PlyrType;
|
var Plyr: typeof PlyrType;
|
||||||
var videoClientId: string;
|
var videoClientId: string;
|
||||||
var videoClientIdPersistent: string;
|
var videoClientIdPersistent: string;
|
||||||
|
|
||||||
var nativex: NativeX | undefined;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Allow global access to the router
|
// Allow global access to the router
|
||||||
|
|
|
@ -0,0 +1,144 @@
|
||||||
|
import type { IPhoto } from './types';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Type of a native promise (this will be the exact type in Java).
|
||||||
|
*/
|
||||||
|
type NativePromise<T> = (call: string, arg: T) => void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Native interface for the Android app.
|
||||||
|
*/
|
||||||
|
export type NativeX = {
|
||||||
|
isNative: () => boolean;
|
||||||
|
getLocalByDayId: NativePromise<string>;
|
||||||
|
getJpeg: NativePromise<string>;
|
||||||
|
};
|
||||||
|
|
||||||
|
/** The native interface is a global object that is injected by the native app. */
|
||||||
|
const nativex: NativeX = globalThis.nativex;
|
||||||
|
|
||||||
|
/** List of promises that are waiting for a native response. */
|
||||||
|
const nativePromises = new Map<string, object>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wraps a native function in a promise.
|
||||||
|
* JavascriptInterface doesn't support async functions, so we have to do this manually.
|
||||||
|
* The native function should call `window.nativexr(call, resolve, reject)` when it's done.
|
||||||
|
*
|
||||||
|
* @param fun Function to promisify
|
||||||
|
* @param binary Whether the response is binary (will not be decoded)
|
||||||
|
*/
|
||||||
|
function nativePromisify<A, T>(fun: NativePromise<A>, binary = false): (arg: A) => Promise<T> {
|
||||||
|
if (!fun) {
|
||||||
|
return () => {
|
||||||
|
return new Promise((_, reject) => {
|
||||||
|
reject('Native function not available');
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return (arg: A) => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const call = Math.random().toString(36).substring(7);
|
||||||
|
nativePromises.set(call, { resolve, reject, binary });
|
||||||
|
fun(call, arg);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers the global handler for native responses.
|
||||||
|
* This should be called by the native app when it's ready to resolve a promise.
|
||||||
|
*
|
||||||
|
* @param call ID passed to native function
|
||||||
|
* @param resolve Response from native function
|
||||||
|
* @param reject Rejection from native function
|
||||||
|
*/
|
||||||
|
globalThis.nativexr = (call: string, resolve?: string, reject?: string) => {
|
||||||
|
const promise = nativePromises.get(call);
|
||||||
|
if (!promise) {
|
||||||
|
console.error('No promise found for call', call);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (resolve !== undefined) {
|
||||||
|
if (!(promise as any).binary) resolve = window.atob(resolve);
|
||||||
|
(promise as any).resolve(resolve);
|
||||||
|
} else if (reject !== undefined) {
|
||||||
|
(promise as any).reject(window.atob(reject));
|
||||||
|
} else {
|
||||||
|
console.error('No resolve or reject found for call', call);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
nativePromises.delete(call);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns Whether the native interface is available.
|
||||||
|
*/
|
||||||
|
export const has = () => !!nativex;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the local photos for a day with a dayId.
|
||||||
|
*
|
||||||
|
* @param dayId Day ID to get photos for
|
||||||
|
* @returns List of local photos (JSON string)
|
||||||
|
*/
|
||||||
|
const getLocalByDayId = nativePromisify<number, string>(nativex?.getLocalByDayId.bind(nativex));
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the JPEG data for a photo using a local URI.
|
||||||
|
*
|
||||||
|
* @param url Local URI to get JPEG data for
|
||||||
|
* @returns JPEG data (base64 string)
|
||||||
|
*/
|
||||||
|
const getJpeg = nativePromisify<string, string>(nativex?.getJpeg.bind(nativex), true);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extend a list of photos with local photos.
|
||||||
|
* Fetches the local photos from the native interface and filters out duplicates.
|
||||||
|
*
|
||||||
|
* @param dayId Day ID to append local photos to
|
||||||
|
* @param photos List of photos to append to (duplicates will not be added)
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export async function extendDayWithLocal(dayId: number, photos: IPhoto[]) {
|
||||||
|
if (!has()) return;
|
||||||
|
|
||||||
|
const localPhotos: IPhoto[] = JSON.parse(await getLocalByDayId(dayId));
|
||||||
|
const photosSet = new Set(photos.map((p) => p.basename));
|
||||||
|
const localOnly = localPhotos.filter((p) => !photosSet.has(p.basename));
|
||||||
|
localOnly.forEach((p) => (p.islocal = true));
|
||||||
|
photos.push(...localOnly);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the JPEG data URI for a photo using a native URI.
|
||||||
|
*
|
||||||
|
* @param url Native URI to get JPEG data for
|
||||||
|
* @returns Data URI for JPEG
|
||||||
|
*/
|
||||||
|
export async function getJpegDataUri(url: string) {
|
||||||
|
const image = await getJpeg(url);
|
||||||
|
return `data:image/jpeg;base64,${image}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks whether a URL is a native URI (nativex://).
|
||||||
|
*
|
||||||
|
* @param url URL to check
|
||||||
|
*/
|
||||||
|
export function IS_NATIVE_URL(url: string) {
|
||||||
|
return url.startsWith('nativex://');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a downsized preview URL for a native file ID.
|
||||||
|
*
|
||||||
|
* @param fileid Local file ID returned by native interface
|
||||||
|
* @returns native URI
|
||||||
|
*/
|
||||||
|
export function NATIVE_URL_PREVIEW(fileid: number) {
|
||||||
|
return `nativex://preview/${fileid}`;
|
||||||
|
}
|
|
@ -9,6 +9,7 @@ export const constants = {
|
||||||
FLAG_IS_FAVORITE: 1 << 3,
|
FLAG_IS_FAVORITE: 1 << 3,
|
||||||
FLAG_SELECTED: 1 << 4,
|
FLAG_SELECTED: 1 << 4,
|
||||||
FLAG_LEAVING: 1 << 5,
|
FLAG_LEAVING: 1 << 5,
|
||||||
|
FLAG_IS_LOCAL: 1 << 6,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -30,4 +31,8 @@ export function convertFlags(photo: IPhoto) {
|
||||||
photo.flag |= constants.c.FLAG_IS_FAVORITE;
|
photo.flag |= constants.c.FLAG_IS_FAVORITE;
|
||||||
delete photo.isfavorite;
|
delete photo.isfavorite;
|
||||||
}
|
}
|
||||||
|
if (photo.islocal) {
|
||||||
|
photo.flag |= constants.c.FLAG_IS_LOCAL;
|
||||||
|
delete photo.islocal;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,15 @@
|
||||||
import { IImageInfo, IPhoto } from '../../types';
|
import { IImageInfo, IPhoto } from '../../types';
|
||||||
import { API } from '../API';
|
import { API } from '../API';
|
||||||
|
import { constants } from './const';
|
||||||
|
import * as nativex from '../../native';
|
||||||
|
|
||||||
/** Get preview URL from photo object */
|
/** Get preview URL from photo object */
|
||||||
export function getPreviewUrl(photo: IPhoto, square: boolean, size: number | [number, number] | 'screen') {
|
export function getPreviewUrl(photo: IPhoto, square: boolean, size: number | [number, number] | 'screen') {
|
||||||
|
// Native preview
|
||||||
|
if (photo.flag & constants.c.FLAG_IS_LOCAL) {
|
||||||
|
return nativex.NATIVE_URL_PREVIEW(photo.fileid);
|
||||||
|
}
|
||||||
|
|
||||||
// Screen-appropriate size
|
// Screen-appropriate size
|
||||||
if (size === 'screen') {
|
if (size === 'screen') {
|
||||||
const sw = Math.floor(screen.width * devicePixelRatio);
|
const sw = Math.floor(screen.width * devicePixelRatio);
|
||||||
|
|
|
@ -1,3 +0,0 @@
|
||||||
export type NativeX = {
|
|
||||||
isNative: () => boolean;
|
|
||||||
};
|
|
|
@ -71,6 +71,8 @@ export type IPhoto = {
|
||||||
video_duration?: number;
|
video_duration?: number;
|
||||||
/** Favorite flag from server */
|
/** Favorite flag from server */
|
||||||
isfavorite?: boolean;
|
isfavorite?: boolean;
|
||||||
|
/** Local file from native */
|
||||||
|
islocal?: boolean;
|
||||||
/** Optional datetaken epoch */
|
/** Optional datetaken epoch */
|
||||||
datetaken?: number;
|
datetaken?: number;
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in New Issue