From d0f448af70156090c07e7f13e64c940ee9a561dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaka=20Mo=C4=8Dnik?= Date: Wed, 22 Jul 2026 16:39:06 +0200 Subject: [PATCH 1/6] target android sdk 37 - move to agp 9.3.0 with gradle 9.5.0 - use mullvad rust android plugin fork to be able to track agp versions - adapt gradle build scripts accordingly - move to newer versions of android tooling in github workflow --- .github/workflows/build-vault-android.yml | 6 +- vault-android/.gitignore | 2 + vault-android/app/build.gradle.kts | 81 ++++++++----------- vault-android/gradle.properties | 15 +++- vault-android/gradle/libs.versions.toml | 10 +-- .../gradle/wrapper/gradle-wrapper.properties | 2 +- vault-mobile/uniffi.toml | 1 + 7 files changed, 58 insertions(+), 59 deletions(-) diff --git a/.github/workflows/build-vault-android.yml b/.github/workflows/build-vault-android.yml index ef33d1c4..c9f216cd 100644 --- a/.github/workflows/build-vault-android.yml +++ b/.github/workflows/build-vault-android.yml @@ -17,12 +17,12 @@ jobs: - name: Setup Android SDK uses: android-actions/setup-android@v3 with: - cmdline-tools-version: 10406996 + cmdline-tools-version: 15859902 accept-android-sdk-licenses: true log-accepted-android-sdk-licenses: false - name: Setup Android NDK run: | - sdkmanager "ndk;26.0.10792818" + sdkmanager "ndk;30.0.15729638" - uses: actions/cache@v3 with: path: ~/.cargo/registry @@ -44,7 +44,7 @@ jobs: run: | cd vault-android echo 'sdk.dir=/usr/local/lib/android/sdk' >> local.properties - echo 'android.ndkVersion=26.0.10792818' >> local.properties + echo 'android.ndkVersion=30.0.15729638' >> local.properties ./gradlew generateUniFFIBindings diff --git a/vault-android/.gitignore b/vault-android/.gitignore index ac7f93c5..719f5491 100644 --- a/vault-android/.gitignore +++ b/vault-android/.gitignore @@ -21,3 +21,5 @@ .cxx /.profile /keystores +/.kotlin + diff --git a/vault-android/app/build.gradle.kts b/vault-android/app/build.gradle.kts index aeef0323..96483cd0 100644 --- a/vault-android/app/build.gradle.kts +++ b/vault-android/app/build.gradle.kts @@ -8,20 +8,21 @@ plugins { alias(libs.plugins.hilt.android) alias(libs.plugins.compose.compiler) alias(libs.plugins.kotlinx.serialization) + alias(libs.plugins.rust.android) } val localProperties = gradleLocalProperties(rootDir, providers) android { namespace = "net.koofr.vault" - compileSdk = 35 + compileSdk = 37 defaultConfig { applicationId = "net.koofr.vault" minSdk = 24 - targetSdk = 35 - versionCode = 125001 - versionName = "0.1.25" + targetSdk = 37 + versionCode = 126001 + versionName = "0.1.26" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" vectorDrawables { @@ -102,14 +103,14 @@ android { } } ndkVersion = localProperties.getProperty("android.ndkVersion") - sourceSets { - getByName("debug") { - jniLibs.srcDir(layout.buildDirectory.dir("rustJniLibs/android")) - } - getByName("release") { - jniLibs.srcDir(layout.buildDirectory.dir("rustJniLibs/android")) - } - } +} + +val rustJniLibsDir = layout.buildDirectory.dir("rustJniLibs/android").get() +tasks.matching { it.name.matches(Regex("merge.*JniLibFolders")) }.configureEach { + inputs.dir(rustJniLibsDir) + // NOTE dependency wastes precious time each build, you could omit this and run cargo build + // manually before android build + // dependsOn("cargoBuild") } dependencies { @@ -163,6 +164,8 @@ dependencies { val uniFFIBindingsDir = layout.buildDirectory.dir("generated/source/uniffi/java") tasks.register("generateUniFFIBindings") { + description = "generates UniFFI bindings for native rust libs" + inputs.file("${project.projectDir}/../../vault-mobile/src/vault-mobile.udl") outputs.dir(uniFFIBindingsDir) @@ -191,58 +194,45 @@ kotlin { } } -apply(plugin = "org.mozilla.rust-android-gradle.rust-android") +interface ExecuteOps { + @get:Inject val execOps: ExecOperations +} fun getGitRevision(): String { + val x = project.objects.newInstance() val stdout = ByteArrayOutputStream() - project.exec { + x.execOps.exec { commandLine("git", "rev-parse", "--short", "HEAD") standardOutput = stdout } - return String(stdout.toByteArray()).trim() + val rev = String(stdout.toByteArray()).trim() + return rev } fun getGitRelease(): String { + val x = project.objects.newInstance() val stdout = ByteArrayOutputStream() - project.exec { + x.execOps.exec { commandLine("git", "describe", "--tags", "--exact-match") standardOutput = stdout isIgnoreExitValue = true } - return String(stdout.toByteArray()).trim() + val rel = String(stdout.toByteArray()).trim() + return rel } -extensions.configure(com.nishtahir.CargoExtension::class) { +cargo { module = "../../vault-mobile" libname = "vault_mobile" targets = listOf("arm", "arm64", "x86", "x86_64") -// targets = listOf("x86") targetDirectory = "../../target" pythonCommand = "python3" profile = System.getenv("GRADLE_CARGO_PROFILE") ?: "release" - exec = { spec, _ -> - spec.environment("GIT_REVISION", getGitRevision()) - spec.environment("GIT_RELEASE", getGitRelease()) - - // Support 16 KB page sizes - // https://github.com/mozilla/rust-android-gradle/pull/151#issuecomment-2931056842 - spec.environment( - "RUST_ANDROID_GRADLE_CC_LINK_ARG", - "-Wl,-z,max-page-size=16384,-soname,libvault_mobile.so", - ) - } + environmentalOverrides["GIT_REVISION"] = getGitRevision() + environmentalOverrides["GIT_RELEASE"] = getGitRelease() + environmentalOverrides["RUST_ANDROID_GRADLE_CC_LINK_ARG"] = "-Wl,-z,max-page-size=16384,-soname,libvault_mobile.so" } -//tasks.whenTaskAdded { -// if (name == "javaPreCompileDebug" || name == "javaPreCompileRelease") { -// dependsOn("cargoBuild") -// dependsOn("generateUniFFIBindings") -// } -// if (name == "kaptGenerateStubsDebugKotlin" || name == "kaptGenerateStubsReleaseKotlin") { -// dependsOn("generateUniFFIBindings") -// } -//} - val mergedJniLibsDir = layout.buildDirectory.dir("intermediates/merged_jni_libs") // mergeDebugNativeLibs and mergeReleaseNativeLibs don't update the .so files in @@ -250,6 +240,8 @@ val mergedJniLibsDir = layout.buildDirectory.dir("intermediates/merged_jni_libs" // cargoBuild the new libraries will be copied correctly without needing to run // clean task tasks.register("cleanupMergedJniLibs") { + description = "cleans merged JNI libs so the shared object files get updated" + delete(mergedJniLibsDir) doLast { @@ -262,12 +254,3 @@ tasks.whenTaskAdded { dependsOn("cleanupMergedJniLibs") } } - -task("printJniLibs") { - doLast { - println("debug") - println(android.sourceSets["debug"].jniLibs) - println("release") - println(android.sourceSets["release"].jniLibs) - } -} diff --git a/vault-android/gradle.properties b/vault-android/gradle.properties index 20e2a015..8d91f1fc 100644 --- a/vault-android/gradle.properties +++ b/vault-android/gradle.properties @@ -20,4 +20,17 @@ kotlin.code.style=official # Enables namespacing of each library's R class so that its R class includes only the # resources declared in the library itself and none from the library's dependencies, # thereby reducing the size of the R class for that library -android.nonTransitiveRClass=true \ No newline at end of file +android.nonTransitiveRClass=true +android.defaults.buildfeatures.resvalues=true +android.sdk.defaultTargetSdkToCompileSdkIfUnset=false +android.enableAppCompileTimeRClass=false +android.usesSdkInManifest.disallowed=false +android.uniquePackageNames=false +android.dependency.useConstraints=true +android.r8.strictFullModeForKeepRules=false +android.r8.optimizedResourceShrinking=false +android.builtInKotlin=false +android.newDsl=false + +# suppress warnings until we replace all the deprecated shit +android.sync.suppressAgpWarnings=DEPRECATED_DSL,UNSUPPORTED_PROJECT_OPTION_USE,LIBRARY_CONSTRAINTS_SHOULD_BE_DISABLED diff --git a/vault-android/gradle/libs.versions.toml b/vault-android/gradle/libs.versions.toml index b3a4c9c5..47118bb8 100644 --- a/vault-android/gradle/libs.versions.toml +++ b/vault-android/gradle/libs.versions.toml @@ -1,10 +1,10 @@ [versions] # Plugins -agp = "8.13.2" -kotlin = "2.0.21" -ksp = "2.0.21-1.0.25" +agp = "9.3.0" +kotlin = "2.2.10" +ksp = "2.3.2" hilt = "2.57.1" -rustAndroid = "0.9.6" +rustAndroid = "0.10.1" # AndroidX + Google coreKtx = "1.12.0" @@ -99,4 +99,4 @@ kotlinx-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", vers ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" } compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } hilt-android = { id = "com.google.dagger.hilt.android", version.ref = "hilt" } -rust-android = { id = "org.mozilla.rust-android-gradle.rust-android", version.ref = "rustAndroid" } +rust-android = { id = "net.mullvad.rust-android", version.ref = "rustAndroid" } diff --git a/vault-android/gradle/wrapper/gradle-wrapper.properties b/vault-android/gradle/wrapper/gradle-wrapper.properties index 8fbd84bd..37427da5 100644 --- a/vault-android/gradle/wrapper/gradle-wrapper.properties +++ b/vault-android/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ #Wed Oct 04 17:00:27 CEST 2023 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.5.0-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/vault-mobile/uniffi.toml b/vault-mobile/uniffi.toml index 1a2fc599..49f182fd 100644 --- a/vault-mobile/uniffi.toml +++ b/vault-mobile/uniffi.toml @@ -4,3 +4,4 @@ cdylib_name = "vault_mobile" [bindings.kotlin] package_name = "net.koofr.vault" cdylib_name = "vault_mobile" +android_cleaner = true From 2f725a7d109e682184b0d0a4bcb73f5da6551007 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaka=20Mo=C4=8Dnik?= Date: Mon, 3 Aug 2026 14:38:35 +0200 Subject: [PATCH 2/6] Yeeted more deprecated build constructs into the void. --- vault-android/app/build.gradle.kts | 82 +++++++++++++------------ vault-android/build.gradle.kts | 1 - vault-android/gradle.properties | 17 +++-- vault-android/gradle/libs.versions.toml | 5 +- 4 files changed, 54 insertions(+), 51 deletions(-) diff --git a/vault-android/app/build.gradle.kts b/vault-android/app/build.gradle.kts index 96483cd0..a4f547a3 100644 --- a/vault-android/app/build.gradle.kts +++ b/vault-android/app/build.gradle.kts @@ -1,9 +1,12 @@ import com.android.build.gradle.internal.cxx.configure.gradleLocalProperties +import com.android.build.api.dsl.ApplicationExtension +import net.mullvad.androidrust.android +import org.gradle.kotlin.dsl.android +import org.jetbrains.kotlin.gradle.dsl.JvmTarget import java.io.ByteArrayOutputStream plugins { alias(libs.plugins.android.application) - alias(libs.plugins.kotlin.android) alias(libs.plugins.ksp) alias(libs.plugins.hilt.android) alias(libs.plugins.compose.compiler) @@ -13,10 +16,48 @@ plugins { val localProperties = gradleLocalProperties(rootDir, providers) -android { +val uniFFIBindingsPath = "generated/source/uniffi/java" +val uniFFIBindingsDir = layout.buildDirectory.dir(uniFFIBindingsPath) + +tasks.register("generateUniFFIBindings") { + description = "generates UniFFI bindings for native rust libs" + + inputs.file("${project.projectDir}/../../vault-mobile/src/vault-mobile.udl") + outputs.dir(uniFFIBindingsDir) + + workingDir = file("${project.projectDir}/../../vault-mobile/uniffi-bindgen") + commandLine( + "cargo", + "run", + "generate", + "../src/vault-mobile.udl", + "--language", + "kotlin", + "--out-dir", + uniFFIBindingsDir.get().asFile, + ) + + doLast { + println("UniFFI bindings generated successfully!") + } +} + +kotlin { + compilerOptions { + jvmTarget = JvmTarget.JVM_17 + } +} + +extensions.configure { namespace = "net.koofr.vault" compileSdk = 37 + sourceSets { + named("main").get().kotlin { + directories.add(uniFFIBindingsDir.get().asFile.absolutePath) + } + } + defaultConfig { applicationId = "net.koofr.vault" minSdk = 24 @@ -87,12 +128,10 @@ android { sourceCompatibility = JavaVersion.VERSION_17 targetCompatibility = JavaVersion.VERSION_17 } - kotlinOptions { - jvmTarget = "17" - } buildFeatures { compose = true buildConfig = true + resValues = true } composeOptions { kotlinCompilerExtensionVersion = "1.5.3" @@ -161,39 +200,6 @@ dependencies { debugImplementation(libs.androidx.compose.ui.test.manifest) } -val uniFFIBindingsDir = layout.buildDirectory.dir("generated/source/uniffi/java") - -tasks.register("generateUniFFIBindings") { - description = "generates UniFFI bindings for native rust libs" - - inputs.file("${project.projectDir}/../../vault-mobile/src/vault-mobile.udl") - outputs.dir(uniFFIBindingsDir) - - workingDir = file("${project.projectDir}/../../vault-mobile/uniffi-bindgen") - commandLine( - "cargo", - "run", - "generate", - "../src/vault-mobile.udl", - "--language", - "kotlin", - "--out-dir", - uniFFIBindingsDir.get().asFile, - ) - - doLast { - println("UniFFI bindings generated successfully!") - } -} - -kotlin { - sourceSets { - main { - kotlin.srcDir(uniFFIBindingsDir) - } - } -} - interface ExecuteOps { @get:Inject val execOps: ExecOperations } diff --git a/vault-android/build.gradle.kts b/vault-android/build.gradle.kts index a15a3113..7024e29b 100644 --- a/vault-android/build.gradle.kts +++ b/vault-android/build.gradle.kts @@ -2,7 +2,6 @@ // sub-projects/modules. plugins { alias(libs.plugins.android.application) apply false - alias(libs.plugins.kotlin.android) apply false alias(libs.plugins.ksp) apply false alias(libs.plugins.compose.compiler) apply false alias(libs.plugins.hilt.android) apply false diff --git a/vault-android/gradle.properties b/vault-android/gradle.properties index 8d91f1fc..5ab5f138 100644 --- a/vault-android/gradle.properties +++ b/vault-android/gradle.properties @@ -21,16 +21,15 @@ kotlin.code.style=official # resources declared in the library itself and none from the library's dependencies, # thereby reducing the size of the R class for that library android.nonTransitiveRClass=true -android.defaults.buildfeatures.resvalues=true -android.sdk.defaultTargetSdkToCompileSdkIfUnset=false -android.enableAppCompileTimeRClass=false -android.usesSdkInManifest.disallowed=false +#android.defaults.buildfeatures.resvalues=true +#android.sdk.defaultTargetSdkToCompileSdkIfUnset=false +#android.enableAppCompileTimeRClass=false +#android.usesSdkInManifest.disallowed=false android.uniquePackageNames=false -android.dependency.useConstraints=true +android.dependency.useConstraints=false +#android.dependency.excludeLibraryComponentsFromConstraints=true android.r8.strictFullModeForKeepRules=false -android.r8.optimizedResourceShrinking=false -android.builtInKotlin=false -android.newDsl=false +#android.r8.optimizedResourceShrinking=false # suppress warnings until we replace all the deprecated shit -android.sync.suppressAgpWarnings=DEPRECATED_DSL,UNSUPPORTED_PROJECT_OPTION_USE,LIBRARY_CONSTRAINTS_SHOULD_BE_DISABLED +#android.sync.suppressAgpWarnings=DEPRECATED_DSL,UNSUPPORTED_PROJECT_OPTION_USE,LIBRARY_CONSTRAINTS_SHOULD_BE_DISABLED diff --git a/vault-android/gradle/libs.versions.toml b/vault-android/gradle/libs.versions.toml index 47118bb8..4c216fe2 100644 --- a/vault-android/gradle/libs.versions.toml +++ b/vault-android/gradle/libs.versions.toml @@ -2,8 +2,8 @@ # Plugins agp = "9.3.0" kotlin = "2.2.10" -ksp = "2.3.2" -hilt = "2.57.1" +ksp = "2.3.4" +hilt = "2.60.1" rustAndroid = "0.10.1" # AndroidX + Google @@ -94,7 +94,6 @@ androidx-uiautomator = { group = "androidx.test.uiautomator", name = "uiautomato [plugins] android-application = { id = "com.android.application", version.ref = "agp" } -kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } kotlinx-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" } compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } From 56cd0ecec0e0af3de397ea164e03ed236de9bc4c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaka=20Mo=C4=8Dnik?= Date: Tue, 4 Aug 2026 15:40:07 +0200 Subject: [PATCH 3/6] Yet another android tooling version bump. Fixed the "failed to parse language tag" error on some devices: turns out rust i18n machinery does not like BCP 47 extensions in the language tag. --- vault-android/app/build.gradle.kts | 2 +- vault-android/app/src/main/AndroidManifest.xml | 2 +- .../main/java/net/koofr/vault/features/intl/IntlHelper.kt | 5 ++++- vault-android/gradle/libs.versions.toml | 2 +- vault-android/gradle/wrapper/gradle-wrapper.properties | 2 +- 5 files changed, 8 insertions(+), 5 deletions(-) diff --git a/vault-android/app/build.gradle.kts b/vault-android/app/build.gradle.kts index a4f547a3..932d3eb8 100644 --- a/vault-android/app/build.gradle.kts +++ b/vault-android/app/build.gradle.kts @@ -60,7 +60,7 @@ extensions.configure { defaultConfig { applicationId = "net.koofr.vault" - minSdk = 24 + minSdk = 26 targetSdk = 37 versionCode = 126001 versionName = "0.1.26" diff --git a/vault-android/app/src/main/AndroidManifest.xml b/vault-android/app/src/main/AndroidManifest.xml index 402e0123..3d1a971f 100644 --- a/vault-android/app/src/main/AndroidManifest.xml +++ b/vault-android/app/src/main/AndroidManifest.xml @@ -21,7 +21,7 @@ android:label="@string/app_name" android:supportsRtl="true" android:theme="@style/Theme.Vault" - tools:targetApi="33"> + tools:targetApi="37"> Date: Mon, 10 Aug 2026 15:38:32 +0200 Subject: [PATCH 4/6] Move to new versions of dependencies. Fix deprecations. Removed custom pull-refresh modifier in favour of compose PullToRefreshBox. --- vault-android/app/build.gradle.kts | 1 + .../java/net/koofr/vault/CommonContent.kt | 4 +- .../vault/composables/RefreshableList.kt | 37 +-- .../composables/pullrefresh/PullRefresh.kt | 115 --------- .../pullrefresh/PullRefreshIndicator.kt | 241 ------------------ .../PullRefreshIndicatorTransform.kt | 72 ------ .../pullrefresh/PullRefreshState.kt | 229 ----------------- .../koofr/vault/features/dialogs/Dialogs.kt | 2 +- .../vault/features/landing/LandingScreen.kt | 4 +- .../features/mainnavigation/MainNavigation.kt | 2 +- .../vault/features/mobilevault/Subscribe.kt | 2 +- .../notifications/NotificationHandler.kt | 2 +- .../RemoteFilesDirPickerScreen.kt | 2 +- .../vault/features/repo/RepoInfoScreen.kt | 2 +- .../repo/RepoSetupBiometricUnlockDialog.kt | 2 +- .../features/repo/UnlockedRepoWrapper.kt | 2 +- .../features/repocreate/RepoCreateFormView.kt | 8 +- .../features/repocreate/RepoCreateScreen.kt | 2 +- .../repofiles/RepoFilesScreenViewModel.kt | 24 +- .../features/reporemove/RepoRemoveScreen.kt | 2 +- .../koofr/vault/features/repos/ReposScreen.kt | 2 +- .../features/repounlock/RepoUnlockScreen.kt | 4 +- .../vault/features/settings/InfoScreen.kt | 2 +- .../vault/features/settings/SettingsScreen.kt | 2 +- .../shareactivity/ShareActivityScreen.kt | 2 +- .../sharetarget/ShareTargetBottomBar.kt | 31 ++- .../sharetarget/ShareTargetNavigation.kt | 2 +- .../features/transfers/TransferInfoView.kt | 2 +- .../transfers/TransfersSummaryBottomBar.kt | 2 +- .../vault/features/transfers/TransfersView.kt | 2 +- .../vault/features/uploads/TakePicture.kt | 2 +- .../net/koofr/vault/features/user/UserIcon.kt | 2 +- vault-android/gradle/libs.versions.toml | 32 +-- 33 files changed, 107 insertions(+), 735 deletions(-) delete mode 100644 vault-android/app/src/main/java/net/koofr/vault/composables/pullrefresh/PullRefresh.kt delete mode 100644 vault-android/app/src/main/java/net/koofr/vault/composables/pullrefresh/PullRefreshIndicator.kt delete mode 100644 vault-android/app/src/main/java/net/koofr/vault/composables/pullrefresh/PullRefreshIndicatorTransform.kt delete mode 100644 vault-android/app/src/main/java/net/koofr/vault/composables/pullrefresh/PullRefreshState.kt diff --git a/vault-android/app/build.gradle.kts b/vault-android/app/build.gradle.kts index 932d3eb8..1a8a58a0 100644 --- a/vault-android/app/build.gradle.kts +++ b/vault-android/app/build.gradle.kts @@ -33,6 +33,7 @@ tasks.register("generateUniFFIBindings") { "../src/vault-mobile.udl", "--language", "kotlin", + "--no-format", "--out-dir", uniFFIBindingsDir.get().asFile, ) diff --git a/vault-android/app/src/main/java/net/koofr/vault/CommonContent.kt b/vault-android/app/src/main/java/net/koofr/vault/CommonContent.kt index 7b8dfb16..6509c883 100644 --- a/vault-android/app/src/main/java/net/koofr/vault/CommonContent.kt +++ b/vault-android/app/src/main/java/net/koofr/vault/CommonContent.kt @@ -6,11 +6,11 @@ import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.compositionLocalOf import androidx.compose.runtime.remember -import androidx.compose.ui.platform.LocalLifecycleOwner -import androidx.hilt.navigation.compose.hiltViewModel +import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel import androidx.lifecycle.DefaultLifecycleObserver import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.ViewModel +import androidx.lifecycle.compose.LocalLifecycleOwner import dagger.hilt.android.lifecycle.HiltViewModel import net.koofr.vault.features.dialogs.Dialogs import net.koofr.vault.features.notifications.NotificationHandler diff --git a/vault-android/app/src/main/java/net/koofr/vault/composables/RefreshableList.kt b/vault-android/app/src/main/java/net/koofr/vault/composables/RefreshableList.kt index 2c30261b..9a4731a9 100644 --- a/vault-android/app/src/main/java/net/koofr/vault/composables/RefreshableList.kt +++ b/vault-android/app/src/main/java/net/koofr/vault/composables/RefreshableList.kt @@ -1,20 +1,20 @@ package net.koofr.vault.composables import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyListScope +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.pulltorefresh.PullToRefreshBox +import androidx.compose.material3.pulltorefresh.rememberPullToRefreshState +import androidx.compose.material3.pulltorefresh.PullToRefreshDefaults.Indicator import androidx.compose.runtime.Composable import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import net.koofr.vault.Status -import net.koofr.vault.composables.pullrefresh.PullRefreshIndicator -import net.koofr.vault.composables.pullrefresh.pullRefresh -import net.koofr.vault.composables.pullrefresh.rememberPullRefreshState @Composable fun RefreshableList( @@ -29,19 +29,26 @@ fun RefreshableList( val refreshing = pullRefreshing.value && status is Status.Loading && status.loaded - val pullRefreshState = rememberPullRefreshState( - refreshing = refreshing, + val state = rememberPullToRefreshState() + + PullToRefreshBox( + isRefreshing = refreshing, onRefresh = { pullRefreshing.value = true onRefresh() }, - ) - - Box( - modifier = modifier - .fillMaxSize() - .pullRefresh(pullRefreshState), + modifier = modifier.fillMaxSize(), + state = state, + indicator = { + Indicator( + state = state, + isRefreshing = refreshing, + modifier = Modifier.align(Alignment.TopCenter), + containerColor = MaterialTheme.colorScheme.background, + color = MaterialTheme.colorScheme.primary + ) + } ) { LazyColumn(modifier = Modifier.fillMaxSize()) { when { @@ -77,12 +84,6 @@ fun RefreshableList( } } - PullRefreshIndicator( - refreshing = refreshing, - state = pullRefreshState, - modifier = Modifier.align(Alignment.TopCenter), - ) - if (status is Status.Loading && !status.loaded) { LoadingView() } diff --git a/vault-android/app/src/main/java/net/koofr/vault/composables/pullrefresh/PullRefresh.kt b/vault-android/app/src/main/java/net/koofr/vault/composables/pullrefresh/PullRefresh.kt deleted file mode 100644 index 9f1a1b66..00000000 --- a/vault-android/app/src/main/java/net/koofr/vault/composables/pullrefresh/PullRefresh.kt +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Copyright 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package net.koofr.vault.composables.pullrefresh - -import androidx.compose.ui.Modifier -import androidx.compose.ui.geometry.Offset -import androidx.compose.ui.input.nestedscroll.NestedScrollConnection -import androidx.compose.ui.input.nestedscroll.NestedScrollSource -import androidx.compose.ui.input.nestedscroll.NestedScrollSource.Companion.Drag -import androidx.compose.ui.input.nestedscroll.nestedScroll -import androidx.compose.ui.platform.debugInspectorInfo -import androidx.compose.ui.platform.inspectable -import androidx.compose.ui.unit.Velocity - -/** - * A nested scroll modifier that provides scroll events to [state]. - * - * Note that this modifier must be added above a scrolling container, such as a lazy column, in - * order to receive scroll events. For example: - * - * @param state The [PullRefreshState] associated with this pull-to-refresh component. - * The state will be updated by this modifier. - * @param enabled If not enabled, all scroll delta and fling velocity will be ignored. - */ -fun Modifier.pullRefresh( - state: PullRefreshState, - enabled: Boolean = true, -) = inspectable( - inspectorInfo = debugInspectorInfo { - name = "pullRefresh" - properties["state"] = state - properties["enabled"] = enabled - }, -) { - Modifier.pullRefresh(state::onPull, state::onRelease, enabled) -} - -/** - * A nested scroll modifier that provides [onPull] and [onRelease] callbacks to aid building custom - * pull refresh components. - * - * Note that this modifier must be added above a scrolling container, such as a lazy column, in - * order to receive scroll events. For example: - * - * @param onPull Callback for dispatching vertical scroll delta, takes float pullDelta as argument. - * Positive delta (pulling down) is dispatched only if the child does not consume it (i.e. pulling - * down despite being at the top of a scrollable component), whereas negative delta (swiping up) is - * dispatched first (in case it is needed to push the indicator back up), and then the unconsumed - * delta is passed on to the child. The callback returns how much delta was consumed. - * @param onRelease Callback for when drag is released, takes float flingVelocity as argument. - * The callback returns how much velocity was consumed - in most cases this should only consume - * velocity if pull refresh has been dragged already and the velocity is positive (the fling is - * downwards), as an upwards fling should typically still scroll a scrollable component beneath the - * pullRefresh. This is invoked before any remaining velocity is passed to the child. - * @param enabled If not enabled, all scroll delta and fling velocity will be ignored and neither - * [onPull] nor [onRelease] will be invoked. - */ -fun Modifier.pullRefresh( - onPull: (pullDelta: Float) -> Float, - onRelease: suspend (flingVelocity: Float) -> Float, - enabled: Boolean = true, -) = inspectable( - inspectorInfo = debugInspectorInfo { - name = "pullRefresh" - properties["onPull"] = onPull - properties["onRelease"] = onRelease - properties["enabled"] = enabled - }, -) { - Modifier.nestedScroll(PullRefreshNestedScrollConnection(onPull, onRelease, enabled)) -} - -private class PullRefreshNestedScrollConnection( - private val onPull: (pullDelta: Float) -> Float, - private val onRelease: suspend (flingVelocity: Float) -> Float, - private val enabled: Boolean, -) : NestedScrollConnection { - - override fun onPreScroll( - available: Offset, - source: NestedScrollSource, - ): Offset = when { - !enabled -> Offset.Zero - source == Drag && available.y < 0 -> Offset(0f, onPull(available.y)) // Swiping up - else -> Offset.Zero - } - - override fun onPostScroll( - consumed: Offset, - available: Offset, - source: NestedScrollSource, - ): Offset = when { - !enabled -> Offset.Zero - source == Drag && available.y > 0 -> Offset(0f, onPull(available.y)) // Pulling down - else -> Offset.Zero - } - - override suspend fun onPreFling(available: Velocity): Velocity { - return Velocity(0f, onRelease(available.y)) - } -} diff --git a/vault-android/app/src/main/java/net/koofr/vault/composables/pullrefresh/PullRefreshIndicator.kt b/vault-android/app/src/main/java/net/koofr/vault/composables/pullrefresh/PullRefreshIndicator.kt deleted file mode 100644 index 0eca9873..00000000 --- a/vault-android/app/src/main/java/net/koofr/vault/composables/pullrefresh/PullRefreshIndicator.kt +++ /dev/null @@ -1,241 +0,0 @@ -/* - * Copyright 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -@file:Suppress("ConstPropertyName", "PrivatePropertyName") - -package net.koofr.vault.composables.pullrefresh - -import androidx.compose.animation.Crossfade -import androidx.compose.animation.core.LinearEasing -import androidx.compose.animation.core.animateFloatAsState -import androidx.compose.animation.core.tween -import androidx.compose.foundation.Canvas -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.shape.CircleShape -import androidx.compose.material3.CircularProgressIndicator -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.contentColorFor -import androidx.compose.runtime.Composable -import androidx.compose.runtime.Immutable -import androidx.compose.runtime.derivedStateOf -import androidx.compose.runtime.getValue -import androidx.compose.runtime.remember -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.shadow -import androidx.compose.ui.geometry.Offset -import androidx.compose.ui.geometry.Rect -import androidx.compose.ui.geometry.center -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.Path -import androidx.compose.ui.graphics.PathFillType -import androidx.compose.ui.graphics.StrokeCap -import androidx.compose.ui.graphics.drawscope.DrawScope -import androidx.compose.ui.graphics.drawscope.Stroke -import androidx.compose.ui.graphics.drawscope.rotate -import androidx.compose.ui.semantics.semantics -import androidx.compose.ui.unit.dp -import kotlin.math.abs -import kotlin.math.max -import kotlin.math.min -import kotlin.math.pow - -/** - * The default indicator for Compose pull-to-refresh, based on Android's SwipeRefreshLayout. - * - * @param refreshing A boolean representing whether a refresh is occurring. - * @param state The [PullRefreshState] which controls where and how the indicator will be drawn. - * @param modifier Modifiers for the indicator. - * @param backgroundColor The color of the indicator's background. - * @param contentColor The color of the indicator's arc and arrow. - * @param scale A boolean controlling whether the indicator's size scales with pull progress or not. - */ -@Composable -fun PullRefreshIndicator( - refreshing: Boolean, - state: PullRefreshState, - modifier: Modifier = Modifier, - backgroundColor: Color = MaterialTheme.colorScheme.surface, - contentColor: Color = contentColorFor(backgroundColor), - scale: Boolean = false, -) { - val showElevation by remember(refreshing, state) { - derivedStateOf { refreshing || state.position > 0.5f } - } - - Box( - modifier = modifier - .size(IndicatorSize) - .pullRefreshIndicatorTransform(state, scale) - .shadow(if (showElevation) Elevation else 0.dp, SpinnerShape, clip = true) - .background(color = backgroundColor, shape = SpinnerShape), - ) { - Crossfade( - targetState = refreshing, - animationSpec = tween(durationMillis = CrossfadeDurationMs), - label = "PullRefreshIndicatorCrossfade", - ) { refreshing -> - Box( - modifier = Modifier.fillMaxSize(), - contentAlignment = Alignment.Center, - ) { - val spinnerSize = (ArcRadius + StrokeWidth).times(2) - - if (refreshing) { - CircularProgressIndicator( - color = contentColor, - strokeWidth = StrokeWidth, - modifier = Modifier.size(spinnerSize), - ) - } else { - CircularArrowIndicator(state, contentColor, Modifier.size(spinnerSize)) - } - } - } - } -} - -/** - * Modifier.size MUST be specified. - */ -@Composable -private fun CircularArrowIndicator( - state: PullRefreshState, - color: Color, - modifier: Modifier, -) { - val path = remember { Path().apply { fillType = PathFillType.EvenOdd } } - - val targetAlpha by remember(state) { - derivedStateOf { - if (state.progress >= 1f) MaxAlpha else MinAlpha - } - } - - val alphaState = animateFloatAsState( - targetValue = targetAlpha, - animationSpec = AlphaTween, - label = "CircularArrowIndicatorAlphaState", - ) - - // Empty semantics for tests - Canvas(modifier.semantics {}) { - val values = ArrowValues(state.progress) - val alpha = alphaState.value - - rotate(degrees = values.rotation) { - val arcRadius = ArcRadius.toPx() + StrokeWidth.toPx() / 2f - val arcBounds = Rect( - size.center.x - arcRadius, - size.center.y - arcRadius, - size.center.x + arcRadius, - size.center.y + arcRadius, - ) - drawArc( - color = color, - alpha = alpha, - startAngle = values.startAngle, - sweepAngle = values.endAngle - values.startAngle, - useCenter = false, - topLeft = arcBounds.topLeft, - size = arcBounds.size, - style = Stroke( - width = StrokeWidth.toPx(), - cap = StrokeCap.Square, - ), - ) - drawArrow(path, arcBounds, color, alpha, values) - } - } -} - -@Immutable -private class ArrowValues( - val rotation: Float, - val startAngle: Float, - val endAngle: Float, - val scale: Float, -) - -private fun ArrowValues(progress: Float): ArrowValues { - // Discard first 40% of progress. Scale remaining progress to full range between 0 and 100%. - val adjustedPercent = max(min(1f, progress) - 0.4f, 0f) * 5 / 3 - // How far beyond the threshold pull has gone, as a percentage of the threshold. - val overshootPercent = abs(progress) - 1.0f - // Limit the overshoot to 200%. Linear between 0 and 200. - val linearTension = overshootPercent.coerceIn(0f, 2f) - // Non-linear tension. Increases with linearTension, but at a decreasing rate. - val tensionPercent = linearTension - linearTension.pow(2) / 4 - - // Calculations based on SwipeRefreshLayout specification. - val endTrim = adjustedPercent * MaxProgressArc - val rotation = (-0.25f + 0.4f * adjustedPercent + tensionPercent) * 0.5f - val startAngle = rotation * 360 - val endAngle = (rotation + endTrim) * 360 - val scale = min(1f, adjustedPercent) - - return ArrowValues(rotation, startAngle, endAngle, scale) -} - -private fun DrawScope.drawArrow( - arrow: Path, - bounds: Rect, - color: Color, - alpha: Float, - values: ArrowValues, -) { - arrow.reset() - arrow.moveTo(0f, 0f) // Move to left corner - arrow.lineTo(x = ArrowWidth.toPx() * values.scale, y = 0f) // Line to right corner - - // Line to tip of arrow - arrow.lineTo( - x = ArrowWidth.toPx() * values.scale / 2, - y = ArrowHeight.toPx() * values.scale, - ) - - val radius = min(bounds.width, bounds.height) / 2f - val inset = ArrowWidth.toPx() * values.scale / 2f - arrow.translate( - Offset( - x = radius + bounds.center.x - inset, - y = bounds.center.y + StrokeWidth.toPx() / 2f, - ), - ) - arrow.close() - rotate(degrees = values.endAngle) { - drawPath(path = arrow, color = color, alpha = alpha) - } -} - -private const val CrossfadeDurationMs = 100 -private const val MaxProgressArc = 0.8f - -private val IndicatorSize = 40.dp -private val SpinnerShape = CircleShape -private val ArcRadius = 7.5.dp -private val StrokeWidth = 2.5.dp -private val ArrowWidth = 10.dp -private val ArrowHeight = 5.dp -private val Elevation = 6.dp - -// Values taken from SwipeRefreshLayout -private const val MinAlpha = 0.3f -private const val MaxAlpha = 1f -private val AlphaTween = tween(300, easing = LinearEasing) diff --git a/vault-android/app/src/main/java/net/koofr/vault/composables/pullrefresh/PullRefreshIndicatorTransform.kt b/vault-android/app/src/main/java/net/koofr/vault/composables/pullrefresh/PullRefreshIndicatorTransform.kt deleted file mode 100644 index 15f1023f..00000000 --- a/vault-android/app/src/main/java/net/koofr/vault/composables/pullrefresh/PullRefreshIndicatorTransform.kt +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package net.koofr.vault.composables.pullrefresh - -import androidx.compose.animation.core.LinearOutSlowInEasing -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.drawWithContent -import androidx.compose.ui.graphics.drawscope.clipRect -import androidx.compose.ui.graphics.graphicsLayer -import androidx.compose.ui.platform.debugInspectorInfo -import androidx.compose.ui.platform.inspectable - -/** - * A modifier for translating the position and scaling the size of a pull-to-refresh indicator - * based on the given [PullRefreshState]. - * - * @param state The [PullRefreshState] which determines the position of the indicator. - * @param scale A boolean controlling whether the indicator's size scales with pull progress or not. - */ -fun Modifier.pullRefreshIndicatorTransform( - state: PullRefreshState, - scale: Boolean = false, -) = inspectable( - inspectorInfo = debugInspectorInfo { - name = "pullRefreshIndicatorTransform" - properties["state"] = state - properties["scale"] = scale - }, -) { - Modifier - // Essentially we only want to clip the at the top, so the indicator will not appear when - // the position is 0. It is preferable to clip the indicator as opposed to the layout that - // contains the indicator, as this would also end up clipping shadows drawn by items in a - // list for example - so we leave the clipping to the scrolling container. We use MAX_VALUE - // for the other dimensions to allow for more room for elevation / arbitrary indicators - we - // only ever really want to clip at the top edge. - .drawWithContent { - clipRect( - top = 0f, - left = -Float.MAX_VALUE, - right = Float.MAX_VALUE, - bottom = Float.MAX_VALUE, - ) { - this@drawWithContent.drawContent() - } - } - .graphicsLayer { - translationY = state.position - size.height - - if (scale && !state.refreshing) { - val scaleFraction = LinearOutSlowInEasing - .transform(state.position / state.threshold) - .coerceIn(0f, 1f) - scaleX = scaleFraction - scaleY = scaleFraction - } - } -} diff --git a/vault-android/app/src/main/java/net/koofr/vault/composables/pullrefresh/PullRefreshState.kt b/vault-android/app/src/main/java/net/koofr/vault/composables/pullrefresh/PullRefreshState.kt deleted file mode 100644 index 49546584..00000000 --- a/vault-android/app/src/main/java/net/koofr/vault/composables/pullrefresh/PullRefreshState.kt +++ /dev/null @@ -1,229 +0,0 @@ -/* - * Copyright 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -@file:Suppress("ConstPropertyName") - -package net.koofr.vault.composables.pullrefresh - -import androidx.compose.animation.core.animate -import androidx.compose.foundation.MutatorMutex -import androidx.compose.runtime.Composable -import androidx.compose.runtime.SideEffect -import androidx.compose.runtime.State -import androidx.compose.runtime.derivedStateOf -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableFloatStateOf -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberCoroutineScope -import androidx.compose.runtime.rememberUpdatedState -import androidx.compose.runtime.setValue -import androidx.compose.ui.platform.LocalDensity -import androidx.compose.ui.unit.Dp -import androidx.compose.ui.unit.dp -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.launch -import kotlin.math.abs -import kotlin.math.pow - -/** - * Creates a [PullRefreshState] that is remembered across compositions. - * - * Changes to [refreshing] will result in [PullRefreshState] being updated. - * - * @param refreshing A boolean representing whether a refresh is currently occurring. - * @param onRefresh The function to be called to trigger a refresh. - * @param refreshThreshold The threshold below which, if a release - * occurs, [onRefresh] will be called. - * @param refreshingOffset The offset at which the indicator will be drawn while refreshing. This - * offset corresponds to the position of the bottom of the indicator. - */ -@Composable -fun rememberPullRefreshState( - refreshing: Boolean, - onRefresh: () -> Unit, - refreshThreshold: Dp = PullRefreshDefaults.RefreshThreshold, - refreshingOffset: Dp = PullRefreshDefaults.RefreshingOffset, -): PullRefreshState { - require(refreshThreshold > 0.dp) { "The refresh trigger must be greater than zero!" } - - val scope = rememberCoroutineScope() - val onRefreshState = rememberUpdatedState(onRefresh) - val thresholdPx: Float - val refreshingOffsetPx: Float - - with(LocalDensity.current) { - thresholdPx = refreshThreshold.toPx() - refreshingOffsetPx = refreshingOffset.toPx() - } - - val state = remember(scope) { - PullRefreshState(scope, onRefreshState, refreshingOffsetPx, thresholdPx) - } - - SideEffect { - state.setRefreshing(refreshing) - state.setThreshold(thresholdPx) - state.setRefreshingOffset(refreshingOffsetPx) - } - - return state -} - -/** - * A state object that can be used in conjunction with [pullRefresh] to add pull-to-refresh - * behaviour to a scroll component. Based on Android's SwipeRefreshLayout. - * - * Provides [progress], a float representing how far the user has pulled as a percentage of the - * refreshThreshold. Values of one or less indicate that the user has not yet pulled past the - * threshold. Values greater than one indicate how far past the threshold the user has pulled. - * - * Can be used in conjunction with [pullRefreshIndicatorTransform] to implement Android-like - * pull-to-refresh behaviour with a custom indicator. - * - * Should be created using [rememberPullRefreshState]. - */ -class PullRefreshState internal constructor( - private val animationScope: CoroutineScope, - private val onRefreshState: State<() -> Unit>, - refreshingOffset: Float, - threshold: Float, -) { - /** - * A float representing how far the user has pulled as a percentage of the refreshThreshold. - * - * If the component has not been pulled at all, progress is zero. If the pull has reached - * halfway to the threshold, progress is 0.5f. A value greater than 1 indicates that pull has - * gone beyond the refreshThreshold - e.g. a value of 2f indicates that the user has pulled to - * two times the refreshThreshold. - */ - val progress get() = adjustedDistancePulled / threshold - - internal val refreshing get() = _refreshing - internal val position get() = _position - internal val threshold get() = _threshold - - private val adjustedDistancePulled by derivedStateOf { distancePulled * DragMultiplier } - - private var _refreshing by mutableStateOf(false) - private var _position by mutableFloatStateOf(0f) - private var distancePulled by mutableFloatStateOf(0f) - private var _threshold by mutableFloatStateOf(threshold) - private var _refreshingOffset by mutableFloatStateOf(refreshingOffset) - - internal fun onPull(pullDelta: Float): Float { - if (_refreshing) return 0f // Already refreshing, do nothing. - - val newOffset = (distancePulled + pullDelta).coerceAtLeast(0f) - val dragConsumed = newOffset - distancePulled - distancePulled = newOffset - _position = calculateIndicatorPosition() - return dragConsumed - } - - internal fun onRelease(velocity: Float): Float { - if (refreshing) return 0f // Already refreshing, do nothing - - if (adjustedDistancePulled > threshold) { - onRefreshState.value() - } - animateIndicatorTo(0f) - val consumed = when { - // We are flinging without having dragged the pull refresh (for example a fling inside - // a list) - don't consume - distancePulled == 0f -> 0f - // If the velocity is negative, the fling is upwards, and we don't want to prevent the - // the list from scrolling - velocity < 0f -> 0f - // We are showing the indicator, and the fling is downwards - consume everything - else -> velocity - } - distancePulled = 0f - return consumed - } - - internal fun setRefreshing(refreshing: Boolean) { - if (_refreshing != refreshing) { - _refreshing = refreshing - distancePulled = 0f - animateIndicatorTo(if (refreshing) _refreshingOffset else 0f) - } - } - - internal fun setThreshold(threshold: Float) { - _threshold = threshold - } - - internal fun setRefreshingOffset(refreshingOffset: Float) { - if (_refreshingOffset != refreshingOffset) { - _refreshingOffset = refreshingOffset - if (refreshing) animateIndicatorTo(refreshingOffset) - } - } - - // Make sure to cancel any existing animations when we launch a new one. We use this instead of - // Animatable as calling snapTo() on every drag delta has a one frame delay, and some extra - // overhead of running through the animation pipeline instead of directly mutating the state. - private val mutatorMutex = MutatorMutex() - - private fun animateIndicatorTo(offset: Float) = animationScope.launch { - mutatorMutex.mutate { - animate(initialValue = _position, targetValue = offset) { value, _ -> - _position = value - } - } - } - - private fun calculateIndicatorPosition(): Float = when { - // If drag hasn't gone past the threshold, the position is the adjustedDistancePulled. - adjustedDistancePulled <= threshold -> adjustedDistancePulled - else -> { - // How far beyond the threshold pull has gone, as a percentage of the threshold. - val overshootPercent = abs(progress) - 1.0f - // Limit the overshoot to 200%. Linear between 0 and 200. - val linearTension = overshootPercent.coerceIn(0f, 2f) - // Non-linear tension. Increases with linearTension, but at a decreasing rate. - val tensionPercent = linearTension - linearTension.pow(2) / 4 - // The additional offset beyond the threshold. - val extraOffset = threshold * tensionPercent - threshold + extraOffset - } - } -} - -/** - * Default parameter values for [rememberPullRefreshState]. - */ -object PullRefreshDefaults { - /** - * If the indicator is below this threshold offset when it is released, a refresh - * will be triggered. - */ - val RefreshThreshold = 80.dp - - /** - * The offset at which the indicator should be rendered whilst a refresh is occurring. - */ - val RefreshingOffset = 56.dp -} - -/** - * The distance pulled is multiplied by this value to give us the adjusted distance pulled, which - * is used in calculating the indicator position (when the adjusted distance pulled is less than - * the refresh threshold, it is the indicator position, otherwise the indicator position is - * derived from the progress). - */ -private const val DragMultiplier = 0.5f diff --git a/vault-android/app/src/main/java/net/koofr/vault/features/dialogs/Dialogs.kt b/vault-android/app/src/main/java/net/koofr/vault/features/dialogs/Dialogs.kt index ab7003de..780cc634 100644 --- a/vault-android/app/src/main/java/net/koofr/vault/features/dialogs/Dialogs.kt +++ b/vault-android/app/src/main/java/net/koofr/vault/features/dialogs/Dialogs.kt @@ -19,7 +19,7 @@ import androidx.compose.ui.text.TextRange import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.TextFieldValue import androidx.compose.ui.unit.dp -import androidx.hilt.navigation.compose.hiltViewModel +import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel import androidx.lifecycle.ViewModel import dagger.hilt.android.lifecycle.HiltViewModel import net.koofr.vault.Dialog diff --git a/vault-android/app/src/main/java/net/koofr/vault/features/landing/LandingScreen.kt b/vault-android/app/src/main/java/net/koofr/vault/features/landing/LandingScreen.kt index 0e8d4f5a..2d613538 100644 --- a/vault-android/app/src/main/java/net/koofr/vault/features/landing/LandingScreen.kt +++ b/vault-android/app/src/main/java/net/koofr/vault/features/landing/LandingScreen.kt @@ -36,9 +36,9 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import androidx.hilt.navigation.compose.hiltViewModel -import androidx.lifecycle.ViewModel import dagger.hilt.android.lifecycle.HiltViewModel +import androidx.lifecycle.ViewModel +import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel import net.koofr.vault.LocalSnackbarHostState import net.koofr.vault.MobileVault import net.koofr.vault.R diff --git a/vault-android/app/src/main/java/net/koofr/vault/features/mainnavigation/MainNavigation.kt b/vault-android/app/src/main/java/net/koofr/vault/features/mainnavigation/MainNavigation.kt index 7696db33..e2a4c495 100644 --- a/vault-android/app/src/main/java/net/koofr/vault/features/mainnavigation/MainNavigation.kt +++ b/vault-android/app/src/main/java/net/koofr/vault/features/mainnavigation/MainNavigation.kt @@ -3,8 +3,8 @@ package net.koofr.vault.features.mainnavigation import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.remember -import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.viewmodel.compose.viewModel +import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel import androidx.navigation.NavType import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable diff --git a/vault-android/app/src/main/java/net/koofr/vault/features/mobilevault/Subscribe.kt b/vault-android/app/src/main/java/net/koofr/vault/features/mobilevault/Subscribe.kt index 04f8b424..8f241255 100644 --- a/vault-android/app/src/main/java/net/koofr/vault/features/mobilevault/Subscribe.kt +++ b/vault-android/app/src/main/java/net/koofr/vault/features/mobilevault/Subscribe.kt @@ -5,8 +5,8 @@ import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.State import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope -import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.ViewModel +import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel import net.koofr.vault.MobileVault import net.koofr.vault.SubscriptionCallback diff --git a/vault-android/app/src/main/java/net/koofr/vault/features/notifications/NotificationHandler.kt b/vault-android/app/src/main/java/net/koofr/vault/features/notifications/NotificationHandler.kt index 92ce68cd..d7a93ff9 100644 --- a/vault-android/app/src/main/java/net/koofr/vault/features/notifications/NotificationHandler.kt +++ b/vault-android/app/src/main/java/net/koofr/vault/features/notifications/NotificationHandler.kt @@ -3,8 +3,8 @@ package net.koofr.vault.features.notifications import android.util.Log import androidx.compose.runtime.Composable import androidx.compose.runtime.rememberCoroutineScope -import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.ViewModel +import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.launch import net.koofr.vault.LocalSnackbarHostState diff --git a/vault-android/app/src/main/java/net/koofr/vault/features/remotefilesdirpicker/RemoteFilesDirPickerScreen.kt b/vault-android/app/src/main/java/net/koofr/vault/features/remotefilesdirpicker/RemoteFilesDirPickerScreen.kt index c51a4975..8e910135 100644 --- a/vault-android/app/src/main/java/net/koofr/vault/features/remotefilesdirpicker/RemoteFilesDirPickerScreen.kt +++ b/vault-android/app/src/main/java/net/koofr/vault/features/remotefilesdirpicker/RemoteFilesDirPickerScreen.kt @@ -31,9 +31,9 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel +import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.launch import net.koofr.vault.LocalSnackbarHostState diff --git a/vault-android/app/src/main/java/net/koofr/vault/features/repo/RepoInfoScreen.kt b/vault-android/app/src/main/java/net/koofr/vault/features/repo/RepoInfoScreen.kt index 4bbb607b..ee299544 100644 --- a/vault-android/app/src/main/java/net/koofr/vault/features/repo/RepoInfoScreen.kt +++ b/vault-android/app/src/main/java/net/koofr/vault/features/repo/RepoInfoScreen.kt @@ -35,10 +35,10 @@ import androidx.compose.ui.semantics.contentDescription import androidx.compose.ui.semantics.semantics import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp -import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelStore +import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.qualifiers.ApplicationContext import net.koofr.vault.LocalSnackbarHostState diff --git a/vault-android/app/src/main/java/net/koofr/vault/features/repo/RepoSetupBiometricUnlockDialog.kt b/vault-android/app/src/main/java/net/koofr/vault/features/repo/RepoSetupBiometricUnlockDialog.kt index c46ec7f7..05664267 100644 --- a/vault-android/app/src/main/java/net/koofr/vault/features/repo/RepoSetupBiometricUnlockDialog.kt +++ b/vault-android/app/src/main/java/net/koofr/vault/features/repo/RepoSetupBiometricUnlockDialog.kt @@ -20,11 +20,11 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.compose.ui.window.Dialog import androidx.fragment.app.FragmentActivity -import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewmodel.compose.viewModel +import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.qualifiers.ApplicationContext import kotlinx.coroutines.launch diff --git a/vault-android/app/src/main/java/net/koofr/vault/features/repo/UnlockedRepoWrapper.kt b/vault-android/app/src/main/java/net/koofr/vault/features/repo/UnlockedRepoWrapper.kt index 4c723ff7..1fab7644 100644 --- a/vault-android/app/src/main/java/net/koofr/vault/features/repo/UnlockedRepoWrapper.kt +++ b/vault-android/app/src/main/java/net/koofr/vault/features/repo/UnlockedRepoWrapper.kt @@ -8,8 +8,8 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.input.pointer.PointerEventTimeoutCancellationException import androidx.compose.ui.input.pointer.pointerInput -import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.ViewModel +import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.coroutineScope import net.koofr.vault.MobileVault diff --git a/vault-android/app/src/main/java/net/koofr/vault/features/repocreate/RepoCreateFormView.kt b/vault-android/app/src/main/java/net/koofr/vault/features/repocreate/RepoCreateFormView.kt index 8b43b1a0..93fc554d 100644 --- a/vault-android/app/src/main/java/net/koofr/vault/features/repocreate/RepoCreateFormView.kt +++ b/vault-android/app/src/main/java/net/koofr/vault/features/repocreate/RepoCreateFormView.kt @@ -12,7 +12,7 @@ import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.outlined.HelpOutline +import androidx.compose.material.icons.automirrored.outlined.HelpOutline import androidx.compose.material3.Button import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon @@ -130,7 +130,7 @@ fun RepoCreateFormView(vm: RepoCreateViewModel, form: RepoCreateForm, modifier: locationInfoSheetVisible.value = true }) { Icon( - Icons.Outlined.HelpOutline, + Icons.AutoMirrored.Outlined.HelpOutline, stringResource(R.string.repo_create_form_location_info_button_content_desc), tint = Color.DarkGray, ) @@ -160,7 +160,7 @@ fun RepoCreateFormView(vm: RepoCreateViewModel, form: RepoCreateForm, modifier: safeKeyInfoSheetVisible.value = true }, modifier = Modifier.padding(top = 8.dp)) { Icon( - Icons.Outlined.HelpOutline, + Icons.AutoMirrored.Outlined.HelpOutline, stringResource(R.string.repo_create_form_password_info_button_content_desc), tint = Color.DarkGray, ) @@ -198,7 +198,7 @@ fun RepoCreateFormView(vm: RepoCreateViewModel, form: RepoCreateForm, modifier: saltInfoSheetVisible.value = true }) { Icon( - Icons.Outlined.HelpOutline, + Icons.AutoMirrored.Outlined.HelpOutline, stringResource(R.string.repo_create_form_salt_info_button_content_desc), tint = Color.DarkGray, ) diff --git a/vault-android/app/src/main/java/net/koofr/vault/features/repocreate/RepoCreateScreen.kt b/vault-android/app/src/main/java/net/koofr/vault/features/repocreate/RepoCreateScreen.kt index 531fb1a2..799a2a3e 100644 --- a/vault-android/app/src/main/java/net/koofr/vault/features/repocreate/RepoCreateScreen.kt +++ b/vault-android/app/src/main/java/net/koofr/vault/features/repocreate/RepoCreateScreen.kt @@ -9,8 +9,8 @@ import androidx.compose.material3.TopAppBar import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource -import androidx.hilt.navigation.compose.hiltViewModel import net.koofr.vault.LocalSnackbarHostState +import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel import net.koofr.vault.R import net.koofr.vault.RepoCreateInfo import net.koofr.vault.Status diff --git a/vault-android/app/src/main/java/net/koofr/vault/features/repofiles/RepoFilesScreenViewModel.kt b/vault-android/app/src/main/java/net/koofr/vault/features/repofiles/RepoFilesScreenViewModel.kt index 22496d34..5246cea3 100644 --- a/vault-android/app/src/main/java/net/koofr/vault/features/repofiles/RepoFilesScreenViewModel.kt +++ b/vault-android/app/src/main/java/net/koofr/vault/features/repofiles/RepoFilesScreenViewModel.kt @@ -2,7 +2,9 @@ package net.koofr.vault.features.repofiles import android.content.Context import android.content.Intent +import androidx.compose.material3.BottomSheetDefaults import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.ModalBottomSheetDefaults import androidx.compose.material3.SheetState import androidx.compose.material3.SheetValue import androidx.compose.runtime.mutableStateOf @@ -39,11 +41,29 @@ open class RepoFilesScreenViewModel constructor( val menuExpanded = mutableStateOf(false) - val fileInfoSheetState = mutableStateOf(SheetState(true, SheetValue.Hidden, { true }, false)) + val fileInfoSheetState = mutableStateOf( + SheetState( + skipPartiallyExpanded = true, + velocityThreshold = { 0f }, + positionalThreshold = { 0f }, + initialValue = SheetValue.Hidden, + confirmValueChange = { true }, + skipHiddenState = false + ) + ) val fileInfoSheetFile = mutableStateOf(null) val sortSheetVisible = mutableStateOf(false) - val sortSheetState = mutableStateOf(SheetState(false, SheetValue.Hidden, { true }, false)) + val sortSheetState = mutableStateOf( + SheetState( + skipPartiallyExpanded = false, + velocityThreshold = { 0f }, + positionalThreshold = { 0f }, + initialValue = SheetValue.Hidden, + confirmValueChange = { true }, + skipHiddenState = false + ) + ) val browserId = mobileVault.repoFilesBrowsersCreate( source = source, diff --git a/vault-android/app/src/main/java/net/koofr/vault/features/reporemove/RepoRemoveScreen.kt b/vault-android/app/src/main/java/net/koofr/vault/features/reporemove/RepoRemoveScreen.kt index 8a1c69e5..c854b536 100644 --- a/vault-android/app/src/main/java/net/koofr/vault/features/reporemove/RepoRemoveScreen.kt +++ b/vault-android/app/src/main/java/net/koofr/vault/features/reporemove/RepoRemoveScreen.kt @@ -29,10 +29,10 @@ import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.fromHtml import androidx.compose.ui.text.input.TextFieldValue import androidx.compose.ui.unit.dp -import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.launch import net.koofr.vault.LocalSnackbarHostState diff --git a/vault-android/app/src/main/java/net/koofr/vault/features/repos/ReposScreen.kt b/vault-android/app/src/main/java/net/koofr/vault/features/repos/ReposScreen.kt index 6cf089c5..8ee446cf 100644 --- a/vault-android/app/src/main/java/net/koofr/vault/features/repos/ReposScreen.kt +++ b/vault-android/app/src/main/java/net/koofr/vault/features/repos/ReposScreen.kt @@ -28,8 +28,8 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp -import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.ViewModel +import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel import net.koofr.vault.LocalSnackbarHostState import net.koofr.vault.MobileVault diff --git a/vault-android/app/src/main/java/net/koofr/vault/features/repounlock/RepoUnlockScreen.kt b/vault-android/app/src/main/java/net/koofr/vault/features/repounlock/RepoUnlockScreen.kt index 782187ba..ef11f14c 100644 --- a/vault-android/app/src/main/java/net/koofr/vault/features/repounlock/RepoUnlockScreen.kt +++ b/vault-android/app/src/main/java/net/koofr/vault/features/repounlock/RepoUnlockScreen.kt @@ -24,17 +24,17 @@ import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.platform.LocalLifecycleOwner +import androidx.lifecycle.compose.LocalLifecycleOwner import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextOverflow import androidx.fragment.app.FragmentActivity -import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.DefaultLifecycleObserver import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewmodel.compose.viewModel +import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.qualifiers.ApplicationContext import kotlinx.coroutines.delay diff --git a/vault-android/app/src/main/java/net/koofr/vault/features/settings/InfoScreen.kt b/vault-android/app/src/main/java/net/koofr/vault/features/settings/InfoScreen.kt index 72833f03..2ce41e26 100644 --- a/vault-android/app/src/main/java/net/koofr/vault/features/settings/InfoScreen.kt +++ b/vault-android/app/src/main/java/net/koofr/vault/features/settings/InfoScreen.kt @@ -20,8 +20,8 @@ import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalUriHandler import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp -import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.ViewModel +import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel import net.koofr.vault.LocalSnackbarHostState import net.koofr.vault.MobileVault diff --git a/vault-android/app/src/main/java/net/koofr/vault/features/settings/SettingsScreen.kt b/vault-android/app/src/main/java/net/koofr/vault/features/settings/SettingsScreen.kt index 74e51f5c..8a3b5111 100644 --- a/vault-android/app/src/main/java/net/koofr/vault/features/settings/SettingsScreen.kt +++ b/vault-android/app/src/main/java/net/koofr/vault/features/settings/SettingsScreen.kt @@ -30,9 +30,9 @@ import androidx.compose.ui.semantics.Role import androidx.compose.ui.semantics.role import androidx.compose.ui.semantics.semantics import androidx.compose.ui.unit.dp -import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.qualifiers.ApplicationContext import kotlinx.coroutines.Dispatchers diff --git a/vault-android/app/src/main/java/net/koofr/vault/features/shareactivity/ShareActivityScreen.kt b/vault-android/app/src/main/java/net/koofr/vault/features/shareactivity/ShareActivityScreen.kt index 3adc3d62..697ed2b0 100644 --- a/vault-android/app/src/main/java/net/koofr/vault/features/shareactivity/ShareActivityScreen.kt +++ b/vault-android/app/src/main/java/net/koofr/vault/features/shareactivity/ShareActivityScreen.kt @@ -177,7 +177,7 @@ fun ShareActivityScreenDone(vm: ShareActivityViewModel) { ) LinearProgressIndicator( - progress.value / 100f, + { progress.intValue / 100f }, ) } } diff --git a/vault-android/app/src/main/java/net/koofr/vault/features/sharetarget/ShareTargetBottomBar.kt b/vault-android/app/src/main/java/net/koofr/vault/features/sharetarget/ShareTargetBottomBar.kt index 35f07077..09d700ef 100644 --- a/vault-android/app/src/main/java/net/koofr/vault/features/sharetarget/ShareTargetBottomBar.kt +++ b/vault-android/app/src/main/java/net/koofr/vault/features/sharetarget/ShareTargetBottomBar.kt @@ -17,7 +17,11 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.res.pluralStringResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.AnnotatedString +import androidx.compose.ui.text.LinkAnnotation import androidx.compose.ui.text.SpanStyle +import androidx.compose.ui.text.buildAnnotatedString +import androidx.compose.ui.text.withAnnotation +import androidx.compose.ui.text.withLink import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import net.koofr.vault.R @@ -40,19 +44,22 @@ fun ShareTargetBottomBar( .windowInsetsPadding(NavigationBarDefaults.windowInsets), ) { Box(modifier = Modifier.weight(1.0f)) { - ClickableText( - AnnotatedString( - pluralStringResource( - R.plurals.share_target_items_count_label, - vm.files.size, - vm.files.size, - ), - spanStyle = SpanStyle(MaterialTheme.colorScheme.onSurface), - ), + Text( + buildAnnotatedString { + withLink( + LinkAnnotation.Clickable("show-files-dialog") { + vm.showFilesDialog() + } + ) { + append(pluralStringResource( + R.plurals.share_target_items_count_label, + vm.files.size, + vm.files.size, + )) + } + }, modifier = Modifier.padding(15.dp, 5.dp), - ) { - vm.showFilesDialog() - } + ) } TextButton(onClick = { diff --git a/vault-android/app/src/main/java/net/koofr/vault/features/sharetarget/ShareTargetNavigation.kt b/vault-android/app/src/main/java/net/koofr/vault/features/sharetarget/ShareTargetNavigation.kt index 853e8026..84a50995 100644 --- a/vault-android/app/src/main/java/net/koofr/vault/features/sharetarget/ShareTargetNavigation.kt +++ b/vault-android/app/src/main/java/net/koofr/vault/features/sharetarget/ShareTargetNavigation.kt @@ -2,7 +2,7 @@ package net.koofr.vault.features.sharetarget import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider -import androidx.hilt.navigation.compose.hiltViewModel +import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel import androidx.navigation.NavType import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable diff --git a/vault-android/app/src/main/java/net/koofr/vault/features/transfers/TransferInfoView.kt b/vault-android/app/src/main/java/net/koofr/vault/features/transfers/TransferInfoView.kt index fed99f04..48732b91 100644 --- a/vault-android/app/src/main/java/net/koofr/vault/features/transfers/TransferInfoView.kt +++ b/vault-android/app/src/main/java/net/koofr/vault/features/transfers/TransferInfoView.kt @@ -38,7 +38,7 @@ fun TransferInfoView(transfer: Transfer, onRetry: () -> Unit) { else -> transfer.percentage.let { if (it != null) { LinearProgressIndicator( - it.toFloat() / 100, + { it.toFloat() / 100 }, modifier = Modifier .padding(bottom = 20.dp) .fillMaxWidth(), diff --git a/vault-android/app/src/main/java/net/koofr/vault/features/transfers/TransfersSummaryBottomBar.kt b/vault-android/app/src/main/java/net/koofr/vault/features/transfers/TransfersSummaryBottomBar.kt index 3acd9f43..d65ca8c1 100644 --- a/vault-android/app/src/main/java/net/koofr/vault/features/transfers/TransfersSummaryBottomBar.kt +++ b/vault-android/app/src/main/java/net/koofr/vault/features/transfers/TransfersSummaryBottomBar.kt @@ -74,7 +74,7 @@ fun TransfersSummaryBottomBar( } LinearProgressIndicator( - summary.percentage.toFloat() / 100, + { summary.percentage.toFloat() / 100 }, modifier = Modifier .padding(top = 15.dp) .fillMaxWidth(), diff --git a/vault-android/app/src/main/java/net/koofr/vault/features/transfers/TransfersView.kt b/vault-android/app/src/main/java/net/koofr/vault/features/transfers/TransfersView.kt index a31a0ad4..64fead0f 100644 --- a/vault-android/app/src/main/java/net/koofr/vault/features/transfers/TransfersView.kt +++ b/vault-android/app/src/main/java/net/koofr/vault/features/transfers/TransfersView.kt @@ -13,8 +13,8 @@ import androidx.compose.material3.TopAppBar import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource -import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.ViewModel +import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel import net.koofr.vault.FileIconProps import net.koofr.vault.FileIconSize diff --git a/vault-android/app/src/main/java/net/koofr/vault/features/uploads/TakePicture.kt b/vault-android/app/src/main/java/net/koofr/vault/features/uploads/TakePicture.kt index 2e3b6984..65c2e999 100644 --- a/vault-android/app/src/main/java/net/koofr/vault/features/uploads/TakePicture.kt +++ b/vault-android/app/src/main/java/net/koofr/vault/features/uploads/TakePicture.kt @@ -7,12 +7,12 @@ import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.runtime.Composable import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember -import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.ViewModel import com.google.accompanist.permissions.ExperimentalPermissionsApi import com.google.accompanist.permissions.isGranted import com.google.accompanist.permissions.rememberPermissionState import com.google.accompanist.permissions.shouldShowRationale +import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel import net.koofr.vault.features.permissions.CameraPermissionDialog import net.koofr.vault.features.storage.StorageHelper diff --git a/vault-android/app/src/main/java/net/koofr/vault/features/user/UserIcon.kt b/vault-android/app/src/main/java/net/koofr/vault/features/user/UserIcon.kt index a5be2576..1cd06c5e 100644 --- a/vault-android/app/src/main/java/net/koofr/vault/features/user/UserIcon.kt +++ b/vault-android/app/src/main/java/net/koofr/vault/features/user/UserIcon.kt @@ -23,8 +23,8 @@ import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.ViewModel +import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel import net.koofr.vault.MobileVault import net.koofr.vault.features.mobilevault.subscribe diff --git a/vault-android/gradle/libs.versions.toml b/vault-android/gradle/libs.versions.toml index 808bde80..19ffce47 100644 --- a/vault-android/gradle/libs.versions.toml +++ b/vault-android/gradle/libs.versions.toml @@ -1,24 +1,24 @@ [versions] # Plugins agp = "9.3.1" -kotlin = "2.2.10" +kotlin = "2.4.10" ksp = "2.3.4" hilt = "2.60.1" rustAndroid = "0.10.1" # AndroidX + Google -coreKtx = "1.12.0" -lifecycleRuntimeKtx = "2.6.2" -activityCompose = "1.7.2" -composeBom = "2023.10.00" -appcompat = "1.6.1" +coreKtx = "1.19.0" +lifecycleRuntimeKtx = "2.11.0" +activityCompose = "1.13.0" +composeBom = "2026.06.01" +appcompat = "1.7.1" biometric = "1.1.0" -browser = "1.6.0" -securityCrypto = "1.1.0-alpha06" -navigationCompose = "2.9.3" -hiltNavigationCompose = "1.2.0" +browser = "1.10.0" +securityCrypto = "1.1.0" +navigationCompose = "2.9.8" +hiltNavigationCompose = "1.4.0" accompanistPermissions = "0.37.3" -media3 = "1.8.0" +media3 = "1.11.0" # Coil coil = "2.7.0" @@ -27,16 +27,16 @@ coil = "2.7.0" photoView = "565505d5cb" # JNA -jna = "5.17.0" +jna = "5.19.1" # KotlinX -kotlinxSerializationJson = "1.7.3" +kotlinxSerializationJson = "1.11.0" # Testing junit = "4.13.2" -androidxJunit = "1.1.5" -espresso = "3.5.1" -uiautomator = "2.3.0-alpha04" +androidxJunit = "1.3.0" +espresso = "3.7.0" +uiautomator = "2.4.0" [libraries] # AndroidX From d98409bf28fd45c73aefc957befa7d770b4c5ce8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaka=20Mo=C4=8Dnik?= Date: Mon, 10 Aug 2026 16:28:25 +0200 Subject: [PATCH 5/6] Import beautification. --- vault-android/app/build.gradle.kts | 4 +--- .../androidTest/java/net/koofr/vault/tests/helpers/Fixture.kt | 1 - .../main/java/net/koofr/vault/composables/RefreshableList.kt | 2 +- .../src/main/java/net/koofr/vault/features/intl/IntlHelper.kt | 1 - .../java/net/koofr/vault/features/landing/LandingScreen.kt | 4 ++-- .../net/koofr/vault/features/mainnavigation/MainNavigation.kt | 2 +- .../java/net/koofr/vault/features/mobilevault/Subscribe.kt | 2 +- .../koofr/vault/features/notifications/NotificationHandler.kt | 2 +- .../remotefilesdirpicker/RemoteFilesDirPickerScreen.kt | 2 +- .../main/java/net/koofr/vault/features/repo/RepoInfoScreen.kt | 2 +- .../vault/features/repo/RepoSetupBiometricUnlockDialog.kt | 2 +- .../java/net/koofr/vault/features/repo/UnlockedRepoWrapper.kt | 2 +- .../net/koofr/vault/features/repocreate/RepoCreateScreen.kt | 2 +- .../vault/features/repofiles/RepoFilesScreenViewModel.kt | 2 -- .../net/koofr/vault/features/reporemove/RepoRemoveScreen.kt | 2 +- .../main/java/net/koofr/vault/features/repos/ReposScreen.kt | 2 +- .../net/koofr/vault/features/repounlock/RepoUnlockScreen.kt | 4 ++-- .../main/java/net/koofr/vault/features/settings/InfoScreen.kt | 2 +- .../java/net/koofr/vault/features/settings/SettingsScreen.kt | 2 +- .../koofr/vault/features/sharetarget/ShareTargetBottomBar.kt | 4 ---- .../java/net/koofr/vault/features/transfers/TransfersView.kt | 2 +- .../main/java/net/koofr/vault/features/uploads/TakePicture.kt | 2 +- .../src/main/java/net/koofr/vault/features/user/UserIcon.kt | 2 +- 23 files changed, 21 insertions(+), 31 deletions(-) diff --git a/vault-android/app/build.gradle.kts b/vault-android/app/build.gradle.kts index 1a8a58a0..f8a854aa 100644 --- a/vault-android/app/build.gradle.kts +++ b/vault-android/app/build.gradle.kts @@ -1,7 +1,5 @@ -import com.android.build.gradle.internal.cxx.configure.gradleLocalProperties import com.android.build.api.dsl.ApplicationExtension -import net.mullvad.androidrust.android -import org.gradle.kotlin.dsl.android +import com.android.build.gradle.internal.cxx.configure.gradleLocalProperties import org.jetbrains.kotlin.gradle.dsl.JvmTarget import java.io.ByteArrayOutputStream diff --git a/vault-android/app/src/androidTest/java/net/koofr/vault/tests/helpers/Fixture.kt b/vault-android/app/src/androidTest/java/net/koofr/vault/tests/helpers/Fixture.kt index 3762d95d..70f4cc9c 100644 --- a/vault-android/app/src/androidTest/java/net/koofr/vault/tests/helpers/Fixture.kt +++ b/vault-android/app/src/androidTest/java/net/koofr/vault/tests/helpers/Fixture.kt @@ -12,7 +12,6 @@ import net.koofr.vault.IntlOwnership import net.koofr.vault.MobileVault import org.json.JSONObject import java.io.Closeable -import kotlin.collections.iterator class Fixture constructor( val fakeRemote: FakeRemote, diff --git a/vault-android/app/src/main/java/net/koofr/vault/composables/RefreshableList.kt b/vault-android/app/src/main/java/net/koofr/vault/composables/RefreshableList.kt index 9a4731a9..b80dec3f 100644 --- a/vault-android/app/src/main/java/net/koofr/vault/composables/RefreshableList.kt +++ b/vault-android/app/src/main/java/net/koofr/vault/composables/RefreshableList.kt @@ -7,8 +7,8 @@ import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyListScope import androidx.compose.material3.MaterialTheme import androidx.compose.material3.pulltorefresh.PullToRefreshBox -import androidx.compose.material3.pulltorefresh.rememberPullToRefreshState import androidx.compose.material3.pulltorefresh.PullToRefreshDefaults.Indicator +import androidx.compose.material3.pulltorefresh.rememberPullToRefreshState import androidx.compose.runtime.Composable import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember diff --git a/vault-android/app/src/main/java/net/koofr/vault/features/intl/IntlHelper.kt b/vault-android/app/src/main/java/net/koofr/vault/features/intl/IntlHelper.kt index 907a6ef7..d06a7c48 100644 --- a/vault-android/app/src/main/java/net/koofr/vault/features/intl/IntlHelper.kt +++ b/vault-android/app/src/main/java/net/koofr/vault/features/intl/IntlHelper.kt @@ -1,7 +1,6 @@ package net.koofr.vault.features.intl import android.content.res.Configuration -import android.util.Log import androidx.appcompat.app.AppCompatDelegate import androidx.core.os.LocaleListCompat import dagger.Module diff --git a/vault-android/app/src/main/java/net/koofr/vault/features/landing/LandingScreen.kt b/vault-android/app/src/main/java/net/koofr/vault/features/landing/LandingScreen.kt index 2d613538..5388d019 100644 --- a/vault-android/app/src/main/java/net/koofr/vault/features/landing/LandingScreen.kt +++ b/vault-android/app/src/main/java/net/koofr/vault/features/landing/LandingScreen.kt @@ -36,9 +36,9 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import dagger.hilt.android.lifecycle.HiltViewModel -import androidx.lifecycle.ViewModel import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel +import androidx.lifecycle.ViewModel +import dagger.hilt.android.lifecycle.HiltViewModel import net.koofr.vault.LocalSnackbarHostState import net.koofr.vault.MobileVault import net.koofr.vault.R diff --git a/vault-android/app/src/main/java/net/koofr/vault/features/mainnavigation/MainNavigation.kt b/vault-android/app/src/main/java/net/koofr/vault/features/mainnavigation/MainNavigation.kt index e2a4c495..4eb9ab69 100644 --- a/vault-android/app/src/main/java/net/koofr/vault/features/mainnavigation/MainNavigation.kt +++ b/vault-android/app/src/main/java/net/koofr/vault/features/mainnavigation/MainNavigation.kt @@ -3,8 +3,8 @@ package net.koofr.vault.features.mainnavigation import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.remember -import androidx.lifecycle.viewmodel.compose.viewModel import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel +import androidx.lifecycle.viewmodel.compose.viewModel import androidx.navigation.NavType import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable diff --git a/vault-android/app/src/main/java/net/koofr/vault/features/mobilevault/Subscribe.kt b/vault-android/app/src/main/java/net/koofr/vault/features/mobilevault/Subscribe.kt index 8f241255..7b7cfd75 100644 --- a/vault-android/app/src/main/java/net/koofr/vault/features/mobilevault/Subscribe.kt +++ b/vault-android/app/src/main/java/net/koofr/vault/features/mobilevault/Subscribe.kt @@ -5,8 +5,8 @@ import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.State import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope -import androidx.lifecycle.ViewModel import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel +import androidx.lifecycle.ViewModel import dagger.hilt.android.lifecycle.HiltViewModel import net.koofr.vault.MobileVault import net.koofr.vault.SubscriptionCallback diff --git a/vault-android/app/src/main/java/net/koofr/vault/features/notifications/NotificationHandler.kt b/vault-android/app/src/main/java/net/koofr/vault/features/notifications/NotificationHandler.kt index d7a93ff9..5cd59ccc 100644 --- a/vault-android/app/src/main/java/net/koofr/vault/features/notifications/NotificationHandler.kt +++ b/vault-android/app/src/main/java/net/koofr/vault/features/notifications/NotificationHandler.kt @@ -3,8 +3,8 @@ package net.koofr.vault.features.notifications import android.util.Log import androidx.compose.runtime.Composable import androidx.compose.runtime.rememberCoroutineScope -import androidx.lifecycle.ViewModel import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel +import androidx.lifecycle.ViewModel import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.launch import net.koofr.vault.LocalSnackbarHostState diff --git a/vault-android/app/src/main/java/net/koofr/vault/features/remotefilesdirpicker/RemoteFilesDirPickerScreen.kt b/vault-android/app/src/main/java/net/koofr/vault/features/remotefilesdirpicker/RemoteFilesDirPickerScreen.kt index 8e910135..4963e341 100644 --- a/vault-android/app/src/main/java/net/koofr/vault/features/remotefilesdirpicker/RemoteFilesDirPickerScreen.kt +++ b/vault-android/app/src/main/java/net/koofr/vault/features/remotefilesdirpicker/RemoteFilesDirPickerScreen.kt @@ -31,9 +31,9 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel -import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.launch import net.koofr.vault.LocalSnackbarHostState diff --git a/vault-android/app/src/main/java/net/koofr/vault/features/repo/RepoInfoScreen.kt b/vault-android/app/src/main/java/net/koofr/vault/features/repo/RepoInfoScreen.kt index ee299544..b89bcf56 100644 --- a/vault-android/app/src/main/java/net/koofr/vault/features/repo/RepoInfoScreen.kt +++ b/vault-android/app/src/main/java/net/koofr/vault/features/repo/RepoInfoScreen.kt @@ -35,10 +35,10 @@ import androidx.compose.ui.semantics.contentDescription import androidx.compose.ui.semantics.semantics import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp +import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelStore -import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.qualifiers.ApplicationContext import net.koofr.vault.LocalSnackbarHostState diff --git a/vault-android/app/src/main/java/net/koofr/vault/features/repo/RepoSetupBiometricUnlockDialog.kt b/vault-android/app/src/main/java/net/koofr/vault/features/repo/RepoSetupBiometricUnlockDialog.kt index 05664267..6e46c050 100644 --- a/vault-android/app/src/main/java/net/koofr/vault/features/repo/RepoSetupBiometricUnlockDialog.kt +++ b/vault-android/app/src/main/java/net/koofr/vault/features/repo/RepoSetupBiometricUnlockDialog.kt @@ -20,11 +20,11 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.compose.ui.window.Dialog import androidx.fragment.app.FragmentActivity +import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewmodel.compose.viewModel -import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.qualifiers.ApplicationContext import kotlinx.coroutines.launch diff --git a/vault-android/app/src/main/java/net/koofr/vault/features/repo/UnlockedRepoWrapper.kt b/vault-android/app/src/main/java/net/koofr/vault/features/repo/UnlockedRepoWrapper.kt index 1fab7644..ece84d6c 100644 --- a/vault-android/app/src/main/java/net/koofr/vault/features/repo/UnlockedRepoWrapper.kt +++ b/vault-android/app/src/main/java/net/koofr/vault/features/repo/UnlockedRepoWrapper.kt @@ -8,8 +8,8 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.input.pointer.PointerEventTimeoutCancellationException import androidx.compose.ui.input.pointer.pointerInput -import androidx.lifecycle.ViewModel import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel +import androidx.lifecycle.ViewModel import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.coroutineScope import net.koofr.vault.MobileVault diff --git a/vault-android/app/src/main/java/net/koofr/vault/features/repocreate/RepoCreateScreen.kt b/vault-android/app/src/main/java/net/koofr/vault/features/repocreate/RepoCreateScreen.kt index 799a2a3e..b5d0f688 100644 --- a/vault-android/app/src/main/java/net/koofr/vault/features/repocreate/RepoCreateScreen.kt +++ b/vault-android/app/src/main/java/net/koofr/vault/features/repocreate/RepoCreateScreen.kt @@ -9,8 +9,8 @@ import androidx.compose.material3.TopAppBar import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource -import net.koofr.vault.LocalSnackbarHostState import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel +import net.koofr.vault.LocalSnackbarHostState import net.koofr.vault.R import net.koofr.vault.RepoCreateInfo import net.koofr.vault.Status diff --git a/vault-android/app/src/main/java/net/koofr/vault/features/repofiles/RepoFilesScreenViewModel.kt b/vault-android/app/src/main/java/net/koofr/vault/features/repofiles/RepoFilesScreenViewModel.kt index 5246cea3..8a99d54b 100644 --- a/vault-android/app/src/main/java/net/koofr/vault/features/repofiles/RepoFilesScreenViewModel.kt +++ b/vault-android/app/src/main/java/net/koofr/vault/features/repofiles/RepoFilesScreenViewModel.kt @@ -2,9 +2,7 @@ package net.koofr.vault.features.repofiles import android.content.Context import android.content.Intent -import androidx.compose.material3.BottomSheetDefaults import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.ModalBottomSheetDefaults import androidx.compose.material3.SheetState import androidx.compose.material3.SheetValue import androidx.compose.runtime.mutableStateOf diff --git a/vault-android/app/src/main/java/net/koofr/vault/features/reporemove/RepoRemoveScreen.kt b/vault-android/app/src/main/java/net/koofr/vault/features/reporemove/RepoRemoveScreen.kt index c854b536..dee4fe72 100644 --- a/vault-android/app/src/main/java/net/koofr/vault/features/reporemove/RepoRemoveScreen.kt +++ b/vault-android/app/src/main/java/net/koofr/vault/features/reporemove/RepoRemoveScreen.kt @@ -29,10 +29,10 @@ import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.fromHtml import androidx.compose.ui.text.input.TextFieldValue import androidx.compose.ui.unit.dp +import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.launch import net.koofr.vault.LocalSnackbarHostState diff --git a/vault-android/app/src/main/java/net/koofr/vault/features/repos/ReposScreen.kt b/vault-android/app/src/main/java/net/koofr/vault/features/repos/ReposScreen.kt index 8ee446cf..196bda27 100644 --- a/vault-android/app/src/main/java/net/koofr/vault/features/repos/ReposScreen.kt +++ b/vault-android/app/src/main/java/net/koofr/vault/features/repos/ReposScreen.kt @@ -28,8 +28,8 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp -import androidx.lifecycle.ViewModel import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel +import androidx.lifecycle.ViewModel import dagger.hilt.android.lifecycle.HiltViewModel import net.koofr.vault.LocalSnackbarHostState import net.koofr.vault.MobileVault diff --git a/vault-android/app/src/main/java/net/koofr/vault/features/repounlock/RepoUnlockScreen.kt b/vault-android/app/src/main/java/net/koofr/vault/features/repounlock/RepoUnlockScreen.kt index ef11f14c..31cd2fa2 100644 --- a/vault-android/app/src/main/java/net/koofr/vault/features/repounlock/RepoUnlockScreen.kt +++ b/vault-android/app/src/main/java/net/koofr/vault/features/repounlock/RepoUnlockScreen.kt @@ -24,17 +24,17 @@ import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext -import androidx.lifecycle.compose.LocalLifecycleOwner import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextOverflow import androidx.fragment.app.FragmentActivity +import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel import androidx.lifecycle.DefaultLifecycleObserver import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel +import androidx.lifecycle.compose.LocalLifecycleOwner import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewmodel.compose.viewModel -import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.qualifiers.ApplicationContext import kotlinx.coroutines.delay diff --git a/vault-android/app/src/main/java/net/koofr/vault/features/settings/InfoScreen.kt b/vault-android/app/src/main/java/net/koofr/vault/features/settings/InfoScreen.kt index 2ce41e26..87cf64ef 100644 --- a/vault-android/app/src/main/java/net/koofr/vault/features/settings/InfoScreen.kt +++ b/vault-android/app/src/main/java/net/koofr/vault/features/settings/InfoScreen.kt @@ -20,8 +20,8 @@ import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalUriHandler import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp -import androidx.lifecycle.ViewModel import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel +import androidx.lifecycle.ViewModel import dagger.hilt.android.lifecycle.HiltViewModel import net.koofr.vault.LocalSnackbarHostState import net.koofr.vault.MobileVault diff --git a/vault-android/app/src/main/java/net/koofr/vault/features/settings/SettingsScreen.kt b/vault-android/app/src/main/java/net/koofr/vault/features/settings/SettingsScreen.kt index 8a3b5111..a9c4def0 100644 --- a/vault-android/app/src/main/java/net/koofr/vault/features/settings/SettingsScreen.kt +++ b/vault-android/app/src/main/java/net/koofr/vault/features/settings/SettingsScreen.kt @@ -30,9 +30,9 @@ import androidx.compose.ui.semantics.Role import androidx.compose.ui.semantics.role import androidx.compose.ui.semantics.semantics import androidx.compose.ui.unit.dp +import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.qualifiers.ApplicationContext import kotlinx.coroutines.Dispatchers diff --git a/vault-android/app/src/main/java/net/koofr/vault/features/sharetarget/ShareTargetBottomBar.kt b/vault-android/app/src/main/java/net/koofr/vault/features/sharetarget/ShareTargetBottomBar.kt index 09d700ef..0ca0e498 100644 --- a/vault-android/app/src/main/java/net/koofr/vault/features/sharetarget/ShareTargetBottomBar.kt +++ b/vault-android/app/src/main/java/net/koofr/vault/features/sharetarget/ShareTargetBottomBar.kt @@ -5,7 +5,6 @@ import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.windowInsetsPadding -import androidx.compose.foundation.text.ClickableText import androidx.compose.material3.MaterialTheme import androidx.compose.material3.NavigationBarDefaults import androidx.compose.material3.Surface @@ -16,11 +15,8 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.pluralStringResource import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.LinkAnnotation -import androidx.compose.ui.text.SpanStyle import androidx.compose.ui.text.buildAnnotatedString -import androidx.compose.ui.text.withAnnotation import androidx.compose.ui.text.withLink import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp diff --git a/vault-android/app/src/main/java/net/koofr/vault/features/transfers/TransfersView.kt b/vault-android/app/src/main/java/net/koofr/vault/features/transfers/TransfersView.kt index 64fead0f..321f5f3b 100644 --- a/vault-android/app/src/main/java/net/koofr/vault/features/transfers/TransfersView.kt +++ b/vault-android/app/src/main/java/net/koofr/vault/features/transfers/TransfersView.kt @@ -13,8 +13,8 @@ import androidx.compose.material3.TopAppBar import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource -import androidx.lifecycle.ViewModel import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel +import androidx.lifecycle.ViewModel import dagger.hilt.android.lifecycle.HiltViewModel import net.koofr.vault.FileIconProps import net.koofr.vault.FileIconSize diff --git a/vault-android/app/src/main/java/net/koofr/vault/features/uploads/TakePicture.kt b/vault-android/app/src/main/java/net/koofr/vault/features/uploads/TakePicture.kt index 65c2e999..483d74bb 100644 --- a/vault-android/app/src/main/java/net/koofr/vault/features/uploads/TakePicture.kt +++ b/vault-android/app/src/main/java/net/koofr/vault/features/uploads/TakePicture.kt @@ -7,12 +7,12 @@ import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.runtime.Composable import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel import androidx.lifecycle.ViewModel import com.google.accompanist.permissions.ExperimentalPermissionsApi import com.google.accompanist.permissions.isGranted import com.google.accompanist.permissions.rememberPermissionState import com.google.accompanist.permissions.shouldShowRationale -import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel import net.koofr.vault.features.permissions.CameraPermissionDialog import net.koofr.vault.features.storage.StorageHelper diff --git a/vault-android/app/src/main/java/net/koofr/vault/features/user/UserIcon.kt b/vault-android/app/src/main/java/net/koofr/vault/features/user/UserIcon.kt index 1cd06c5e..186e3081 100644 --- a/vault-android/app/src/main/java/net/koofr/vault/features/user/UserIcon.kt +++ b/vault-android/app/src/main/java/net/koofr/vault/features/user/UserIcon.kt @@ -23,8 +23,8 @@ import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import androidx.lifecycle.ViewModel import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel +import androidx.lifecycle.ViewModel import dagger.hilt.android.lifecycle.HiltViewModel import net.koofr.vault.MobileVault import net.koofr.vault.features.mobilevault.subscribe From 48ae54617861e4c328879e6c4c9293205380fd7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaka=20Mo=C4=8Dnik?= Date: Tue, 11 Aug 2026 15:00:16 +0200 Subject: [PATCH 6/6] Fixed a race condition with subsequent changes of selected files. --- vault-desktop-server/src/handlers.rs | 21 +++++++------ vault-mobile/src/lib.rs | 46 +++++++++++++--------------- 2 files changed, 33 insertions(+), 34 deletions(-) diff --git a/vault-desktop-server/src/handlers.rs b/vault-desktop-server/src/handlers.rs index e3033af8..c4406d8d 100644 --- a/vault-desktop-server/src/handlers.rs +++ b/vault-desktop-server/src/handlers.rs @@ -1745,17 +1745,18 @@ pub async fn repo_files_browsers_download_selected( ) { match state.file_handlers.save_file.clone() { Some(save_file) => { - base.clone().spawn(move |vault| { + let reader_provider = match base + .vault + .repo_files_browsers_get_selected_reader(browser_id) + { + Ok(reader_provider) => reader_provider, + Err(err) => { + base.errors.handle_error(err); + return; + } + }; + base.clone().spawn(move |_| { async move { - let reader_provider = - match vault.repo_files_browsers_get_selected_reader(browser_id) { - Ok(reader_provider) => reader_provider, - Err(err) => { - base.errors.handle_error(err); - return; - } - }; - transfers_download_reader_provider_pick_file(base, reader_provider, save_file) .await; } diff --git a/vault-mobile/src/lib.rs b/vault-mobile/src/lib.rs index 4ac0a3f5..7bb449da 100644 --- a/vault-mobile/src/lib.rs +++ b/vault-mobile/src/lib.rs @@ -3434,19 +3434,18 @@ impl MobileVault { on_open: Option>, on_done: Box, ) { + let reader_provider = match self + .vault + .clone() + .repo_files_browsers_get_selected_reader(browser_id) + { + Ok(reader_provider) => reader_provider, + Err(err) => { + self.errors.handle_error(err); + return; + } + }; self.clone().spawn(async move { - let reader_provider = match self - .vault - .clone() - .repo_files_browsers_get_selected_reader(browser_id) - { - Ok(reader_provider) => reader_provider, - Err(err) => { - self.errors.handle_error(err); - return; - } - }; - let downloadable = Box::new(FileDownloadable { original_path: local_file_path.into(), append_name, @@ -3467,19 +3466,18 @@ impl MobileVault { browser_id: u32, stream_provider: Box, ) { + let reader_provider = match self + .vault + .clone() + .repo_files_browsers_get_selected_reader(browser_id) + { + Ok(reader_provider) => reader_provider, + Err(err) => { + self.errors.handle_error(err); + return; + } + }; self.clone().spawn(async move { - let reader_provider = match self - .vault - .clone() - .repo_files_browsers_get_selected_reader(browser_id) - { - Ok(reader_provider) => reader_provider, - Err(err) => { - self.errors.handle_error(err); - return; - } - }; - let downloadable = Box::new(StreamDownloadable { stream_provider: Arc::new(stream_provider), tokio_runtime: self.tokio_runtime.clone(),