diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9d3c1f5a5..c09487ad5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -405,3 +405,50 @@ jobs: steps.changes.outputs.windows != 'true' shell: pwsh run: '"No Windows desktop or build-system changes; native Windows validation was skipped." >> $env:GITHUB_STEP_SUMMARY' + + macos-sqlite-runtime: + name: Intel macOS SQLite runtime + runs-on: macos-15-intel + timeout-minutes: 45 + steps: + - name: Check out source + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 + + - name: Detect SQLite packaging changes + id: changes + continue-on-error: true + uses: dorny/paths-filter@7b450fff21473bca461d4b92ce414b9d0420d706 # v4.0.2 + with: + filters: | + sqlite: + - ".github/workflows/ci.yml" + - "gradle/**" + - "gradle.properties" + - "build.gradle.kts" + - "settings.gradle.kts" + - "ui/build.gradle.kts" + - "ui/src/desktopMain/kotlin/dev/obiente/nextcloudnative/app/DesktopSqliteRuntime.kt" + - "ui/src/desktopMain/kotlin/dev/obiente/nextcloudnative/nativeui/preview/Main.kt" + - "ui/src/desktopTest/kotlin/dev/obiente/nextcloudnative/app/DesktopSqliteRuntimeTest.kt" + + - name: Set up JDK 21 + if: >- + github.event_name == 'workflow_dispatch' || + steps.changes.outcome != 'success' || + steps.changes.outputs.sqlite == 'true' + uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5 + with: + distribution: temurin + java-version: "21" + cache: gradle + + - name: Verify Intel macOS SQLite in tests and the packaged app + if: >- + github.event_name == 'workflow_dispatch' || + steps.changes.outcome != 'success' || + steps.changes.outputs.sqlite == 'true' + run: | + ./gradlew --no-daemon :ui:desktopTest --tests '*DesktopSqliteRuntimeTest' :ui:createDistributable + launcher=$(find ui/build/compose/binaries/main/app -type f -path '*/Contents/MacOS/nati.ve' -print -quit) + test -n "$launcher" + "$launcher" --verify-sqlite-runtime diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 1838204cb..36c4605ac 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -307,6 +307,14 @@ jobs: fi exit "${status}" + - name: Verify packaged macOS SQLite runtime + if: matrix.platform == 'macos' + shell: bash + run: | + launcher=$(find ui/build/compose/binaries/main/app -type f -path '*/Contents/MacOS/nati.ve' -print -quit) + test -n "$launcher" + "$launcher" --verify-sqlite-runtime + - name: Verify unsigned Windows MSI if: matrix.platform == 'windows' shell: pwsh diff --git a/.github/workflows/prerelease.yml b/.github/workflows/prerelease.yml index 3fbd453ee..ee2aacd32 100644 --- a/.github/workflows/prerelease.yml +++ b/.github/workflows/prerelease.yml @@ -203,6 +203,14 @@ jobs: fi exit "${status}" + - name: Verify packaged macOS SQLite runtime + if: matrix.platform == 'macos' + shell: bash + run: | + launcher=$(find ui/build/compose/binaries/main/app -type f -path '*/Contents/MacOS/nati.ve' -print -quit) + test -n "$launcher" + "$launcher" --verify-sqlite-runtime + - name: Verify unsigned Windows MSI if: matrix.platform == 'windows' shell: pwsh diff --git a/PLATFORMS.md b/PLATFORMS.md index a38b9fa87..a37fb0d05 100644 --- a/PLATFORMS.md +++ b/PLATFORMS.md @@ -218,3 +218,14 @@ bundles; the DMG does not migrate an existing bundle automatically. These names describe source packaging configuration, not confirmation that a release containing it has been published. Check the [release artifacts](https://github.com/Obiente/native/releases) for availability. + +## Desktop SQLite runtime verification + +The JVM SQLite dependency is constrained to 2.6.2 because the 2.7.0 and 2.7.1 +artifacts omit the Intel macOS native library. `DesktopSqliteRuntimeTest` checks +the native resources for each packaged desktop architecture and opens an +in-memory database on the test host. An Intel macOS CI job validates relevant +dependency and packaging changes. Nightly and prerelease macOS packages run their +launcher with `--verify-sqlite-runtime`, before creating application services. +This check does not inspect accounts or modify user databases. A resource check +on one OS does not establish runtime validation on another OS. diff --git a/changes/unreleased/459-intel-macos-sqlite.md b/changes/unreleased/459-intel-macos-sqlite.md new file mode 100644 index 000000000..b4543f218 --- /dev/null +++ b/changes/unreleased/459-intel-macos-sqlite.md @@ -0,0 +1,7 @@ +category: fix +issue: 459 +pull: 469 +platforms: desktop +user-facing: yes + +Restore the bundled SQLite library for Intel Macs so folder-sync and media-backup databases can open, and verify native runtime resources before packaging. diff --git a/changes/unreleased/465-media-workspace-state.md b/changes/unreleased/465-media-workspace-state.md new file mode 100644 index 000000000..6aab910b9 --- /dev/null +++ b/changes/unreleased/465-media-workspace-state.md @@ -0,0 +1,7 @@ +category: fix +issue: 465 +pull: 468 +platforms: android, desktop +user-facing: yes + +Keep navigation chrome independent from app state and restore workspace settings when entering or leaving full-screen media, preventing image navigation crashes. Do not retain detail or editor state for restoration into another record. diff --git a/changes/unreleased/media-route-account-ownership.md b/changes/unreleased/media-route-account-ownership.md new file mode 100644 index 000000000..637b53e3b --- /dev/null +++ b/changes/unreleased/media-route-account-ownership.md @@ -0,0 +1,7 @@ +category: security +issue: 172 +pull: none +platforms: android, desktop +user-facing: yes + +Media viewer routes are scoped to their owning account and cleared when that account is removed, preventing retained media from reopening through another account or after removal. diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 92d9c336b..15bfe5733 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -18,7 +18,8 @@ media3 = "1.10.1" libvlc = "3.7.5" javafx = "21.0.12" jse = "1.1.0" -sqlite = "2.7.0" +# SQLite 2.7 JVM artifacts omit the Intel macOS native library. +sqlite = { strictly = "2.6.2" } [libraries] kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "serialization" } diff --git a/tools/kotlin-file-size-baseline.txt b/tools/kotlin-file-size-baseline.txt index da1e8167d..f7e9b347e 100644 --- a/tools/kotlin-file-size-baseline.txt +++ b/tools/kotlin-file-size-baseline.txt @@ -25,7 +25,7 @@ ui/src/commonMain/kotlin/dev/obiente/nextcloudnative/app/NativeDeckBoardSurface. ui/src/commonMain/kotlin/dev/obiente/nextcloudnative/app/NativeDeckRelationDialogs.kt|1234 ui/src/commonMain/kotlin/dev/obiente/nextcloudnative/app/NativeDeckScreen.kt|1883 ui/src/commonMain/kotlin/dev/obiente/nextcloudnative/app/NextcloudMediaViewer.kt|1331 -ui/src/commonMain/kotlin/dev/obiente/nextcloudnative/app/NextcloudNativeApp.kt|12324 +ui/src/commonMain/kotlin/dev/obiente/nextcloudnative/app/NextcloudNativeApp.kt|12313 ui/src/commonMain/kotlin/dev/obiente/nextcloudnative/app/NextcloudNotes.kt|1693 ui/src/commonMain/kotlin/dev/obiente/nextcloudnative/app/NextcloudPhotoEditor.kt|808 ui/src/commonMain/kotlin/dev/obiente/nextcloudnative/app/NextcloudPlatform.kt|1717 diff --git a/tools/test-marketing-capture-workflow.sh b/tools/test-marketing-capture-workflow.sh index 4764eb827..0cc95bf4b 100755 --- a/tools/test-marketing-capture-workflow.sh +++ b/tools/test-marketing-capture-workflow.sh @@ -67,10 +67,10 @@ for stale_gate in \ fi done -scope_detector_count="$(grep -Ec '^ - name: Detect (build scopes|Windows desktop changes)$' "$ci")" +scope_detector_count="$(grep -Ec '^ - name: Detect (build scopes|Windows desktop changes|SQLite packaging changes)$' "$ci")" continued_detector_count="$(grep -Fc 'continue-on-error: true' "$ci")" -if [[ "$scope_detector_count" -ne 2 || "$continued_detector_count" -ne 2 ]]; then - printf 'Build scope detection must degrade safely for both build jobs.\n' >&2 +if [[ "$scope_detector_count" -ne 3 || "$continued_detector_count" -ne 3 ]]; then + printf 'Build scope detection must degrade safely for all three build jobs.\n' >&2 exit 1 fi require_text "$ci" "steps.changes.outcome != 'success'" diff --git a/ui/src/commonMain/kotlin/dev/obiente/nextcloudnative/app/AppWorkspaceState.kt b/ui/src/commonMain/kotlin/dev/obiente/nextcloudnative/app/AppWorkspaceState.kt new file mode 100644 index 000000000..25bb08717 --- /dev/null +++ b/ui/src/commonMain/kotlin/dev/obiente/nextcloudnative/app/AppWorkspaceState.kt @@ -0,0 +1,54 @@ +package dev.obiente.nextcloudnative.app + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.movableContentOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.saveable.SaveableStateHolder + +/** Moves only workspace content; application chrome keeps its independent state. */ +@Composable +internal fun rememberAppWorkspaceContent( + holder: SaveableStateHolder, + appId: String?, + screen: Screen, + content: @Composable () -> Unit, +): @Composable () -> Unit { + val routeKind = screen.workspaceStateKind() + // Retain workspace roots only. Detail/editor bundles must not be restored into another record. + val stateKey = appId?.takeIf { screen.usesPersistentAppNavigation() }?.let { "app:$it:$routeKind" } + val movable = remember(holder, appId, routeKind) { + movableContentOf<@Composable () -> Unit> { render -> + if (stateKey == null) render() else holder.SaveableStateProvider(stateKey) { render() } + } + } + return { movable(content) } +} + +/** Keys contain route kinds, never private paths, selected records, or editor content. */ +private fun Screen.workspaceStateKind(): String = when (this) { + Screen.Root -> "root" + Screen.Search -> "search" + is Screen.Files -> "files" + Screen.Media -> "media" + is Screen.PersonMedia -> "person-media" + Screen.Talk -> "talk" + Screen.Notes -> "notes" + Screen.Dashboard -> "dashboard" + Screen.UserStatus -> "user-status" + Screen.Calendar -> "calendar" + Screen.Contacts -> "contacts" + Screen.Tasks -> "tasks" + Screen.Deck -> "deck" + Screen.AdminApps -> "admin-apps" + Screen.OfflineCenter -> "offline-center" + Screen.Transfers -> "transfers" + Screen.ProjectNews -> "project-news" + is Screen.ProjectNewsArticleView -> "project-news-article" + is Screen.Chat -> "chat" + is Screen.NoteEditor -> "note-editor" + is Screen.AppInfo -> "app-info" + is Screen.MediaViewer -> "media-viewer" + is Screen.FileInfo -> "file-info" + is Screen.DocumentPreview -> "document-preview" + is Screen.TextEditor -> "text-editor" +} diff --git a/ui/src/commonMain/kotlin/dev/obiente/nextcloudnative/app/NextcloudNativeApp.kt b/ui/src/commonMain/kotlin/dev/obiente/nextcloudnative/app/NextcloudNativeApp.kt index 818594bc4..3f4041248 100644 --- a/ui/src/commonMain/kotlin/dev/obiente/nextcloudnative/app/NextcloudNativeApp.kt +++ b/ui/src/commonMain/kotlin/dev/obiente/nextcloudnative/app/NextcloudNativeApp.kt @@ -2573,6 +2573,11 @@ private fun AuthenticatedApp( }, ) } + val workspaceContent = rememberAppWorkspaceContent( + holder = appWorkspaceSaveableStateHolder, + appId = appWorkspaceNavigation.activeAppId?.takeIf { screen != Screen.Root }, + screen = screen, content = screenContent, + ) Box(modifier = Modifier.weight(1f).fillMaxWidth()) { if (shouldUseNextcloudRootShell(presentation, screen.usesPersistentAppNavigation())) { RootShell( @@ -2602,26 +2607,10 @@ private fun AuthenticatedApp( } } }, - content = { - val appId = appWorkspaceNavigation.activeAppId - if (appId != null && screen != Screen.Root) { - appWorkspaceSaveableStateHolder.SaveableStateProvider("app:$appId") { - screenContent() - } - } else { - screenContent() - } - }, + content = workspaceContent, ) } else { - val appId = appWorkspaceNavigation.activeAppId - if (appId != null && screen != Screen.Root) { - appWorkspaceSaveableStateHolder.SaveableStateProvider("app:$appId") { - screenContent() - } - } else { - screenContent() - } + workspaceContent() } } } diff --git a/ui/src/desktopMain/kotlin/dev/obiente/nextcloudnative/app/DesktopSqliteRuntime.kt b/ui/src/desktopMain/kotlin/dev/obiente/nextcloudnative/app/DesktopSqliteRuntime.kt new file mode 100644 index 000000000..e7f014a92 --- /dev/null +++ b/ui/src/desktopMain/kotlin/dev/obiente/nextcloudnative/app/DesktopSqliteRuntime.kt @@ -0,0 +1,13 @@ +package dev.obiente.nextcloudnative.app + +import androidx.sqlite.driver.bundled.BundledSQLiteDriver + +/** Runs before application services and only uses an in-memory database. */ +internal fun verifyDesktopSqliteRuntime() { + BundledSQLiteDriver().open(":memory:").use { connection -> + connection.prepare("SELECT sqlite_version()").use { statement -> + check(statement.step()) { "SQLite runtime verification returned no version." } + check(statement.getText(0).isNotBlank()) { "SQLite runtime verification returned an empty version." } + } + } +} diff --git a/ui/src/desktopMain/kotlin/dev/obiente/nextcloudnative/nativeui/preview/Main.kt b/ui/src/desktopMain/kotlin/dev/obiente/nextcloudnative/nativeui/preview/Main.kt index 27993892b..692661658 100644 --- a/ui/src/desktopMain/kotlin/dev/obiente/nextcloudnative/nativeui/preview/Main.kt +++ b/ui/src/desktopMain/kotlin/dev/obiente/nextcloudnative/nativeui/preview/Main.kt @@ -58,7 +58,13 @@ import javax.swing.SwingUtilities import kotlinx.coroutines.flow.collect import kotlinx.coroutines.launch -fun main(arguments: Array) = runDesktopEntryPoint(arguments) +fun main(arguments: Array) { + if (arguments.contentEquals(arrayOf("--verify-sqlite-runtime"))) { + dev.obiente.nextcloudnative.app.verifyDesktopSqliteRuntime() + return + } + runDesktopEntryPoint(arguments) +} @OptIn(ExperimentalComposeUiApi::class) internal fun runDesktopEntryPoint( diff --git a/ui/src/desktopTest/kotlin/dev/obiente/nextcloudnative/app/AppWorkspaceStateTest.kt b/ui/src/desktopTest/kotlin/dev/obiente/nextcloudnative/app/AppWorkspaceStateTest.kt new file mode 100644 index 000000000..e0f7d4f46 --- /dev/null +++ b/ui/src/desktopTest/kotlin/dev/obiente/nextcloudnative/app/AppWorkspaceStateTest.kt @@ -0,0 +1,97 @@ +package dev.obiente.nextcloudnative.app + +import androidx.compose.foundation.layout.Column +import androidx.compose.material3.Button +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.semantics.SemanticsProperties +import androidx.compose.ui.semantics.getOrNull +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.saveable.rememberSaveableStateHolder +import androidx.compose.runtime.setValue +import dev.obiente.nextcloudnative.app.design.NextcloudDesktopWorkspaceKind +import dev.obiente.nextcloudnative.app.design.NextcloudDestination +import dev.obiente.nextcloudnative.app.design.NextcloudPresentation +import kotlin.test.Test +import kotlin.test.assertTrue + +class AppWorkspaceStateTest { + @Test fun desktopAppSwitchingPreservesGlobalSidebarAndPerAppContent() { + val app = mutableStateOf("files") + nativeSceneTest(1440, 900, content = { + val workspace = rememberAppWorkspaceContent(rememberSaveableStateHolder(), app.value, Screen.Files("/")) { + SavedCounter("Files") + } + RootShell( + presentation = NextcloudPresentation.Desktop, + selected = NextcloudDestination.Apps, + desktopWorkspaceKind = NextcloudDesktopWorkspaceKind.AppWorkspace, + onSelected = {}, identity = null, activeAppId = app.value, content = workspace, + ) + }) { + click("Collapse sidebar") + click("Change Files state") + assertTrue(has("Files state: 1")) + app.value = "photos" + settle() + assertTrue(has("Expand sidebar"), "The sidebar must remain collapsed after switching apps") + assertTrue(has("Files state: 0"), "A different app must start with independent content state") + app.value = "files" + settle() + assertTrue(has("Expand sidebar")) + assertTrue(has("Files state: 1")) + } + } + + @Test fun adaptiveReparentingAndImmersiveNavigationRestoreTheScreenSubtree() { + val showShell = mutableStateOf(true) + val route = mutableStateOf(Screen.Files("/")) + nativeSceneTest(390, 800, content = { + val screen = route.value + val workspace = rememberAppWorkspaceContent(rememberSaveableStateHolder(), "files", screen) { + SavedCounter( + if (screen is Screen.MediaViewer) "Viewer" else "Files", + if (screen is Screen.MediaViewer) screen.navigationKey else "workspace", + ) + } + if (showShell.value) { + RootShell( + presentation = NextcloudPresentation.Adaptive, + selected = NextcloudDestination.Apps, + onSelected = {}, identity = null, activeAppId = "files", content = workspace, + ) + } else workspace() + }) { + click("Change Files state") + repeat(8) { + showShell.value = !showShell.value + settle() + assertTrue(has("Files state: 1"), "Layout changes must retain the moving screen subtree") + } + route.value = Screen.MediaViewer("synthetic-media", 0, 0, Screen.Files("/")) + showShell.value = false + settle() + assertTrue(has("Viewer state: 0"), "Expected isolated viewer state; rendered text: ${nodes().mapNotNull { it.config.getOrNull(SemanticsProperties.Text) }}") + click("Change Viewer state") + route.value = Screen.Files("/") + showShell.value = true + settle() + assertTrue(has("Files state: 1"), "Returning from the viewer must restore the Files screen state") + route.value = Screen.MediaViewer("another-synthetic-media", 0, 0, Screen.Files("/")) + showShell.value = false + settle() + assertTrue(has("Viewer state: 0"), "A different record must not restore the previous detail state") + } + } + + @Composable + private fun SavedCounter(label: String, recordKey: String = "workspace") { + var value by rememberSaveable(recordKey) { mutableStateOf(0) } + Column { + Text("$label state: $value") + Button(onClick = { value += 1 }) { Text("Change $label state") } + } + } +} diff --git a/ui/src/desktopTest/kotlin/dev/obiente/nextcloudnative/app/DesktopSqliteRuntimeTest.kt b/ui/src/desktopTest/kotlin/dev/obiente/nextcloudnative/app/DesktopSqliteRuntimeTest.kt new file mode 100644 index 000000000..b82d05acd --- /dev/null +++ b/ui/src/desktopTest/kotlin/dev/obiente/nextcloudnative/app/DesktopSqliteRuntimeTest.kt @@ -0,0 +1,22 @@ +package dev.obiente.nextcloudnative.app + +import androidx.sqlite.driver.bundled.BundledSQLiteDriver +import kotlin.test.Test +import kotlin.test.assertNotNull + +class DesktopSqliteRuntimeTest { + @Test fun distributedDriverIncludesEverySupportedDesktopBinary() { + val loader = BundledSQLiteDriver::class.java.classLoader + listOf( + "linux_x64/libsqliteJni.so", + "linux_arm64/libsqliteJni.so", + "osx_x64/libsqliteJni.dylib", + "osx_arm64/libsqliteJni.dylib", + "windows_x64/sqliteJni.dll", + ).forEach { binary -> + assertNotNull(loader.getResource("natives/$binary"), "Missing SQLite runtime: $binary") + } + } + + @Test fun nativeDriverCanOpenAndQueryTheRuntime() = verifyDesktopSqliteRuntime() +} diff --git a/website/public/screenshots/capture-manifest.json b/website/public/screenshots/capture-manifest.json index a23f274ae..d4aa79b5f 100644 --- a/website/public/screenshots/capture-manifest.json +++ b/website/public/screenshots/capture-manifest.json @@ -29,6 +29,7 @@ "ui/src/commonMain/kotlin/dev/obiente/nextcloudnative/app/AppWorkspaceNavigationMemory.kt", "ui/src/commonMain/kotlin/dev/obiente/nextcloudnative/app/AppWorkspacePins.kt", "ui/src/commonMain/kotlin/dev/obiente/nextcloudnative/app/AppWorkspacePresentation.kt", + "ui/src/commonMain/kotlin/dev/obiente/nextcloudnative/app/AppWorkspaceState.kt", "ui/src/commonMain/kotlin/dev/obiente/nextcloudnative/app/AppsWorkspace.kt", "ui/src/commonMain/kotlin/dev/obiente/nextcloudnative/app/CalendarDateTimeFields.kt", "ui/src/commonMain/kotlin/dev/obiente/nextcloudnative/app/CalendarDialogSurface.kt", @@ -433,7 +434,7 @@ "captureSourceHashes": { "build.gradle.kts": "de2d98f7fce24b2bc67423bb06aa88d251e1458b1fa3c9c9700dbb14289edbf0", "gradle.properties": "99c3ee1a9d68a2548830e7dca0cc4b02f4acf408bfc394a0acd55e7865c53739", - "gradle/libs.versions.toml": "10ef19d481e525d0ad1e94c9a74abe0a4caca6572cf7d2d0dadabfba3974ab5a", + "gradle/libs.versions.toml": "69ed47ea88842a0097cc7dc5273016d8954e668763618976a9b781efe2915dda", "gradle/wrapper/gradle-wrapper.properties": "aef287d114ce3153c3d535697a61928f5034990d570cbb2e93df549e9671483d", "settings.gradle.kts": "0acbe4b907815189abfedb2256c8659558e5a7e6995a3681a2bdfb05e335fd1a", "tools/marketing-capture-inputs.txt": "3c96e83e1ba2d715b1cda9cedf036fc97b78c3ca63b7fc930325ed536940c1f3", @@ -455,6 +456,7 @@ "ui/src/commonMain/kotlin/dev/obiente/nextcloudnative/app/AppWorkspaceNavigationMemory.kt": "2a48ec4fda7ca47657253891e880a4169b16dbc83c197808532a92169961d569", "ui/src/commonMain/kotlin/dev/obiente/nextcloudnative/app/AppWorkspacePins.kt": "91842afe8be45598eca9f23da7012401c53c6571737d1a9b38e82332ce84123c", "ui/src/commonMain/kotlin/dev/obiente/nextcloudnative/app/AppWorkspacePresentation.kt": "e5655f80ca80cdb88ac49a54554e7d4cf684293efb2af4e291f0ef4b34529594", + "ui/src/commonMain/kotlin/dev/obiente/nextcloudnative/app/AppWorkspaceState.kt": "5489d5f40f8ec23b6d4575c376548a746d442262db64d51ef462b31eef268c75", "ui/src/commonMain/kotlin/dev/obiente/nextcloudnative/app/AppsWorkspace.kt": "87b5ee4b871c85d55a8f43f3cb07ee619672def16fade42f819d67c913ac5295", "ui/src/commonMain/kotlin/dev/obiente/nextcloudnative/app/CalendarDateTimeFields.kt": "dbd41b992d9ebf24f76e6e66b03c8de23cf76267eca831eee318e5a582231d81", "ui/src/commonMain/kotlin/dev/obiente/nextcloudnative/app/CalendarDialogSurface.kt": "c571d170ab27cbe0bf02ecc4ae81b641b89fa6410244a7ce79edc46cc49101e1", @@ -647,7 +649,7 @@ "ui/src/commonMain/kotlin/dev/obiente/nextcloudnative/app/NextcloudLinkRouting.kt": "5b29a90b69bb32aba118ef6c8b3f9d6eb26c03835823119b4a0f5bb1c1f4cb17", "ui/src/commonMain/kotlin/dev/obiente/nextcloudnative/app/NextcloudMediaViewer.kt": "fb424bf8979ac292ef30daba64e905e6f5123066cda793aa0244907e11b465c9", "ui/src/commonMain/kotlin/dev/obiente/nextcloudnative/app/NextcloudMediaViewerActions.kt": "48aaed6948d1423113d300cc3ab86d244ab76e8225e24988e9855edf74553944", - "ui/src/commonMain/kotlin/dev/obiente/nextcloudnative/app/NextcloudNativeApp.kt": "d73abfae9c2da3525c01a24c3477f0ee0cd36e230eea7da0f0ea9f821eada192", + "ui/src/commonMain/kotlin/dev/obiente/nextcloudnative/app/NextcloudNativeApp.kt": "8707f03ada4353bd0249acdbde2eeb50a76edd62d471703deeab924862fa082d", "ui/src/commonMain/kotlin/dev/obiente/nextcloudnative/app/NextcloudNotes.kt": "29b0b80824eeb2d154f52a9eacc10bd4d7068b7eca3bd9427aec67903bf6ee0d", "ui/src/commonMain/kotlin/dev/obiente/nextcloudnative/app/NextcloudNotesCache.kt": "aa9ec330c3a569e2bc9948858ddde1d939358069d38fbd7a0319b1ecc3baa363", "ui/src/commonMain/kotlin/dev/obiente/nextcloudnative/app/NextcloudPeople.kt": "cff910ea2cc77211ef81779c49ee0c957851f2b4a3ed32b857b12ded1cee643b", @@ -835,7 +837,7 @@ "ui/src/desktopMain/kotlin/dev/obiente/nextcloudnative/nativeui/preview/DynamicBoardInteractionPreviewMain.kt": "7402f5b4bf3bbf3cf20761eaa10eca9131fb6e63da2988d0aa6038942d706c53", "ui/src/desktopMain/kotlin/dev/obiente/nextcloudnative/nativeui/preview/FileSyncTrayVisualQaMain.kt": "4ce39ce3a53b72a32ca7489ef65cf301e9d8279f4be9ef9afc3ace701075c301", "ui/src/desktopMain/kotlin/dev/obiente/nextcloudnative/nativeui/preview/FileSyncWorkspaceVisualQaMain.kt": "af79af05554e41806121b49f7fe8e31a67127636f0faec78f44ac812726c86e0", - "ui/src/desktopMain/kotlin/dev/obiente/nextcloudnative/nativeui/preview/Main.kt": "37681a229096d46fd1eb06a8f969c22a2903e765b6b4eefb887be7b8dea461ad", + "ui/src/desktopMain/kotlin/dev/obiente/nextcloudnative/nativeui/preview/Main.kt": "b6f26394074b06186397d381dc7b20dc4690c36fd37865df5134377188520936", "ui/src/desktopMain/kotlin/dev/obiente/nextcloudnative/nativeui/preview/MarketingCaptureMain.kt": "bd7f6b8870c0030f061c5e466849b597f3388df6e9b4c5a6987000bb0dc0fbdf", "ui/src/desktopMain/kotlin/dev/obiente/nextcloudnative/nativeui/preview/MarketingCaptureOwnership.kt": "c5bd9b63793a8884cf6b74cadf3061dc0ac295a982c97ae7f0a906ac3ae6f3c9", "ui/src/desktopMain/kotlin/dev/obiente/nextcloudnative/nativeui/preview/NativeTiffMarketingCapture.kt": "6970352c6e42d09b793c55ea296991d756d232f216d8d152aa00319b2ddb8d20",