From 07e91a0d09d4af8e7aeaf909ef17df7eefd7a3b1 Mon Sep 17 00:00:00 2001 From: Myx Date: Thu, 11 Jun 2026 22:31:40 +0200 Subject: [PATCH] fix: Bug - Add logout in mobile version of settings, allow clearing data on android Expose settings logout on mobile where the title bar is hidden, and enable Capacitor data settings with storage visibility and local erase/sign-out. Co-authored-by: Cursor --- agents-docs/features/authentication.md | 6 + agents-docs/features/mobile-capacitor.md | 1 + e2e/helpers/settings-modal.ts | 76 +++++++++++++ .../mobile/mobile-settings-logout.spec.ts | 25 +++++ toju-app/public/i18n/catalog/settings.json | 8 +- toju-app/public/i18n/en.json | 8 +- .../services/auth-token-store.service.spec.ts | 9 ++ .../services/auth-token-store.service.ts | 6 + .../services/user-logout.service.spec.ts | 66 +++++++++++ .../services/user-logout.service.ts | 28 +++++ .../src/app/domains/authentication/index.ts | 1 + .../data-settings-capability.rules.spec.ts | 29 +++++ .../logic/data-settings-capability.rules.ts | 16 +++ .../data-settings.component.html | 64 ++++++++--- .../data-settings/data-settings.component.ts | 46 +++++++- .../settings-modal.component.html | 16 ++- .../settings-modal.component.ts | 13 ++- .../shell/title-bar/title-bar.component.ts | 16 +-- .../local-user-data.service.spec.ts | 104 ++++++++++++++++++ .../persistence/local-user-data.service.ts | 54 +++++++++ 20 files changed, 553 insertions(+), 39 deletions(-) create mode 100644 e2e/helpers/settings-modal.ts create mode 100644 e2e/tests/mobile/mobile-settings-logout.spec.ts create mode 100644 toju-app/src/app/domains/authentication/application/services/user-logout.service.spec.ts create mode 100644 toju-app/src/app/domains/authentication/application/services/user-logout.service.ts create mode 100644 toju-app/src/app/features/settings/domain/logic/data-settings-capability.rules.spec.ts create mode 100644 toju-app/src/app/features/settings/domain/logic/data-settings-capability.rules.ts create mode 100644 toju-app/src/app/infrastructure/persistence/local-user-data.service.spec.ts create mode 100644 toju-app/src/app/infrastructure/persistence/local-user-data.service.ts diff --git a/agents-docs/features/authentication.md b/agents-docs/features/authentication.md index 838a539..b4b24e4 100644 --- a/agents-docs/features/authentication.md +++ b/agents-docs/features/authentication.md @@ -12,6 +12,12 @@ Session-token authentication for the signaling server and product client. | Electron Local API | Separate in-memory bearer tokens | Proxies login to allowed signaling servers only | | Product client local DB | OS user account | SQLite and attachments are plaintext at rest | +## Client logout + +- Desktop: title-bar menu **Logout** (`UserLogoutService`). +- Mobile / all platforms: settings modal footer **Logout** (`data-testid="settings-logout-button"`) — required because the title bar is hidden on mobile breakpoints. +- Logout disconnects realtime sessions, clears the persisted current-user id, resets NgRx room/user/message state, and navigates to `/login`. + ## Login / register response ```json diff --git a/agents-docs/features/mobile-capacitor.md b/agents-docs/features/mobile-capacitor.md index 9058d15..3348c94 100644 --- a/agents-docs/features/mobile-capacitor.md +++ b/agents-docs/features/mobile-capacitor.md @@ -256,6 +256,7 @@ Network security configs: - `MobileCallSessionService` — CallKit + foreground service + in-call notifications. - `App` bootstrap — initializes mobile persistence, lifecycle, app-update polling, call-session, and push registration wiring. - `MobileAppUpdateService` — periodic Play Store / App Store checks (30 min) and settings UI actions; mirrors Electron `DesktopAppUpdateService` polling but uses native store APIs instead of release manifests. +- Settings → **Data** on Capacitor shells shows the private app-data root and **Erase user data** (`LocalUserDataService` clears SQLite, Capacitor attachment files, auth tokens, and `metoyou_*` localStorage keys, then logs out). ## Phase 3 completion notes diff --git a/e2e/helpers/settings-modal.ts b/e2e/helpers/settings-modal.ts new file mode 100644 index 0000000..521dc91 --- /dev/null +++ b/e2e/helpers/settings-modal.ts @@ -0,0 +1,76 @@ +import { expect, type Page } from '@playwright/test'; + +const MOBILE_VIEWPORT = { width: 390, height: 844 }; + +export async function openSettingsModal(page: Page, settingsPage = 'general'): Promise { + await page.evaluate((targetPage) => { + interface SettingsModalServiceHandle { + open: (page: string) => void; + } + interface SettingsModalComponentHandle { + mobilePage?: { set: (page: 'menu' | 'detail') => void }; + animating?: { set: (value: boolean) => void }; + navigate?: (page: string) => void; + } + interface AppComponentHandle { + settingsModal?: SettingsModalServiceHandle; + } + interface AngularDebugApi { + getComponent: (element: Element) => AppComponentHandle & SettingsModalComponentHandle; + applyChanges?: (component: unknown) => void; + } + + const debugApi = (window as Window & { ng?: AngularDebugApi }).ng; + const appRoot = document.querySelector('app-root'); + const settingsHost = document.querySelector('app-settings-modal'); + const appComponent = appRoot && debugApi?.getComponent(appRoot); + const settingsComponent = settingsHost && debugApi?.getComponent(settingsHost); + + if (!appComponent?.settingsModal?.open) { + throw new Error('Angular debug API could not open settings modal'); + } + + appComponent.settingsModal.open(targetPage); + settingsComponent?.mobilePage?.set('menu'); + settingsComponent?.animating?.set(true); + debugApi?.applyChanges?.(appComponent); + debugApi?.applyChanges?.(settingsComponent); + }, settingsPage); + + await expect(page.getByRole('heading', { name: 'Settings', exact: true })).toBeVisible({ timeout: 10_000 }); + await expect(page.getByTestId('settings-logout-button')).toBeVisible({ timeout: 10_000 }); +} + +export async function openSettingsDetailPage(page: Page, settingsPage: string): Promise { + await openSettingsModal(page, settingsPage); + + await page.evaluate((targetPage) => { + interface SettingsModalComponentHandle { + navigate?: (page: string) => void; + animating?: { set: (value: boolean) => void }; + } + interface AngularDebugApi { + getComponent: (element: Element) => SettingsModalComponentHandle; + applyChanges?: (component: SettingsModalComponentHandle) => void; + } + + const host = document.querySelector('app-settings-modal'); + const debugApi = (window as Window & { ng?: AngularDebugApi }).ng; + const component = host && debugApi?.getComponent(host); + + if (!component?.navigate) { + throw new Error('Angular debug API could not navigate settings modal'); + } + + component.navigate(targetPage); + component.animating?.set(true); + debugApi?.applyChanges?.(component); + }, settingsPage); +} + +export async function openSettingsDataPage(page: Page): Promise { + await openSettingsDetailPage(page, 'data'); + await expect(page.locator('app-data-settings')).toBeVisible({ timeout: 10_000 }); +} + +export { MOBILE_VIEWPORT }; diff --git a/e2e/tests/mobile/mobile-settings-logout.spec.ts b/e2e/tests/mobile/mobile-settings-logout.spec.ts new file mode 100644 index 0000000..3a39c0f --- /dev/null +++ b/e2e/tests/mobile/mobile-settings-logout.spec.ts @@ -0,0 +1,25 @@ +import { expect, test } from '../../fixtures/multi-client'; +import { expectDashboardReady } from '../../helpers/dashboard'; +import { MOBILE_VIEWPORT, openSettingsModal } from '../../helpers/settings-modal'; +import { RegisterPage } from '../../pages/register.page'; + +test.describe('Mobile settings logout', () => { + test('exposes logout in the settings menu on mobile viewports', async ({ createClient }) => { + const { page } = await createClient(); + const suffix = `mobile_logout_${Date.now()}`; + + await page.setViewportSize(MOBILE_VIEWPORT); + + const register = new RegisterPage(page); + + await register.goto(); + await register.register(`user_${suffix}`, 'Mobile Logout User', 'TestPass123!'); + await expectDashboardReady(page); + + await openSettingsModal(page); + await page.getByTestId('settings-logout-button').click(); + + await expect(page).toHaveURL(/\/login/, { timeout: 15_000 }); + await expect(page.locator('#login-username')).toBeVisible({ timeout: 10_000 }); + }); +}); diff --git a/toju-app/public/i18n/catalog/settings.json b/toju-app/public/i18n/catalog/settings.json index 3f36167..a42e812 100644 --- a/toju-app/public/i18n/catalog/settings.json +++ b/toju-app/public/i18n/catalog/settings.json @@ -262,12 +262,14 @@ "localData": { "title": "Local data", "description": "Manage the folder that contains local messages, rooms, attachments, avatars, saved themes, and desktop storage.", + "descriptionMobile": "Review and erase the private app storage that holds local messages, rooms, attachments, and saved settings on this device.", "restartApp": "Restart app" }, - "desktopOnly": "Data management is only available in the packaged Electron desktop app.", + "desktopOnly": "Data management is only available in the desktop app or native mobile app.", "currentFolder": { "title": "Current data folder", - "resolving": "Resolving data folder..." + "resolving": "Resolving data folder...", + "descriptionMobile": "Files are stored in the app's private data directory on this device." }, "openFolder": "Open folder", "opening": "Opening...", @@ -287,6 +289,7 @@ "erase": { "title": "Erase user data", "description": "Remove local app data from this device and recreate an empty database.", + "descriptionMobile": "Remove local messages, rooms, attachments, and saved app data from this device.", "button": "Erase user data", "erasing": "Erasing...", "confirm": "Erase all local Toju data on this device? This cannot be undone." @@ -301,6 +304,7 @@ "importedWithBackup": "Imported data. Previous data was backed up to {{path}}.", "imported": "Imported data.", "erased": "Local data erased. Restart the app to finish resetting the session.", + "erasedMobile": "Local data erased. You have been signed out.", "operationFailed": "Data operation failed." } }, diff --git a/toju-app/public/i18n/en.json b/toju-app/public/i18n/en.json index 6853e03..b5235bd 100644 --- a/toju-app/public/i18n/en.json +++ b/toju-app/public/i18n/en.json @@ -1317,12 +1317,14 @@ "localData": { "title": "Local data", "description": "Manage the folder that contains local messages, rooms, attachments, avatars, saved themes, and desktop storage.", + "descriptionMobile": "Review and erase the private app storage that holds local messages, rooms, attachments, and saved settings on this device.", "restartApp": "Restart app" }, - "desktopOnly": "Data management is only available in the packaged Electron desktop app.", + "desktopOnly": "Data management is only available in the desktop app or native mobile app.", "currentFolder": { "title": "Current data folder", - "resolving": "Resolving data folder..." + "resolving": "Resolving data folder...", + "descriptionMobile": "Files are stored in the app's private data directory on this device." }, "openFolder": "Open folder", "opening": "Opening...", @@ -1342,6 +1344,7 @@ "erase": { "title": "Erase user data", "description": "Remove local app data from this device and recreate an empty database.", + "descriptionMobile": "Remove local messages, rooms, attachments, and saved app data from this device.", "button": "Erase user data", "erasing": "Erasing...", "confirm": "Erase all local Toju data on this device? This cannot be undone." @@ -1356,6 +1359,7 @@ "importedWithBackup": "Imported data. Previous data was backed up to {{path}}.", "imported": "Imported data.", "erased": "Local data erased. Restart the app to finish resetting the session.", + "erasedMobile": "Local data erased. You have been signed out.", "operationFailed": "Data operation failed." } }, diff --git a/toju-app/src/app/domains/authentication/application/services/auth-token-store.service.spec.ts b/toju-app/src/app/domains/authentication/application/services/auth-token-store.service.spec.ts index 083de2a..e9cc6d9 100644 --- a/toju-app/src/app/domains/authentication/application/services/auth-token-store.service.spec.ts +++ b/toju-app/src/app/domains/authentication/application/services/auth-token-store.service.spec.ts @@ -43,4 +43,13 @@ describe('AuthTokenStoreService', () => { expect(service.getToken('http://localhost:3001')).toBeNull(); }); + + it('clears every stored token', () => { + service.setToken('http://localhost:3001', 'token-abc', Date.now() + 60_000); + service.setToken('http://localhost:3002', 'token-def', Date.now() + 60_000); + + service.clearAllTokens(); + + expect(service.hasAnyValidToken()).toBe(false); + }); }); diff --git a/toju-app/src/app/domains/authentication/application/services/auth-token-store.service.ts b/toju-app/src/app/domains/authentication/application/services/auth-token-store.service.ts index c8ead5d..a41cc73 100644 --- a/toju-app/src/app/domains/authentication/application/services/auth-token-store.service.ts +++ b/toju-app/src/app/domains/authentication/application/services/auth-token-store.service.ts @@ -49,6 +49,12 @@ export class AuthTokenStoreService { this.writeStore(nextStore); } + clearAllTokens(): void { + try { + localStorage.removeItem(STORAGE_KEY); + } catch {} + } + hasAnyValidToken(): boolean { const now = Date.now(); diff --git a/toju-app/src/app/domains/authentication/application/services/user-logout.service.spec.ts b/toju-app/src/app/domains/authentication/application/services/user-logout.service.spec.ts new file mode 100644 index 0000000..d8d6376 --- /dev/null +++ b/toju-app/src/app/domains/authentication/application/services/user-logout.service.spec.ts @@ -0,0 +1,66 @@ +import '@angular/compiler'; +import { Injector, runInInjectionContext } from '@angular/core'; +import { + beforeEach, + describe, + expect, + it, + vi +} from 'vitest'; +import { Router } from '@angular/router'; +import { Store } from '@ngrx/store'; + +import { RealtimeSessionFacade } from '../../../../core/realtime'; +import { MessagesActions } from '../../../../store/messages/messages.actions'; +import { RoomsActions } from '../../../../store/rooms/rooms.actions'; +import { UsersActions } from '../../../../store/users/users.actions'; +import { UserLogoutService } from './user-logout.service'; + +describe('UserLogoutService', () => { + let webrtc: { disconnect: ReturnType }; + let store: { dispatch: ReturnType }; + let router: { navigate: ReturnType }; + let service: UserLogoutService; + + beforeEach(() => { + vi.stubGlobal('localStorage', { + getItem: vi.fn(() => null), + setItem: vi.fn(), + removeItem: vi.fn(), + clear: vi.fn(), + key: vi.fn(() => null), + length: 0 + }); + + webrtc = { disconnect: vi.fn() }; + store = { dispatch: vi.fn() }; + router = { navigate: vi.fn(() => Promise.resolve(true)) }; + + const injector = Injector.create({ + providers: [ + UserLogoutService, + { provide: RealtimeSessionFacade, useValue: webrtc }, + { provide: Store, useValue: store }, + { provide: Router, useValue: router } + ] + }); + + service = runInInjectionContext(injector, () => injector.get(UserLogoutService)); + }); + + it('disconnects, clears persisted user scope, resets store slices, and navigates to login', () => { + service.logout(); + + expect(webrtc.disconnect).toHaveBeenCalledOnce(); + expect(store.dispatch).toHaveBeenCalledWith(MessagesActions.clearMessages()); + expect(store.dispatch).toHaveBeenCalledWith(RoomsActions.resetRoomsState()); + expect(store.dispatch).toHaveBeenCalledWith(UsersActions.resetUsersState()); + expect(router.navigate).toHaveBeenCalledWith(['/login']); + }); + + it('can reset client state without navigating away', () => { + service.logout({ navigate: false }); + + expect(router.navigate).not.toHaveBeenCalled(); + }); +}); diff --git a/toju-app/src/app/domains/authentication/application/services/user-logout.service.ts b/toju-app/src/app/domains/authentication/application/services/user-logout.service.ts new file mode 100644 index 0000000..9731a2e --- /dev/null +++ b/toju-app/src/app/domains/authentication/application/services/user-logout.service.ts @@ -0,0 +1,28 @@ +import { Injectable, inject } from '@angular/core'; +import { Router } from '@angular/router'; +import { Store } from '@ngrx/store'; + +import { RealtimeSessionFacade } from '../../../../core/realtime'; +import { clearStoredCurrentUserId } from '../../../../core/storage/current-user-storage'; +import { MessagesActions } from '../../../../store/messages/messages.actions'; +import { RoomsActions } from '../../../../store/rooms/rooms.actions'; +import { UsersActions } from '../../../../store/users/users.actions'; + +@Injectable({ providedIn: 'root' }) +export class UserLogoutService { + private readonly webrtc = inject(RealtimeSessionFacade); + private readonly store = inject(Store); + private readonly router = inject(Router); + + logout(options?: { navigate?: boolean }): void { + this.webrtc.disconnect(); + clearStoredCurrentUserId(); + this.store.dispatch(MessagesActions.clearMessages()); + this.store.dispatch(RoomsActions.resetRoomsState()); + this.store.dispatch(UsersActions.resetUsersState()); + + if (options?.navigate !== false) { + void this.router.navigate(['/login']); + } + } +} diff --git a/toju-app/src/app/domains/authentication/index.ts b/toju-app/src/app/domains/authentication/index.ts index cc262d1..9214a75 100644 --- a/toju-app/src/app/domains/authentication/index.ts +++ b/toju-app/src/app/domains/authentication/index.ts @@ -1,5 +1,6 @@ export * from './application/services/authentication.service'; export * from './application/services/auth-token-store.service'; +export * from './application/services/user-logout.service'; export * from './application/services/signal-server-auth.service'; export * from './application/services/signal-server-authorize.service'; export * from './application/services/signal-server-credential-store.service'; diff --git a/toju-app/src/app/features/settings/domain/logic/data-settings-capability.rules.spec.ts b/toju-app/src/app/features/settings/domain/logic/data-settings-capability.rules.spec.ts new file mode 100644 index 0000000..384489f --- /dev/null +++ b/toju-app/src/app/features/settings/domain/logic/data-settings-capability.rules.spec.ts @@ -0,0 +1,29 @@ +import { + describe, + expect, + it +} from 'vitest'; + +import { + supportsDesktopDataFolderActions, + supportsLocalDataManagement, + supportsMobileLocalDataErase +} from './data-settings-capability.rules'; + +describe('data settings capability rules', () => { + it('enables local data management on Electron and Capacitor only', () => { + expect(supportsLocalDataManagement({ isElectron: true, isCapacitor: false })).toBe(true); + expect(supportsLocalDataManagement({ isElectron: false, isCapacitor: true })).toBe(true); + expect(supportsLocalDataManagement({ isElectron: false, isCapacitor: false })).toBe(false); + }); + + it('limits folder, export, and import actions to Electron', () => { + expect(supportsDesktopDataFolderActions({ isElectron: true, isCapacitor: false })).toBe(true); + expect(supportsDesktopDataFolderActions({ isElectron: false, isCapacitor: true })).toBe(false); + }); + + it('allows erase on Capacitor shells', () => { + expect(supportsMobileLocalDataErase({ isElectron: false, isCapacitor: true })).toBe(true); + expect(supportsMobileLocalDataErase({ isElectron: true, isCapacitor: false })).toBe(false); + }); +}); diff --git a/toju-app/src/app/features/settings/domain/logic/data-settings-capability.rules.ts b/toju-app/src/app/features/settings/domain/logic/data-settings-capability.rules.ts new file mode 100644 index 0000000..71cdf93 --- /dev/null +++ b/toju-app/src/app/features/settings/domain/logic/data-settings-capability.rules.ts @@ -0,0 +1,16 @@ +export interface DataSettingsPlatform { + isElectron: boolean; + isCapacitor: boolean; +} + +export function supportsLocalDataManagement(platform: DataSettingsPlatform): boolean { + return platform.isElectron || platform.isCapacitor; +} + +export function supportsDesktopDataFolderActions(platform: DataSettingsPlatform): boolean { + return platform.isElectron; +} + +export function supportsMobileLocalDataErase(platform: DataSettingsPlatform): boolean { + return platform.isCapacitor; +} diff --git a/toju-app/src/app/features/settings/settings-modal/data-settings/data-settings.component.html b/toju-app/src/app/features/settings/settings-modal/data-settings/data-settings.component.html index 52b0ded..1d63dd1 100644 --- a/toju-app/src/app/features/settings/settings-modal/data-settings/data-settings.component.html +++ b/toju-app/src/app/features/settings/settings-modal/data-settings/data-settings.component.html @@ -10,7 +10,11 @@

{{ 'settings.data.localData.title' | translate }}

- {{ 'settings.data.localData.description' | translate }} + @if (supportsMobileLocalDataErase) { + {{ 'settings.data.localData.descriptionMobile' | translate }} + } @else { + {{ 'settings.data.localData.description' | translate }} + }

@@ -25,17 +29,17 @@ name="lucideRefreshCw" class="h-4 w-4" /> - Restart app + {{ 'settings.data.localData.restartApp' | translate }} } - @if (!isElectron) { + @if (!supportsLocalDataManagement) {

{{ 'settings.data.desktopOnly' | translate }}

- } @else { + } @else if (supportsDesktopDataFolderActions) {
{{ 'settings.data.currentFolder.title' | translate }}
@@ -108,6 +112,7 @@
+ } @else if (supportsMobileLocalDataErase) { +
+
+
{{ 'settings.data.currentFolder.title' | translate }}
+

+ {{ dataPath() || ('settings.data.currentFolder.resolving' | translate) }} +

+

{{ 'settings.data.currentFolder.descriptionMobile' | translate }}

+
+
- @if (statusMessage()) { -
-

{{ statusMessage() }}

-
- } +
+
+
{{ 'settings.data.erase.title' | translate }}
+

{{ 'settings.data.erase.descriptionMobile' | translate }}

+
- @if (errorMessage()) { -
-

{{ errorMessage() }}

-
- } + +
+ } + + @if (statusMessage()) { +
+

{{ statusMessage() }}

+
+ } + + @if (errorMessage()) { +
+

{{ errorMessage() }}

+
} diff --git a/toju-app/src/app/features/settings/settings-modal/data-settings/data-settings.component.ts b/toju-app/src/app/features/settings/settings-modal/data-settings/data-settings.component.ts index 910dc50..a34e34c 100644 --- a/toju-app/src/app/features/settings/settings-modal/data-settings/data-settings.component.ts +++ b/toju-app/src/app/features/settings/settings-modal/data-settings/data-settings.component.ts @@ -15,8 +15,16 @@ import { lucideUpload } from '@ng-icons/lucide'; +import { PlatformService } from '../../../../core/platform'; import { ElectronBridgeService } from '../../../../core/platform/electron/electron-bridge.service'; +import { CapacitorAttachmentFileStore } from '../../../../domains/attachment/infrastructure/services/capacitor-attachment-file-store'; +import { LocalUserDataService } from '../../../../infrastructure/persistence/local-user-data.service'; import { APP_TRANSLATE_IMPORTS, AppI18nService } from '../../../../core/i18n'; +import { + supportsDesktopDataFolderActions, + supportsLocalDataManagement, + supportsMobileLocalDataErase +} from '../../domain/logic/data-settings-capability.rules'; type DataAction = 'open' | 'export' | 'import' | 'erase' | 'restart'; @@ -42,9 +50,25 @@ type DataAction = 'open' | 'export' | 'import' | 'erase' | 'restart'; }) export class DataSettingsComponent { private readonly electron = inject(ElectronBridgeService); + private readonly platform = inject(PlatformService); + private readonly localUserData = inject(LocalUserDataService); + private readonly capacitorAttachmentStore = inject(CapacitorAttachmentFileStore); private readonly appI18n = inject(AppI18nService); readonly isElectron = this.electron.isAvailable; + readonly isCapacitor = this.platform.isCapacitor; + readonly supportsLocalDataManagement = supportsLocalDataManagement({ + isElectron: this.isElectron, + isCapacitor: this.isCapacitor + }); + readonly supportsDesktopDataFolderActions = supportsDesktopDataFolderActions({ + isElectron: this.isElectron, + isCapacitor: this.isCapacitor + }); + readonly supportsMobileLocalDataErase = supportsMobileLocalDataErase({ + isElectron: this.isElectron, + isCapacitor: this.isCapacitor + }); readonly dataPath = signal(null); readonly busyAction = signal(null); readonly statusMessage = signal(null); @@ -106,6 +130,14 @@ export class DataSettingsComponent { } await this.runAction('erase', async () => { + if (this.supportsMobileLocalDataErase) { + const result = await this.localUserData.eraseLocalUserData(); + + this.restartRequired.set(result.restartRequired); + this.statusMessage.set(this.appI18n.instant('settings.data.messages.erasedMobile')); + return; + } + const result = await this.electron.requireApi().eraseUserData(); this.restartRequired.set(result.restartRequired); @@ -121,14 +153,18 @@ export class DataSettingsComponent { } private async loadDataPath(): Promise { - if (!this.isElectron) { + if (this.supportsDesktopDataFolderActions) { + try { + this.dataPath.set(await this.electron.requireApi().getAppDataPath()); + } catch { + this.dataPath.set(null); + } + return; } - try { - this.dataPath.set(await this.electron.requireApi().getAppDataPath()); - } catch { - this.dataPath.set(null); + if (this.supportsMobileLocalDataErase) { + this.dataPath.set(await this.capacitorAttachmentStore.getAppDataPath()); } } diff --git a/toju-app/src/app/features/settings/settings-modal/settings-modal.component.html b/toju-app/src/app/features/settings/settings-modal/settings-modal.component.html index d3e0fe1..ebafba3 100644 --- a/toju-app/src/app/features/settings/settings-modal/settings-modal.component.html +++ b/toju-app/src/app/features/settings/settings-modal/settings-modal.component.html @@ -129,7 +129,21 @@ } -
+
+ @if (currentUser()) { + + }