+ {{
+ t(
+ 'memories',
+ 'Memories can show local media on your device alongside the media on your server. This requires access to the media on this device.'
+ )
+ }}
+
+
+ {{
+ hasMediaPermission
+ ? t('memories', 'Access to media has been granted.')
+ : t(
+ 'memories',
+ 'Access to media is not available yet. If the button below does not work, grant the permission through settings.'
+ )
+ }}
+
+
+
+ {{ t('memories', 'Grant permissions') }}
+
+
+
+ {{ hasMediaPermission ? t('memories', 'Continue') : t('memories', 'Skip this step') }}
+
+
+
{{ t('memories', 'Choose the folders on this device to show on your timeline.') }}
{{
t(
@@ -41,15 +70,22 @@
-
- {{ folder.name }}
-
+
+ {{ t('memories', 'Synchronizing local files ({n} done).', { n: syncStatus }) }}
+
+ {{ t('memories', 'This may take a while. Do not close this window.') }}
+
+
+
+ {{ folder.name }}
+
+
@@ -82,17 +118,23 @@ export default defineComponent({
data: () => ({
banner,
+ hasMediaPermission: false,
step: util.uid ? 1 : 0,
localFolders: [] as nativex.LocalFolderConfig[],
+ syncStatus: -1,
+ syncStatusWatch: 0,
}),
watch: {
step() {
switch (this.step) {
case 2:
- this.localFolders = nativex.getLocalFolders();
+ this.hasMediaPermission = nativex.configHasMediaPermission();
break;
case 3:
+ this.localFolders = nativex.getLocalFolders();
+ break;
+ case 4:
this.$router.push('/');
break;
}
@@ -110,16 +152,36 @@ export default defineComponent({
// set nativex theme
nativex.setTheme(getComputedStyle(document.body).getPropertyValue('--color-background-plain'));
+
+ // set up sync status watcher
+ this.syncStatusWatch = window.setInterval(() => {
+ if (this.hasMediaPermission && this.step === 3) {
+ const newStatus = nativex.nativex.getSyncStatus();
+
+ // Refresh local folders if newly reached state -1
+ if (newStatus === -1 && this.syncStatus !== -1) {
+ this.localFolders = nativex.getLocalFolders();
+ }
+
+ this.syncStatus = newStatus;
+ }
+ }, 500);
},
beforeDestroy() {
nativex.setTheme(); // reset theme
+ window.clearInterval(this.syncStatusWatch);
},
methods: {
updateDeviceFolders() {
nativex.setLocalFolders(this.localFolders);
},
+
+ async grantMediaPermission() {
+ await nativex.configAllowMedia();
+ this.hasMediaPermission = nativex.configHasMediaPermission();
+ },
},
});
diff --git a/src/native/api.ts b/src/native/api.ts
index 144d6909..bffdd15c 100644
--- a/src/native/api.ts
+++ b/src/native/api.ts
@@ -77,6 +77,13 @@ export const NAPI = {
* @returns {void}
*/
SHARE_LOCAL: (auid: number) => `${BASE_URL}/api/share/local/${auid}`,
+
+ /**
+ * Allow usage of local media (permissions request)
+ * @param val Allow or disallow media
+ * @returns
+ */
+ CONFIG_ALLOW_MEDIA: (val: boolean) => `${BASE_URL}/api/config/allow_media/${val ? '1' : '0'}`,
};
/** NativeX synchronous API. */
@@ -159,6 +166,18 @@ export type NativeX = {
* @returns JSON-encoded array of LocalFolderConfig
*/
configGetLocalFolders: () => string;
+
+ /**
+ * Check if the user has allowed media access.
+ * @returns Whether the user has allowed media access.
+ */
+ configHasMediaPermission: () => boolean;
+
+ /**
+ * Get the current sync status.
+ * @returns number of file synced or -1
+ */
+ getSyncStatus: () => number;
};
/** The native interface is a global object that is injected by the native app. */
diff --git a/src/native/config.ts b/src/native/config.ts
index fbbd6247..b5408e2c 100644
--- a/src/native/config.ts
+++ b/src/native/config.ts
@@ -1,4 +1,4 @@
-import { nativex } from './api';
+import { NAPI, nativex } from './api';
/** Setting of whether a local folder is enabled */
export type LocalFolderConfig = {
@@ -21,3 +21,17 @@ export function setLocalFolders(config: LocalFolderConfig[]) {
export function getLocalFolders() {
return JSON.parse(nativex?.configGetLocalFolders?.() ?? '[]') as LocalFolderConfig[];
}
+
+/**
+ * Check if the user has allowed media access.
+ */
+export function configHasMediaPermission() {
+ return nativex?.configHasMediaPermission?.() ?? false;
+}
+
+/**
+ * Allow access to media.
+ */
+export async function configAllowMedia(val: boolean = true) {
+ return await fetch(NAPI.CONFIG_ALLOW_MEDIA(val));
+}
diff --git a/src/native/days.ts b/src/native/days.ts
index e82108df..ab2b7328 100644
--- a/src/native/days.ts
+++ b/src/native/days.ts
@@ -1,4 +1,3 @@
-import axios from '@nextcloud/axios';
import { NAPI } from './api';
import { API } from '../services/API';
import { has } from './basic';
@@ -70,6 +69,11 @@ export async function deleteLocalPhotos(photos: IPhoto[], dry: boolean = false):
if (!has()) return 0;
const auids = photos.map((p) => p.auid).filter((a) => !!a) as number[];
- const res = await axios.get(API.Q(NAPI.IMAGE_DELETE(auids), { dry }));
- return res.data.confirms ? res.data.count : 0;
+
+ // Delete local photos
+ const res = await fetch(API.Q(NAPI.IMAGE_DELETE(auids), { dry }));
+ if (!res.ok) throw new Error('Failed to delete photos');
+
+ const data = await res.json();
+ return data.confirms ? data.count : 0;
}