mirror of
https://github.com/permissionlesstech/bitchat-android.git
synced 2026-11-08 08:01:09 +00:00
Merge remote-tracking branch 'origin/main' into claude/ios-features-android-port-ebe483
# Conflicts: # app/src/main/java/com/bitchat/android/ui/ChatViewModel.kt
This commit is contained in:
commit
57bb99c170
@ -6,6 +6,9 @@ plugins {
|
||||
alias(libs.plugins.kotlin.compose)
|
||||
}
|
||||
|
||||
// Opt-in instrumentation of the minified variant; production release signing is unchanged.
|
||||
val releaseUiTests = providers.gradleProperty("releaseUiTests").orNull == "true"
|
||||
|
||||
val githubReleaseCertSha256 = providers
|
||||
.environmentVariable("BITCHAT_GITHUB_RELEASE_CERT_SHA256")
|
||||
.orElse(providers.gradleProperty("BITCHAT_GITHUB_RELEASE_CERT_SHA256"))
|
||||
@ -25,6 +28,7 @@ android {
|
||||
namespace = "com.bitchat.android"
|
||||
compileSdk = libs.versions.compileSdk.get().toInt()
|
||||
buildToolsVersion = libs.versions.buildTools.get()
|
||||
if (releaseUiTests) testBuildType = "release"
|
||||
|
||||
defaultConfig {
|
||||
applicationId = "com.bitchat.droid"
|
||||
@ -38,7 +42,11 @@ android {
|
||||
"\"$normalizedGithubReleaseCertSha256\""
|
||||
)
|
||||
|
||||
testInstrumentationRunner = "com.bitchat.android.ui.PerformanceTestRunner"
|
||||
testInstrumentationRunner = if (releaseUiTests) {
|
||||
"com.bitchat.android.ui.ReleaseScannerTestRunner"
|
||||
} else {
|
||||
"com.bitchat.android.ui.PerformanceTestRunner"
|
||||
}
|
||||
vectorDrawables {
|
||||
useSupportLibrary = true
|
||||
}
|
||||
@ -59,6 +67,10 @@ android {
|
||||
}
|
||||
}
|
||||
release {
|
||||
if (releaseUiTests) {
|
||||
signingConfig = signingConfigs.getByName("debug")
|
||||
proguardFiles("release-instrumentation.pro")
|
||||
}
|
||||
isMinifyEnabled = true
|
||||
isShrinkResources = true
|
||||
proguardFiles(
|
||||
@ -192,6 +204,8 @@ dependencies {
|
||||
|
||||
// Testing
|
||||
testImplementation(libs.bundles.testing)
|
||||
testImplementation(platform(libs.androidx.compose.bom))
|
||||
testImplementation(libs.androidx.compose.ui.test.junit4)
|
||||
androidTestImplementation(platform(libs.androidx.compose.bom))
|
||||
androidTestImplementation(libs.androidx.compose.ui.test.junit4)
|
||||
androidTestImplementation(libs.androidx.test.runner)
|
||||
|
||||
@ -2,178 +2,178 @@
|
||||
# Manual edits can break the build and are not advised.
|
||||
# This file is expected to be part of source control.
|
||||
# To regenerate this file, run: ./gradlew :app:dependencies --write-locks
|
||||
androidx.activity:activity-compose:1.13.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.activity:activity-ktx:1.13.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.activity:activity:1.13.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.annotation:annotation-experimental:1.4.1=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.annotation:annotation-jvm:1.10.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.annotation:annotation:1.10.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.appcompat:appcompat-resources:1.7.1=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.appcompat:appcompat:1.7.1=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.arch.core:core-common:2.2.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.arch.core:core-runtime:2.2.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.autofill:autofill:1.0.0=debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.activity:activity-compose:1.13.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestCompileClasspath,releaseAndroidTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.activity:activity-ktx:1.13.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestCompileClasspath,releaseAndroidTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.activity:activity:1.13.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestCompileClasspath,releaseAndroidTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.annotation:annotation-experimental:1.4.1=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestCompileClasspath,releaseAndroidTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.annotation:annotation-jvm:1.10.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestCompileClasspath,releaseAndroidTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.annotation:annotation:1.10.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestCompileClasspath,releaseAndroidTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.appcompat:appcompat-resources:1.7.1=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestCompileClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.appcompat:appcompat:1.7.1=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestCompileClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.arch.core:core-common:2.2.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestCompileClasspath,releaseAndroidTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.arch.core:core-runtime:2.2.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestCompileClasspath,releaseAndroidTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.autofill:autofill:1.0.0=debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.camera.featurecombinationquery:featurecombinationquery:1.6.1=debugAndroidTestLintChecksClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.camera.viewfinder:viewfinder-compose:1.6.1=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.camera.viewfinder:viewfinder-core:1.6.1=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.camera.viewfinder:viewfinder-compose:1.6.1=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestCompileClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.camera.viewfinder:viewfinder-core:1.6.1=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestCompileClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.camera:camera-camera2-pipe:1.6.1=debugAndroidTestLintChecksClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.camera:camera-camera2:1.6.1=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.camera:camera-compose:1.6.1=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.camera:camera-core:1.6.1=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.camera:camera-lifecycle:1.6.1=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.collection:collection-jvm:1.5.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.collection:collection-ktx:1.5.0=debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.collection:collection:1.5.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.compose.animation:animation-android:1.11.4=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.compose.animation:animation-core-android:1.11.4=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.compose.animation:animation-core:1.11.4=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.compose.animation:animation:1.11.4=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.compose.foundation:foundation-android:1.11.4=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.compose.foundation:foundation-layout-android:1.11.4=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.compose.foundation:foundation-layout:1.11.4=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.compose.foundation:foundation:1.11.4=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.compose.material3:material3-android:1.4.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.compose.material3:material3:1.4.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.compose.material:material-icons-core-android:1.7.8=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath
|
||||
androidx.camera:camera-camera2:1.6.1=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestCompileClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.camera:camera-compose:1.6.1=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestCompileClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.camera:camera-core:1.6.1=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestCompileClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.camera:camera-lifecycle:1.6.1=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestCompileClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.collection:collection-jvm:1.5.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestCompileClasspath,releaseAndroidTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.collection:collection-ktx:1.5.0=debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.collection:collection:1.5.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestCompileClasspath,releaseAndroidTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.compose.animation:animation-android:1.11.4=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestCompileClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.compose.animation:animation-core-android:1.11.4=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestCompileClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.compose.animation:animation-core:1.11.4=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestCompileClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.compose.animation:animation:1.11.4=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestCompileClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.compose.foundation:foundation-android:1.11.4=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestCompileClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.compose.foundation:foundation-layout-android:1.11.4=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestCompileClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.compose.foundation:foundation-layout:1.11.4=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestCompileClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.compose.foundation:foundation:1.11.4=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestCompileClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.compose.material3:material3-android:1.4.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestCompileClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.compose.material3:material3:1.4.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestCompileClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.compose.material:material-icons-core-android:1.7.8=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestCompileClasspath,releaseCompileClasspath,releaseRuntimeClasspath
|
||||
androidx.compose.material:material-icons-core-desktop:1.7.8=debugAndroidTestLintChecksClasspath,debugLintChecksClasspath,debugUnitTestLintChecksClasspath,releaseLintChecksClasspath
|
||||
androidx.compose.material:material-icons-core:1.7.8=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.compose.material:material-icons-extended-android:1.7.8=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath
|
||||
androidx.compose.material:material-icons-core:1.7.8=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestCompileClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.compose.material:material-icons-extended-android:1.7.8=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestCompileClasspath,releaseCompileClasspath,releaseRuntimeClasspath
|
||||
androidx.compose.material:material-icons-extended-desktop:1.7.8=debugAndroidTestLintChecksClasspath,debugLintChecksClasspath,debugUnitTestLintChecksClasspath,releaseLintChecksClasspath
|
||||
androidx.compose.material:material-icons-extended:1.7.8=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.compose.material:material-ripple-android:1.11.4=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.compose.material:material-ripple:1.11.4=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.compose.runtime:runtime-android:1.11.4=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.compose.runtime:runtime-annotation-android:1.11.4=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.compose.runtime:runtime-annotation:1.11.4=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.compose.runtime:runtime-retain-android:1.11.4=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.compose.runtime:runtime-retain:1.11.4=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.compose.runtime:runtime-saveable-android:1.11.4=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.compose.runtime:runtime-saveable:1.11.4=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.compose.runtime:runtime:1.11.4=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.compose.ui:ui-android:1.11.4=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.compose.ui:ui-geometry-android:1.11.4=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.compose.ui:ui-geometry:1.11.4=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.compose.ui:ui-graphics-android:1.11.4=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.compose.ui:ui-graphics:1.11.4=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.compose.ui:ui-test-android:1.11.4=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath
|
||||
androidx.compose.ui:ui-test-junit4-android:1.11.4=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath
|
||||
androidx.compose.ui:ui-test-junit4:1.11.4=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath
|
||||
androidx.compose.ui:ui-test-manifest:1.11.4=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath
|
||||
androidx.compose.ui:ui-test:1.11.4=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath
|
||||
androidx.compose.ui:ui-text-android:1.11.4=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.compose.ui:ui-text:1.11.4=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.compose.material:material-icons-extended:1.7.8=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestCompileClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.compose.material:material-ripple-android:1.11.4=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestCompileClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.compose.material:material-ripple:1.11.4=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestCompileClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.compose.runtime:runtime-android:1.11.4=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestCompileClasspath,releaseAndroidTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.compose.runtime:runtime-annotation-android:1.11.4=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestCompileClasspath,releaseAndroidTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.compose.runtime:runtime-annotation:1.11.4=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestCompileClasspath,releaseAndroidTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.compose.runtime:runtime-retain-android:1.11.4=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestCompileClasspath,releaseAndroidTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.compose.runtime:runtime-retain:1.11.4=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestCompileClasspath,releaseAndroidTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.compose.runtime:runtime-saveable-android:1.11.4=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestCompileClasspath,releaseAndroidTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.compose.runtime:runtime-saveable:1.11.4=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestCompileClasspath,releaseAndroidTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.compose.runtime:runtime:1.11.4=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestCompileClasspath,releaseAndroidTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.compose.ui:ui-android:1.11.4=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestCompileClasspath,releaseAndroidTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.compose.ui:ui-geometry-android:1.11.4=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestCompileClasspath,releaseAndroidTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.compose.ui:ui-geometry:1.11.4=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestCompileClasspath,releaseAndroidTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.compose.ui:ui-graphics-android:1.11.4=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestCompileClasspath,releaseAndroidTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.compose.ui:ui-graphics:1.11.4=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestCompileClasspath,releaseAndroidTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.compose.ui:ui-test-android:1.11.4=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestCompileClasspath,releaseAndroidTestRuntimeClasspath
|
||||
androidx.compose.ui:ui-test-junit4-android:1.11.4=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestCompileClasspath,releaseAndroidTestRuntimeClasspath
|
||||
androidx.compose.ui:ui-test-junit4:1.11.4=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestCompileClasspath,releaseAndroidTestRuntimeClasspath
|
||||
androidx.compose.ui:ui-test-manifest:1.11.4=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath
|
||||
androidx.compose.ui:ui-test:1.11.4=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestCompileClasspath,releaseAndroidTestRuntimeClasspath
|
||||
androidx.compose.ui:ui-text-android:1.11.4=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestCompileClasspath,releaseAndroidTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.compose.ui:ui-text:1.11.4=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestCompileClasspath,releaseAndroidTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.compose.ui:ui-tooling-android:1.11.4=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath
|
||||
androidx.compose.ui:ui-tooling-data-android:1.11.4=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath
|
||||
androidx.compose.ui:ui-tooling-data:1.11.4=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath
|
||||
androidx.compose.ui:ui-tooling-preview-android:1.11.4=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.compose.ui:ui-tooling-preview:1.11.4=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.compose.ui:ui-tooling-preview-android:1.11.4=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestCompileClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.compose.ui:ui-tooling-preview:1.11.4=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestCompileClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.compose.ui:ui-tooling:1.11.4=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath
|
||||
androidx.compose.ui:ui-unit-android:1.11.4=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.compose.ui:ui-unit:1.11.4=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.compose.ui:ui-util-android:1.11.4=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.compose.ui:ui-util:1.11.4=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.compose.ui:ui:1.11.4=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.compose:compose-bom:2026.06.01=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.compose.ui:ui-unit-android:1.11.4=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestCompileClasspath,releaseAndroidTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.compose.ui:ui-unit:1.11.4=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestCompileClasspath,releaseAndroidTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.compose.ui:ui-util-android:1.11.4=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestCompileClasspath,releaseAndroidTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.compose.ui:ui-util:1.11.4=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestCompileClasspath,releaseAndroidTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.compose.ui:ui:1.11.4=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestCompileClasspath,releaseAndroidTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.compose:compose-bom:2026.06.01=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestCompileClasspath,releaseAndroidTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.concurrent:concurrent-futures-ktx:1.1.0=debugAndroidTestLintChecksClasspath,debugLintChecksClasspath,debugRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.concurrent:concurrent-futures-ktx:1.2.0=debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath
|
||||
androidx.concurrent:concurrent-futures:1.1.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugLintChecksClasspath,debugRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.concurrent:concurrent-futures:1.1.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugLintChecksClasspath,debugRuntimeClasspath,releaseAndroidTestCompileClasspath,releaseAndroidTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.concurrent:concurrent-futures:1.2.0=debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath
|
||||
androidx.core:core-backported-fixes:1.0.0=debugAndroidTestLintChecksClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.core:core-ktx:1.19.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.core:core-viewtree:1.0.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.core:core:1.19.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.cursoradapter:cursoradapter:1.0.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.customview:customview-poolingcontainer:1.0.0=debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.customview:customview:1.0.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.documentfile:documentfile:1.0.0=debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.drawerlayout:drawerlayout:1.0.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.dynamicanimation:dynamicanimation:1.0.0=debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.core:core-ktx:1.19.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestCompileClasspath,releaseAndroidTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.core:core-viewtree:1.0.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestCompileClasspath,releaseAndroidTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.core:core:1.19.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestCompileClasspath,releaseAndroidTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.cursoradapter:cursoradapter:1.0.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestCompileClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.customview:customview-poolingcontainer:1.0.0=debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.customview:customview:1.0.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestCompileClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.documentfile:documentfile:1.0.0=debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.drawerlayout:drawerlayout:1.0.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestCompileClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.dynamicanimation:dynamicanimation:1.0.0=debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.emoji2:emoji2-views-helper:1.4.0=debugAndroidTestLintChecksClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.emoji2:emoji2:1.4.0=debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.exifinterface:exifinterface:1.4.2=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.fragment:fragment:1.5.4=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.graphics:graphics-path:1.0.1=debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.interpolator:interpolator:1.0.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.legacy:legacy-support-core-utils:1.0.0=debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.emoji2:emoji2:1.4.0=debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.exifinterface:exifinterface:1.4.2=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestCompileClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.fragment:fragment:1.5.4=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestCompileClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.graphics:graphics-path:1.0.1=debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.interpolator:interpolator:1.0.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestCompileClasspath,releaseAndroidTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.legacy:legacy-support-core-utils:1.0.0=debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.lifecycle:lifecycle-common-java8:2.11.0=debugAndroidTestLintChecksClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.lifecycle:lifecycle-common-jvm:2.11.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.lifecycle:lifecycle-common:2.11.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.lifecycle:lifecycle-livedata-core-ktx:2.11.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.lifecycle:lifecycle-livedata-core:2.11.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.lifecycle:lifecycle-livedata:2.11.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.lifecycle:lifecycle-process:2.11.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.lifecycle:lifecycle-runtime-android:2.11.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.lifecycle:lifecycle-runtime-compose-android:2.11.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.lifecycle:lifecycle-runtime-compose:2.11.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.lifecycle:lifecycle-runtime-ktx-android:2.11.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.lifecycle:lifecycle-runtime-ktx:2.11.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.lifecycle:lifecycle-runtime:2.11.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.lifecycle:lifecycle-common-jvm:2.11.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestCompileClasspath,releaseAndroidTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.lifecycle:lifecycle-common:2.11.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestCompileClasspath,releaseAndroidTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.lifecycle:lifecycle-livedata-core-ktx:2.11.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestCompileClasspath,releaseAndroidTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.lifecycle:lifecycle-livedata-core:2.11.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestCompileClasspath,releaseAndroidTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.lifecycle:lifecycle-livedata:2.11.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestCompileClasspath,releaseAndroidTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.lifecycle:lifecycle-process:2.11.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestCompileClasspath,releaseAndroidTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.lifecycle:lifecycle-runtime-android:2.11.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestCompileClasspath,releaseAndroidTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.lifecycle:lifecycle-runtime-compose-android:2.11.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestCompileClasspath,releaseAndroidTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.lifecycle:lifecycle-runtime-compose:2.11.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestCompileClasspath,releaseAndroidTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.lifecycle:lifecycle-runtime-ktx-android:2.11.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestCompileClasspath,releaseAndroidTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.lifecycle:lifecycle-runtime-ktx:2.11.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestCompileClasspath,releaseAndroidTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.lifecycle:lifecycle-runtime:2.11.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestCompileClasspath,releaseAndroidTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.lifecycle:lifecycle-service:2.11.0=debugAndroidTestLintChecksClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.lifecycle:lifecycle-viewmodel-android:2.11.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.lifecycle:lifecycle-viewmodel-compose-android:2.11.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.lifecycle:lifecycle-viewmodel-compose:2.11.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.lifecycle:lifecycle-viewmodel-ktx:2.11.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.lifecycle:lifecycle-viewmodel-savedstate-android:2.11.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.lifecycle:lifecycle-viewmodel-savedstate:2.11.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.lifecycle:lifecycle-viewmodel:2.11.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.loader:loader:1.0.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.localbroadcastmanager:localbroadcastmanager:1.0.0=debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.navigation:navigation-common-android:2.9.8=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.navigation:navigation-common:2.9.8=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.navigation:navigation-compose-android:2.9.8=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.navigation:navigation-compose:2.9.8=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.navigation:navigation-runtime-android:2.9.8=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.navigation:navigation-runtime:2.9.8=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.navigationevent:navigationevent-android:1.0.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.navigationevent:navigationevent-compose-android:1.0.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.navigationevent:navigationevent-compose:1.0.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.navigationevent:navigationevent:1.0.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.print:print:1.0.0=debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.profileinstaller:profileinstaller:1.4.0=debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.lifecycle:lifecycle-viewmodel-android:2.11.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestCompileClasspath,releaseAndroidTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.lifecycle:lifecycle-viewmodel-compose-android:2.11.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestCompileClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.lifecycle:lifecycle-viewmodel-compose:2.11.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestCompileClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.lifecycle:lifecycle-viewmodel-ktx:2.11.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestCompileClasspath,releaseAndroidTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.lifecycle:lifecycle-viewmodel-savedstate-android:2.11.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestCompileClasspath,releaseAndroidTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.lifecycle:lifecycle-viewmodel-savedstate:2.11.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestCompileClasspath,releaseAndroidTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.lifecycle:lifecycle-viewmodel:2.11.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestCompileClasspath,releaseAndroidTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.loader:loader:1.0.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestCompileClasspath,releaseAndroidTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.localbroadcastmanager:localbroadcastmanager:1.0.0=debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.navigation:navigation-common-android:2.9.8=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestCompileClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.navigation:navigation-common:2.9.8=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestCompileClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.navigation:navigation-compose-android:2.9.8=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestCompileClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.navigation:navigation-compose:2.9.8=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestCompileClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.navigation:navigation-runtime-android:2.9.8=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestCompileClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.navigation:navigation-runtime:2.9.8=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestCompileClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.navigationevent:navigationevent-android:1.0.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestCompileClasspath,releaseAndroidTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.navigationevent:navigationevent-compose-android:1.0.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestCompileClasspath,releaseAndroidTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.navigationevent:navigationevent-compose:1.0.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestCompileClasspath,releaseAndroidTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.navigationevent:navigationevent:1.0.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestCompileClasspath,releaseAndroidTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.print:print:1.0.0=debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.profileinstaller:profileinstaller:1.4.0=debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.resourceinspection:resourceinspection-annotation:1.0.1=debugAndroidTestLintChecksClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.room:room-common:2.6.1=debugAndroidTestLintChecksClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.room:room-ktx:2.6.1=debugAndroidTestLintChecksClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.room:room-runtime:2.6.1=debugAndroidTestLintChecksClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.savedstate:savedstate-android:1.4.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.savedstate:savedstate-compose-android:1.4.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.savedstate:savedstate-compose:1.4.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.savedstate:savedstate-ktx:1.4.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.savedstate:savedstate:1.4.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.security:security-crypto:1.1.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.savedstate:savedstate-android:1.4.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestCompileClasspath,releaseAndroidTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.savedstate:savedstate-compose-android:1.4.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestCompileClasspath,releaseAndroidTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.savedstate:savedstate-compose:1.4.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestCompileClasspath,releaseAndroidTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.savedstate:savedstate-ktx:1.4.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestCompileClasspath,releaseAndroidTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.savedstate:savedstate:1.4.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestCompileClasspath,releaseAndroidTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.security:security-crypto:1.1.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestCompileClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.sqlite:sqlite-framework:2.4.0=debugAndroidTestLintChecksClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.sqlite:sqlite:2.4.0=debugAndroidTestLintChecksClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.startup:startup-runtime:1.1.1=debugAndroidTestCompileClasspath,debugCompileClasspath,debugUnitTestCompileClasspath,releaseCompileClasspath
|
||||
androidx.startup:startup-runtime:1.2.0=debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.test.espresso:espresso-core:3.5.0=debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath
|
||||
androidx.startup:startup-runtime:1.1.1=debugAndroidTestCompileClasspath,debugCompileClasspath,debugUnitTestCompileClasspath,releaseAndroidTestCompileClasspath,releaseCompileClasspath
|
||||
androidx.startup:startup-runtime:1.2.0=debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.test.espresso:espresso-core:3.5.0=debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,releaseAndroidTestRuntimeClasspath
|
||||
androidx.test.espresso:espresso-core:3.7.0=debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath
|
||||
androidx.test.espresso:espresso-idling-resource:3.5.0=debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath
|
||||
androidx.test.espresso:espresso-idling-resource:3.5.0=debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,releaseAndroidTestRuntimeClasspath
|
||||
androidx.test.espresso:espresso-idling-resource:3.7.0=debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath
|
||||
androidx.test.ext:junit:1.1.5=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath
|
||||
androidx.test.ext:junit:1.1.5=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,releaseAndroidTestCompileClasspath,releaseAndroidTestRuntimeClasspath
|
||||
androidx.test.ext:junit:1.3.0=debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath
|
||||
androidx.test.services:storage:1.4.2=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath
|
||||
androidx.test.services:storage:1.4.2=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,releaseAndroidTestCompileClasspath,releaseAndroidTestRuntimeClasspath
|
||||
androidx.test.services:storage:1.6.0=debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath
|
||||
androidx.test:annotation:1.0.1=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath
|
||||
androidx.test:core:1.5.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath
|
||||
androidx.test:annotation:1.0.1=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,releaseAndroidTestCompileClasspath,releaseAndroidTestRuntimeClasspath
|
||||
androidx.test:core:1.5.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,releaseAndroidTestCompileClasspath,releaseAndroidTestRuntimeClasspath
|
||||
androidx.test:core:1.7.0=debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath
|
||||
androidx.test:monitor:1.6.1=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath
|
||||
androidx.test:monitor:1.6.1=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,releaseAndroidTestCompileClasspath,releaseAndroidTestRuntimeClasspath
|
||||
androidx.test:monitor:1.8.0=debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath
|
||||
androidx.test:runner:1.5.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath
|
||||
androidx.test:runner:1.5.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,releaseAndroidTestCompileClasspath,releaseAndroidTestRuntimeClasspath
|
||||
androidx.test:runner:1.7.0=debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath
|
||||
androidx.tracing:tracing-android:1.3.0=debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.tracing:tracing-android:1.3.0=debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.tracing:tracing-ktx:1.3.0=debugAndroidTestLintChecksClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.tracing:tracing:1.0.0=debugAndroidTestCompileClasspath
|
||||
androidx.tracing:tracing:1.0.0=debugAndroidTestCompileClasspath,releaseAndroidTestCompileClasspath
|
||||
androidx.tracing:tracing:1.1.0=debugUnitTestCompileClasspath
|
||||
androidx.tracing:tracing:1.3.0=debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.transition:transition:1.6.0=debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.vectordrawable:vectordrawable-animated:1.1.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.vectordrawable:vectordrawable:1.1.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.versionedparcelable:versionedparcelable:1.1.1=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.viewpager:viewpager:1.0.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.window:window-core-android:1.5.0=debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.window:window-core:1.5.0=debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.window:window:1.5.0=debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.work:work-runtime-ktx:2.10.1=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.work:work-runtime:2.10.1=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.tracing:tracing:1.3.0=debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.transition:transition:1.6.0=debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.vectordrawable:vectordrawable-animated:1.1.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestCompileClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.vectordrawable:vectordrawable:1.1.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestCompileClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.versionedparcelable:versionedparcelable:1.1.1=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestCompileClasspath,releaseAndroidTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.viewpager:viewpager:1.0.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestCompileClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.window:window-core-android:1.5.0=debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.window:window-core:1.5.0=debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.window:window:1.5.0=debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.work:work-runtime-ktx:2.10.1=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestCompileClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
androidx.work:work-runtime:2.10.1=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestCompileClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
com.almworks.sqlite4java:sqlite4java:1.0.392=debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath
|
||||
com.android.tools.analytics-library:protos:32.3.1=androidLintTool,unified-test-platform-android-test-plugin-result-listener-gradle
|
||||
com.android.tools.analytics-library:shared:32.3.1=androidLintTool,unified-test-platform-android-test-plugin-result-listener-gradle
|
||||
@ -218,16 +218,16 @@ com.android.tools:play-sdk-proto:32.3.1=androidLintTool
|
||||
com.android.tools:repository:32.3.1=androidLintTool,unified-test-platform-android-test-plugin-result-listener-gradle
|
||||
com.android.tools:sdk-common:32.3.1=androidLintTool,unified-test-platform-android-test-plugin-result-listener-gradle
|
||||
com.android.tools:sdklib:32.3.1=androidLintTool,unified-test-platform-android-test-plugin-result-listener-gradle
|
||||
com.google.accompanist:accompanist-permissions:0.37.3=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
com.google.android.datatransport:transport-api:2.2.1=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
com.google.android.datatransport:transport-backend-cct:2.3.3=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
com.google.android.datatransport:transport-runtime:2.2.6=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
com.google.android.gms:play-services-base:18.9.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
com.google.android.gms:play-services-basement:18.9.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
com.google.android.gms:play-services-location:21.4.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
com.google.android.gms:play-services-mlkit-barcode-scanning:18.3.1=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
com.google.android.gms:play-services-tasks:18.4.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
com.google.android.odml:image:1.0.0-beta1=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
com.google.accompanist:accompanist-permissions:0.37.3=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestCompileClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
com.google.android.datatransport:transport-api:2.2.1=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestCompileClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
com.google.android.datatransport:transport-backend-cct:2.3.3=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestCompileClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
com.google.android.datatransport:transport-runtime:2.2.6=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestCompileClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
com.google.android.gms:play-services-base:18.9.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestCompileClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
com.google.android.gms:play-services-basement:18.9.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestCompileClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
com.google.android.gms:play-services-location:21.4.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestCompileClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
com.google.android.gms:play-services-mlkit-barcode-scanning:18.3.1=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestCompileClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
com.google.android.gms:play-services-tasks:18.4.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestCompileClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
com.google.android.odml:image:1.0.0-beta1=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestCompileClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
com.google.android:annotations:4.1.1.4=unified-test-platform-android-test-plugin-host-emulator-control,unified-test-platform-core
|
||||
com.google.api.grpc:proto-google-common-protos:2.17.0=unified-test-platform-android-device-provider-ddmlib,unified-test-platform-android-test-plugin-host-additional-test-output,unified-test-platform-android-test-plugin-host-apk-installer,unified-test-platform-android-test-plugin-host-coverage,unified-test-platform-android-test-plugin-host-device-info,unified-test-platform-android-test-plugin-host-logcat,unified-test-platform-android-test-plugin-result-listener-gradle,unified-test-platform-core
|
||||
com.google.api.grpc:proto-google-common-protos:2.48.0=unified-test-platform-android-test-plugin-host-emulator-control
|
||||
@ -236,39 +236,39 @@ com.google.auto.service:auto-service:1.1.1=unified-test-platform-android-device-
|
||||
com.google.auto.value:auto-value-annotations:1.11.0=debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath
|
||||
com.google.auto.value:auto-value-annotations:1.6.3=debugAndroidTestLintChecksClasspath,debugLintChecksClasspath,debugRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
com.google.auto:auto-common:1.2.1=unified-test-platform-android-device-provider-ddmlib,unified-test-platform-android-driver-instrumentation,unified-test-platform-android-test-plugin,unified-test-platform-android-test-plugin-host-additional-test-output,unified-test-platform-android-test-plugin-host-apk-installer,unified-test-platform-android-test-plugin-host-coverage,unified-test-platform-android-test-plugin-host-device-info,unified-test-platform-android-test-plugin-host-emulator-control,unified-test-platform-android-test-plugin-host-logcat,unified-test-platform-android-test-plugin-result-listener-gradle
|
||||
com.google.code.findbugs:jsr305:3.0.2=androidLintTool,debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath,unified-test-platform-android-device-provider-ddmlib,unified-test-platform-android-driver-instrumentation,unified-test-platform-android-test-plugin,unified-test-platform-android-test-plugin-host-additional-test-output,unified-test-platform-android-test-plugin-host-apk-installer,unified-test-platform-android-test-plugin-host-coverage,unified-test-platform-android-test-plugin-host-device-info,unified-test-platform-android-test-plugin-host-emulator-control,unified-test-platform-android-test-plugin-host-logcat,unified-test-platform-android-test-plugin-result-listener-gradle,unified-test-platform-core,unified-test-platform-gradle-work-action,unified-test-platform-launcher
|
||||
com.google.code.findbugs:jsr305:3.0.2=androidLintTool,debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestCompileClasspath,releaseAndroidTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath,unified-test-platform-android-device-provider-ddmlib,unified-test-platform-android-driver-instrumentation,unified-test-platform-android-test-plugin,unified-test-platform-android-test-plugin-host-additional-test-output,unified-test-platform-android-test-plugin-host-apk-installer,unified-test-platform-android-test-plugin-host-coverage,unified-test-platform-android-test-plugin-host-device-info,unified-test-platform-android-test-plugin-host-emulator-control,unified-test-platform-android-test-plugin-host-logcat,unified-test-platform-android-test-plugin-result-listener-gradle,unified-test-platform-core,unified-test-platform-gradle-work-action,unified-test-platform-launcher
|
||||
com.google.code.gson:gson:2.10.1=unified-test-platform-core,unified-test-platform-gradle-work-action
|
||||
com.google.code.gson:gson:2.11.0=androidLintTool,unified-test-platform-android-test-plugin-host-emulator-control,unified-test-platform-android-test-plugin-result-listener-gradle
|
||||
com.google.code.gson:gson:2.14.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
com.google.code.gson:gson:2.14.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestCompileClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
com.google.code.gson:gson:2.8.9=unified-test-platform-android-driver-instrumentation,unified-test-platform-launcher
|
||||
com.google.crypto.tink:tink-android:1.23.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
com.google.crypto.tink:tink-android:1.23.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestCompileClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
com.google.crypto.tink:tink:1.18.0=unified-test-platform-android-test-plugin-host-emulator-control,unified-test-platform-gradle-work-action
|
||||
com.google.dagger:dagger:2.48=unified-test-platform-android-device-provider-ddmlib,unified-test-platform-android-driver-instrumentation,unified-test-platform-android-test-plugin-host-additional-test-output,unified-test-platform-android-test-plugin-host-apk-installer,unified-test-platform-android-test-plugin-host-coverage,unified-test-platform-android-test-plugin-host-device-info,unified-test-platform-android-test-plugin-host-emulator-control,unified-test-platform-android-test-plugin-host-logcat,unified-test-platform-android-test-plugin-result-listener-gradle,unified-test-platform-core,unified-test-platform-gradle-work-action
|
||||
com.google.dagger:dagger:2.59=debugAndroidTestLintChecksClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
com.google.errorprone:error_prone_annotation:2.38.0=debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath
|
||||
com.google.errorprone:error_prone_annotations:2.23.0=unified-test-platform-android-driver-instrumentation,unified-test-platform-android-test-plugin,unified-test-platform-core,unified-test-platform-launcher
|
||||
com.google.errorprone:error_prone_annotations:2.36.0=androidLintTool,unified-test-platform-android-device-provider-ddmlib,unified-test-platform-android-test-plugin-host-additional-test-output,unified-test-platform-android-test-plugin-host-apk-installer,unified-test-platform-android-test-plugin-host-coverage,unified-test-platform-android-test-plugin-host-device-info,unified-test-platform-android-test-plugin-host-emulator-control,unified-test-platform-android-test-plugin-host-logcat,unified-test-platform-android-test-plugin-result-listener-gradle,unified-test-platform-gradle-work-action
|
||||
com.google.errorprone:error_prone_annotations:2.48.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
com.google.firebase:firebase-annotations:16.0.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
com.google.firebase:firebase-components:16.1.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
com.google.firebase:firebase-encoders-json:17.1.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
com.google.firebase:firebase-encoders:16.1.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
com.google.errorprone:error_prone_annotations:2.48.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestCompileClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
com.google.firebase:firebase-annotations:16.0.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestCompileClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
com.google.firebase:firebase-components:16.1.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestCompileClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
com.google.firebase:firebase-encoders-json:17.1.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestCompileClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
com.google.firebase:firebase-encoders:16.1.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestCompileClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
com.google.guava:failureaccess:1.0.1=unified-test-platform-android-driver-instrumentation,unified-test-platform-android-test-plugin,unified-test-platform-core,unified-test-platform-launcher
|
||||
com.google.guava:failureaccess:1.0.2=androidLintTool,unified-test-platform-android-device-provider-ddmlib,unified-test-platform-android-test-plugin-host-additional-test-output,unified-test-platform-android-test-plugin-host-apk-installer,unified-test-platform-android-test-plugin-host-coverage,unified-test-platform-android-test-plugin-host-device-info,unified-test-platform-android-test-plugin-host-emulator-control,unified-test-platform-android-test-plugin-host-logcat,unified-test-platform-android-test-plugin-result-listener-gradle,unified-test-platform-gradle-work-action
|
||||
com.google.guava:failureaccess:1.0.3=debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath
|
||||
com.google.guava:guava:32.0.1-jre=unified-test-platform-android-driver-instrumentation,unified-test-platform-android-test-plugin,unified-test-platform-core,unified-test-platform-launcher
|
||||
com.google.guava:guava:33.4.0-jre=androidLintTool,unified-test-platform-android-device-provider-ddmlib,unified-test-platform-android-test-plugin-host-additional-test-output,unified-test-platform-android-test-plugin-host-apk-installer,unified-test-platform-android-test-plugin-host-coverage,unified-test-platform-android-test-plugin-host-device-info,unified-test-platform-android-test-plugin-host-emulator-control,unified-test-platform-android-test-plugin-host-logcat,unified-test-platform-android-test-plugin-result-listener-gradle,unified-test-platform-gradle-work-action
|
||||
com.google.guava:guava:33.4.8-jre=debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath
|
||||
com.google.guava:listenablefuture:1.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
com.google.guava:listenablefuture:1.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,releaseAndroidTestCompileClasspath,releaseAndroidTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava=androidLintTool,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,unified-test-platform-android-device-provider-ddmlib,unified-test-platform-android-driver-instrumentation,unified-test-platform-android-test-plugin,unified-test-platform-android-test-plugin-host-additional-test-output,unified-test-platform-android-test-plugin-host-apk-installer,unified-test-platform-android-test-plugin-host-coverage,unified-test-platform-android-test-plugin-host-device-info,unified-test-platform-android-test-plugin-host-emulator-control,unified-test-platform-android-test-plugin-host-logcat,unified-test-platform-android-test-plugin-result-listener-gradle,unified-test-platform-core,unified-test-platform-gradle-work-action,unified-test-platform-launcher
|
||||
com.google.j2objc:j2objc-annotations:2.8=unified-test-platform-android-driver-instrumentation,unified-test-platform-android-test-plugin,unified-test-platform-core,unified-test-platform-launcher
|
||||
com.google.j2objc:j2objc-annotations:3.0.0=androidLintTool,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,unified-test-platform-android-device-provider-ddmlib,unified-test-platform-android-test-plugin-host-additional-test-output,unified-test-platform-android-test-plugin-host-apk-installer,unified-test-platform-android-test-plugin-host-coverage,unified-test-platform-android-test-plugin-host-device-info,unified-test-platform-android-test-plugin-host-emulator-control,unified-test-platform-android-test-plugin-host-logcat,unified-test-platform-android-test-plugin-result-listener-gradle,unified-test-platform-gradle-work-action
|
||||
com.google.jimfs:jimfs:1.1=androidLintTool,unified-test-platform-android-test-plugin-result-listener-gradle
|
||||
com.google.mlkit:barcode-scanning-common:17.0.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
com.google.mlkit:barcode-scanning:17.3.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
com.google.mlkit:common:18.11.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
com.google.mlkit:vision-common:17.3.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
com.google.mlkit:vision-interfaces:16.3.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
com.google.mlkit:barcode-scanning-common:17.0.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestCompileClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
com.google.mlkit:barcode-scanning:17.3.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestCompileClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
com.google.mlkit:common:18.11.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestCompileClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
com.google.mlkit:vision-common:17.3.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestCompileClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
com.google.mlkit:vision-interfaces:16.3.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestCompileClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
com.google.protobuf:protobuf-java-util:3.22.3=unified-test-platform-core
|
||||
com.google.protobuf:protobuf-java-util:4.28.3=unified-test-platform-android-driver-instrumentation,unified-test-platform-android-test-plugin-host-emulator-control,unified-test-platform-gradle-work-action,unified-test-platform-launcher
|
||||
com.google.protobuf:protobuf-java:3.25.5=androidLintTool
|
||||
@ -281,14 +281,14 @@ com.google.testing.platform:core-proto:0.0.9-alpha04=unified-test-platform-andro
|
||||
com.google.testing.platform:core:0.0.9-alpha04=unified-test-platform-core
|
||||
com.google.testing.platform:launcher:0.0.9-alpha04=unified-test-platform-gradle-work-action,unified-test-platform-launcher
|
||||
com.google.testparameterinjector:test-parameter-injector:1.18=debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath
|
||||
com.google.zxing:core:3.5.4=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
com.google.zxing:core:3.5.4=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestCompileClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
com.ibm.icu:icu4j:77.1=debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath
|
||||
com.squareup.okhttp3:mockwebserver3:5.4.0=debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath
|
||||
com.squareup.okhttp3:okhttp-android:5.4.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
com.squareup.okhttp3:okhttp:5.4.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
com.squareup.okio:okio-jvm:3.17.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
com.squareup.okio:okio:3.17.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
com.squareup:javawriter:2.1.1=debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath
|
||||
com.squareup.okhttp3:okhttp-android:5.4.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestCompileClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
com.squareup.okhttp3:okhttp:5.4.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestCompileClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
com.squareup.okio:okio-jvm:3.17.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestCompileClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
com.squareup.okio:okio:3.17.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestCompileClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
com.squareup:javawriter:2.1.1=debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,releaseAndroidTestRuntimeClasspath
|
||||
com.sun.istack:istack-commons-runtime:3.0.8=androidLintTool,unified-test-platform-android-test-plugin-result-listener-gradle
|
||||
com.sun.xml.fastinfoset:FastInfoset:1.2.16=androidLintTool,unified-test-platform-android-test-plugin-result-listener-gradle
|
||||
commons-codec:commons-codec:1.17.1=androidLintTool,unified-test-platform-android-test-plugin-result-listener-gradle
|
||||
@ -340,14 +340,14 @@ jakarta.activation:jakarta.activation-api:1.2.1=androidLintTool,unified-test-pla
|
||||
jakarta.inject:jakarta.inject-api:2.0.1=debugAndroidTestLintChecksClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
jakarta.xml.bind:jakarta.xml.bind-api:2.3.2=androidLintTool,unified-test-platform-android-test-plugin-result-listener-gradle
|
||||
javax.annotation:javax.annotation-api:1.3.2=debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,unified-test-platform-android-test-plugin-host-emulator-control
|
||||
javax.inject:javax.inject:1=androidLintTool,debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath,unified-test-platform-android-device-provider-ddmlib,unified-test-platform-android-driver-instrumentation,unified-test-platform-android-test-plugin-host-additional-test-output,unified-test-platform-android-test-plugin-host-apk-installer,unified-test-platform-android-test-plugin-host-coverage,unified-test-platform-android-test-plugin-host-device-info,unified-test-platform-android-test-plugin-host-emulator-control,unified-test-platform-android-test-plugin-host-logcat,unified-test-platform-android-test-plugin-result-listener-gradle,unified-test-platform-core,unified-test-platform-gradle-work-action
|
||||
junit:junit:4.13.2=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath
|
||||
javax.inject:javax.inject:1=androidLintTool,debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestCompileClasspath,releaseAndroidTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath,unified-test-platform-android-device-provider-ddmlib,unified-test-platform-android-driver-instrumentation,unified-test-platform-android-test-plugin-host-additional-test-output,unified-test-platform-android-test-plugin-host-apk-installer,unified-test-platform-android-test-plugin-host-coverage,unified-test-platform-android-test-plugin-host-device-info,unified-test-platform-android-test-plugin-host-emulator-control,unified-test-platform-android-test-plugin-host-logcat,unified-test-platform-android-test-plugin-result-listener-gradle,unified-test-platform-core,unified-test-platform-gradle-work-action
|
||||
junit:junit:4.13.2=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestCompileClasspath,releaseAndroidTestRuntimeClasspath
|
||||
net.bytebuddy:byte-buddy-agent:1.17.7=debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath
|
||||
net.bytebuddy:byte-buddy:1.17.7=debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath
|
||||
net.java.dev.jna:jna-platform:5.6.0=androidLintTool,unified-test-platform-android-device-provider-ddmlib,unified-test-platform-android-test-plugin-host-additional-test-output,unified-test-platform-android-test-plugin-host-apk-installer,unified-test-platform-android-test-plugin-host-coverage,unified-test-platform-android-test-plugin-host-device-info,unified-test-platform-android-test-plugin-host-emulator-control,unified-test-platform-android-test-plugin-host-logcat,unified-test-platform-android-test-plugin-result-listener-gradle,unified-test-platform-gradle-work-action
|
||||
net.java.dev.jna:jna:5.6.0=androidLintTool,unified-test-platform-android-device-provider-ddmlib,unified-test-platform-android-test-plugin-host-additional-test-output,unified-test-platform-android-test-plugin-host-apk-installer,unified-test-platform-android-test-plugin-host-coverage,unified-test-platform-android-test-plugin-host-device-info,unified-test-platform-android-test-plugin-host-emulator-control,unified-test-platform-android-test-plugin-host-logcat,unified-test-platform-android-test-plugin-result-listener-gradle,unified-test-platform-gradle-work-action
|
||||
net.sf.kxml:kxml2:2.3.0=androidLintTool,unified-test-platform-android-device-provider-ddmlib,unified-test-platform-android-test-plugin-result-listener-gradle,unified-test-platform-gradle-work-action
|
||||
no.nordicsemi.android:ble:2.11.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
no.nordicsemi.android:ble:2.11.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestCompileClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
org.apache.commons:commons-compress:1.27.1=androidLintTool,unified-test-platform-android-test-plugin-result-listener-gradle
|
||||
org.apache.commons:commons-lang3:3.16.0=androidLintTool,unified-test-platform-android-test-plugin-result-listener-gradle
|
||||
org.apache.httpcomponents:httpclient:4.5.6=androidLintTool,unified-test-platform-android-test-plugin-result-listener-gradle
|
||||
@ -355,7 +355,7 @@ org.apache.httpcomponents:httpcore:4.4.16=androidLintTool,unified-test-platform-
|
||||
org.apache.httpcomponents:httpmime:4.5.6=androidLintTool,unified-test-platform-android-test-plugin-result-listener-gradle
|
||||
org.bouncycastle:bcpkix-jdk18on:1.79=androidLintTool,unified-test-platform-android-test-plugin-result-listener-gradle
|
||||
org.bouncycastle:bcprov-jdk18on:1.79=androidLintTool,unified-test-platform-android-test-plugin-result-listener-gradle
|
||||
org.bouncycastle:bcprov-jdk18on:1.85=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
org.bouncycastle:bcprov-jdk18on:1.85=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestCompileClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
org.bouncycastle:bcutil-jdk18on:1.79=androidLintTool,unified-test-platform-android-test-plugin-result-listener-gradle
|
||||
org.checkerframework:checker-qual:3.33.0=unified-test-platform-android-driver-instrumentation,unified-test-platform-android-test-plugin,unified-test-platform-core,unified-test-platform-launcher
|
||||
org.checkerframework:checker-qual:3.43.0=androidLintTool,unified-test-platform-android-device-provider-ddmlib,unified-test-platform-android-test-plugin-host-additional-test-output,unified-test-platform-android-test-plugin-host-apk-installer,unified-test-platform-android-test-plugin-host-coverage,unified-test-platform-android-test-plugin-host-device-info,unified-test-platform-android-test-plugin-host-emulator-control,unified-test-platform-android-test-plugin-host-logcat,unified-test-platform-android-test-plugin-result-listener-gradle,unified-test-platform-gradle-work-action
|
||||
@ -365,34 +365,34 @@ org.codehaus.mojo:animal-sniffer-annotations:1.24=unified-test-platform-android-
|
||||
org.conscrypt:conscrypt-openjdk-uber:2.5.2=debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath
|
||||
org.glassfish.jaxb:jaxb-runtime:2.3.2=androidLintTool,unified-test-platform-android-test-plugin-result-listener-gradle
|
||||
org.glassfish.jaxb:txw2:2.3.2=androidLintTool,unified-test-platform-android-test-plugin-result-listener-gradle
|
||||
org.hamcrest:hamcrest-core:1.3=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath
|
||||
org.hamcrest:hamcrest-integration:1.3=debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath
|
||||
org.hamcrest:hamcrest-library:1.3=debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath
|
||||
org.hamcrest:hamcrest-core:1.3=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestCompileClasspath,releaseAndroidTestRuntimeClasspath
|
||||
org.hamcrest:hamcrest-integration:1.3=debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,releaseAndroidTestRuntimeClasspath
|
||||
org.hamcrest:hamcrest-library:1.3=debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestRuntimeClasspath
|
||||
org.jetbrains.kotlin:compose-group-mapping:2.4.10=composeMappingProducerClasspath
|
||||
org.jetbrains.kotlin:kotlin-build-tools-api:2.4.0=kotlinAbiValidationCompatClasspath
|
||||
org.jetbrains.kotlin:kotlin-build-tools-api:2.4.10=kotlinBuildToolsApiClasspath,kotlinCompilerClasspath,kotlinCompilerPluginClasspathDebug,kotlinCompilerPluginClasspathDebugAndroidTest,kotlinCompilerPluginClasspathDebugUnitTest,kotlinCompilerPluginClasspathRelease
|
||||
org.jetbrains.kotlin:kotlin-build-tools-api:2.4.10=kotlinBuildToolsApiClasspath,kotlinCompilerClasspath,kotlinCompilerPluginClasspathDebug,kotlinCompilerPluginClasspathDebugAndroidTest,kotlinCompilerPluginClasspathDebugUnitTest,kotlinCompilerPluginClasspathRelease,kotlinCompilerPluginClasspathReleaseAndroidTest
|
||||
org.jetbrains.kotlin:kotlin-build-tools-compat:2.4.10=kotlinAbiValidationCompatClasspath,kotlinBuildToolsApiClasspath,kotlinCompilerClasspath
|
||||
org.jetbrains.kotlin:kotlin-build-tools-cri-impl:2.4.0=kotlinAbiValidationCompatClasspath
|
||||
org.jetbrains.kotlin:kotlin-build-tools-cri-impl:2.4.10=kotlinBuildToolsApiClasspath,kotlinCompilerClasspath
|
||||
org.jetbrains.kotlin:kotlin-build-tools-impl:2.4.0=kotlinAbiValidationCompatClasspath
|
||||
org.jetbrains.kotlin:kotlin-build-tools-impl:2.4.10=kotlinBuildToolsApiClasspath,kotlinCompilerClasspath
|
||||
org.jetbrains.kotlin:kotlin-compiler-embeddable:2.4.0=kotlinAbiValidationCompatClasspath
|
||||
org.jetbrains.kotlin:kotlin-compiler-embeddable:2.4.10=kotlinBuildToolsApiClasspath,kotlinCompilerClasspath,kotlinCompilerPluginClasspathDebug,kotlinCompilerPluginClasspathDebugAndroidTest,kotlinCompilerPluginClasspathDebugUnitTest,kotlinCompilerPluginClasspathRelease
|
||||
org.jetbrains.kotlin:kotlin-compiler-embeddable:2.4.10=kotlinBuildToolsApiClasspath,kotlinCompilerClasspath,kotlinCompilerPluginClasspathDebug,kotlinCompilerPluginClasspathDebugAndroidTest,kotlinCompilerPluginClasspathDebugUnitTest,kotlinCompilerPluginClasspathRelease,kotlinCompilerPluginClasspathReleaseAndroidTest
|
||||
org.jetbrains.kotlin:kotlin-compiler-runner:2.4.0=kotlinAbiValidationCompatClasspath
|
||||
org.jetbrains.kotlin:kotlin-compiler-runner:2.4.10=kotlinBuildToolsApiClasspath,kotlinCompilerClasspath
|
||||
org.jetbrains.kotlin:kotlin-compose-compiler-plugin-embeddable:2.4.10=kotlin-extension,kotlinCompilerPluginClasspathDebug,kotlinCompilerPluginClasspathDebugAndroidTest,kotlinCompilerPluginClasspathDebugUnitTest,kotlinCompilerPluginClasspathRelease
|
||||
org.jetbrains.kotlin:kotlin-compose-compiler-plugin-embeddable:2.4.10=kotlin-extension,kotlinCompilerPluginClasspathDebug,kotlinCompilerPluginClasspathDebugAndroidTest,kotlinCompilerPluginClasspathDebugUnitTest,kotlinCompilerPluginClasspathRelease,kotlinCompilerPluginClasspathReleaseAndroidTest
|
||||
org.jetbrains.kotlin:kotlin-daemon-client:2.4.0=kotlinAbiValidationCompatClasspath
|
||||
org.jetbrains.kotlin:kotlin-daemon-client:2.4.10=kotlinBuildToolsApiClasspath,kotlinCompilerClasspath
|
||||
org.jetbrains.kotlin:kotlin-daemon-embeddable:2.4.0=kotlinAbiValidationCompatClasspath
|
||||
org.jetbrains.kotlin:kotlin-daemon-embeddable:2.4.10=kotlinBuildToolsApiClasspath,kotlinCompilerClasspath,kotlinCompilerPluginClasspathDebug,kotlinCompilerPluginClasspathDebugAndroidTest,kotlinCompilerPluginClasspathDebugUnitTest,kotlinCompilerPluginClasspathRelease
|
||||
org.jetbrains.kotlin:kotlin-parcelize-compiler:2.4.10=kotlinCompilerPluginClasspathDebug,kotlinCompilerPluginClasspathDebugAndroidTest,kotlinCompilerPluginClasspathDebugUnitTest,kotlinCompilerPluginClasspathRelease
|
||||
org.jetbrains.kotlin:kotlin-parcelize-runtime:2.4.10=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugRuntimeClasspathCopy,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath,releaseRuntimeClasspathCopy
|
||||
org.jetbrains.kotlin:kotlin-reflect:1.6.10=kotlinAbiValidationCompatClasspath,kotlinBuildToolsApiClasspath,kotlinCompilerClasspath,kotlinCompilerPluginClasspathDebug,kotlinCompilerPluginClasspathDebugAndroidTest,kotlinCompilerPluginClasspathDebugUnitTest,kotlinCompilerPluginClasspathRelease
|
||||
org.jetbrains.kotlin:kotlin-daemon-embeddable:2.4.10=kotlinBuildToolsApiClasspath,kotlinCompilerClasspath,kotlinCompilerPluginClasspathDebug,kotlinCompilerPluginClasspathDebugAndroidTest,kotlinCompilerPluginClasspathDebugUnitTest,kotlinCompilerPluginClasspathRelease,kotlinCompilerPluginClasspathReleaseAndroidTest
|
||||
org.jetbrains.kotlin:kotlin-parcelize-compiler:2.4.10=kotlinCompilerPluginClasspathDebug,kotlinCompilerPluginClasspathDebugAndroidTest,kotlinCompilerPluginClasspathDebugUnitTest,kotlinCompilerPluginClasspathRelease,kotlinCompilerPluginClasspathReleaseAndroidTest
|
||||
org.jetbrains.kotlin:kotlin-parcelize-runtime:2.4.10=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugRuntimeClasspathCopy,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestCompileClasspath,releaseAndroidTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath,releaseRuntimeClasspathCopy
|
||||
org.jetbrains.kotlin:kotlin-reflect:1.6.10=kotlinAbiValidationCompatClasspath,kotlinBuildToolsApiClasspath,kotlinCompilerClasspath,kotlinCompilerPluginClasspathDebug,kotlinCompilerPluginClasspathDebugAndroidTest,kotlinCompilerPluginClasspathDebugUnitTest,kotlinCompilerPluginClasspathRelease,kotlinCompilerPluginClasspathReleaseAndroidTest
|
||||
org.jetbrains.kotlin:kotlin-reflect:1.8.21=unified-test-platform-android-device-provider-ddmlib,unified-test-platform-android-driver-instrumentation,unified-test-platform-android-test-plugin,unified-test-platform-android-test-plugin-host-additional-test-output,unified-test-platform-android-test-plugin-host-apk-installer,unified-test-platform-android-test-plugin-host-coverage,unified-test-platform-android-test-plugin-host-device-info,unified-test-platform-android-test-plugin-host-emulator-control,unified-test-platform-android-test-plugin-host-logcat,unified-test-platform-core,unified-test-platform-gradle-work-action,unified-test-platform-launcher
|
||||
org.jetbrains.kotlin:kotlin-reflect:2.1.20=debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath
|
||||
org.jetbrains.kotlin:kotlin-reflect:2.2.10=androidLintTool,unified-test-platform-android-test-plugin-result-listener-gradle
|
||||
org.jetbrains.kotlin:kotlin-script-runtime:2.4.0=kotlinAbiValidationCompatClasspath
|
||||
org.jetbrains.kotlin:kotlin-script-runtime:2.4.10=kotlinBuildToolsApiClasspath,kotlinCompilerClasspath,kotlinCompilerPluginClasspathDebug,kotlinCompilerPluginClasspathDebugAndroidTest,kotlinCompilerPluginClasspathDebugUnitTest,kotlinCompilerPluginClasspathRelease
|
||||
org.jetbrains.kotlin:kotlin-script-runtime:2.4.10=kotlinBuildToolsApiClasspath,kotlinCompilerClasspath,kotlinCompilerPluginClasspathDebug,kotlinCompilerPluginClasspathDebugAndroidTest,kotlinCompilerPluginClasspathDebugUnitTest,kotlinCompilerPluginClasspathRelease,kotlinCompilerPluginClasspathReleaseAndroidTest
|
||||
org.jetbrains.kotlin:kotlin-stdlib-common:1.8.21=unified-test-platform-android-test-plugin,unified-test-platform-core
|
||||
org.jetbrains.kotlin:kotlin-stdlib-common:1.9.0=unified-test-platform-android-driver-instrumentation,unified-test-platform-launcher
|
||||
org.jetbrains.kotlin:kotlin-stdlib-common:2.2.10=unified-test-platform-android-device-provider-ddmlib,unified-test-platform-android-test-plugin-host-additional-test-output,unified-test-platform-android-test-plugin-host-apk-installer,unified-test-platform-android-test-plugin-host-coverage,unified-test-platform-android-test-plugin-host-device-info,unified-test-platform-android-test-plugin-host-emulator-control,unified-test-platform-android-test-plugin-host-logcat,unified-test-platform-gradle-work-action
|
||||
@ -408,35 +408,35 @@ org.jetbrains.kotlin:kotlin-stdlib:1.9.0=unified-test-platform-android-driver-in
|
||||
org.jetbrains.kotlin:kotlin-stdlib:2.2.10=androidLintTool,unified-test-platform-android-device-provider-ddmlib,unified-test-platform-android-test-plugin-host-additional-test-output,unified-test-platform-android-test-plugin-host-apk-installer,unified-test-platform-android-test-plugin-host-coverage,unified-test-platform-android-test-plugin-host-device-info,unified-test-platform-android-test-plugin-host-emulator-control,unified-test-platform-android-test-plugin-host-logcat,unified-test-platform-android-test-plugin-result-listener-gradle,unified-test-platform-gradle-work-action
|
||||
org.jetbrains.kotlin:kotlin-stdlib:2.2.21=composeMappingProducerClasspath
|
||||
org.jetbrains.kotlin:kotlin-stdlib:2.4.0=kotlinAbiValidationCompatClasspath
|
||||
org.jetbrains.kotlin:kotlin-stdlib:2.4.10=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugRuntimeClasspathCopy,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,kotlinBuildToolsApiClasspath,kotlinCompilerClasspath,kotlinCompilerPluginClasspathDebug,kotlinCompilerPluginClasspathDebugAndroidTest,kotlinCompilerPluginClasspathDebugUnitTest,kotlinCompilerPluginClasspathRelease,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath,releaseRuntimeClasspathCopy
|
||||
org.jetbrains.kotlin:kotlin-stdlib:2.4.10=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugRuntimeClasspathCopy,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,kotlinBuildToolsApiClasspath,kotlinCompilerClasspath,kotlinCompilerPluginClasspathDebug,kotlinCompilerPluginClasspathDebugAndroidTest,kotlinCompilerPluginClasspathDebugUnitTest,kotlinCompilerPluginClasspathRelease,kotlinCompilerPluginClasspathReleaseAndroidTest,releaseAndroidTestCompileClasspath,releaseAndroidTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath,releaseRuntimeClasspathCopy
|
||||
org.jetbrains.kotlin:kotlin-tooling-core:2.4.10=kotlinAbiValidationCompatClasspath,kotlinBuildToolsApiClasspath,kotlinCompilerClasspath
|
||||
org.jetbrains.kotlinx:atomicfu-jvm:0.22.0=unified-test-platform-android-driver-instrumentation,unified-test-platform-android-test-plugin-host-emulator-control,unified-test-platform-gradle-work-action,unified-test-platform-launcher
|
||||
org.jetbrains.kotlinx:atomicfu-jvm:0.28.0=debugAndroidTestLintChecksClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
org.jetbrains.kotlinx:atomicfu:0.22.0=unified-test-platform-android-driver-instrumentation,unified-test-platform-android-test-plugin-host-emulator-control,unified-test-platform-gradle-work-action,unified-test-platform-launcher
|
||||
org.jetbrains.kotlinx:atomicfu:0.28.0=debugAndroidTestLintChecksClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
org.jetbrains.kotlinx:kotlinx-coroutines-android:1.11.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.11.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
org.jetbrains.kotlinx:kotlinx-coroutines-android:1.11.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestCompileClasspath,releaseAndroidTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.11.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestCompileClasspath,releaseAndroidTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.7.3=unified-test-platform-android-device-provider-ddmlib,unified-test-platform-android-driver-instrumentation,unified-test-platform-android-test-plugin,unified-test-platform-android-test-plugin-host-additional-test-output,unified-test-platform-android-test-plugin-host-apk-installer,unified-test-platform-android-test-plugin-host-coverage,unified-test-platform-android-test-plugin-host-device-info,unified-test-platform-android-test-plugin-host-emulator-control,unified-test-platform-android-test-plugin-host-logcat,unified-test-platform-core,unified-test-platform-gradle-work-action,unified-test-platform-launcher
|
||||
org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.9.0=androidLintTool,unified-test-platform-android-test-plugin-result-listener-gradle
|
||||
org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.11.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.11.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestCompileClasspath,releaseAndroidTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.7.3=unified-test-platform-android-device-provider-ddmlib,unified-test-platform-android-driver-instrumentation,unified-test-platform-android-test-plugin,unified-test-platform-android-test-plugin-host-additional-test-output,unified-test-platform-android-test-plugin-host-apk-installer,unified-test-platform-android-test-plugin-host-coverage,unified-test-platform-android-test-plugin-host-device-info,unified-test-platform-android-test-plugin-host-emulator-control,unified-test-platform-android-test-plugin-host-logcat,unified-test-platform-core,unified-test-platform-gradle-work-action,unified-test-platform-launcher
|
||||
org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.8.0=kotlinAbiValidationCompatClasspath,kotlinBuildToolsApiClasspath,kotlinCompilerClasspath,kotlinCompilerPluginClasspathDebug,kotlinCompilerPluginClasspathDebugAndroidTest,kotlinCompilerPluginClasspathDebugUnitTest,kotlinCompilerPluginClasspathRelease
|
||||
org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.8.0=kotlinAbiValidationCompatClasspath,kotlinBuildToolsApiClasspath,kotlinCompilerClasspath,kotlinCompilerPluginClasspathDebug,kotlinCompilerPluginClasspathDebugAndroidTest,kotlinCompilerPluginClasspathDebugUnitTest,kotlinCompilerPluginClasspathRelease,kotlinCompilerPluginClasspathReleaseAndroidTest
|
||||
org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.9.0=androidLintTool,unified-test-platform-android-test-plugin-result-listener-gradle
|
||||
org.jetbrains.kotlinx:kotlinx-coroutines-core:1.11.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
org.jetbrains.kotlinx:kotlinx-coroutines-core:1.11.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestCompileClasspath,releaseAndroidTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3=unified-test-platform-android-device-provider-ddmlib,unified-test-platform-android-test-plugin-host-additional-test-output,unified-test-platform-android-test-plugin-host-apk-installer,unified-test-platform-android-test-plugin-host-coverage,unified-test-platform-android-test-plugin-host-device-info,unified-test-platform-android-test-plugin-host-emulator-control,unified-test-platform-android-test-plugin-host-logcat,unified-test-platform-core,unified-test-platform-gradle-work-action,unified-test-platform-launcher
|
||||
org.jetbrains.kotlinx:kotlinx-coroutines-core:1.9.0=androidLintTool,unified-test-platform-android-test-plugin-result-listener-gradle
|
||||
org.jetbrains.kotlinx:kotlinx-coroutines-test-jvm:1.11.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath
|
||||
org.jetbrains.kotlinx:kotlinx-coroutines-test:1.11.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath
|
||||
org.jetbrains.kotlinx:kotlinx-serialization-bom:1.7.3=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
org.jetbrains.kotlinx:kotlinx-serialization-core-jvm:1.7.3=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
org.jetbrains.kotlinx:kotlinx-serialization-core:1.7.3=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
org.jetbrains:annotations:13.0=composeMappingProducerClasspath,debugRuntimeClasspathCopy,kotlinAbiValidationCompatClasspath,kotlinBuildToolsApiClasspath,kotlinCompilerClasspath,kotlinCompilerPluginClasspathDebug,kotlinCompilerPluginClasspathDebugAndroidTest,kotlinCompilerPluginClasspathDebugUnitTest,kotlinCompilerPluginClasspathRelease,releaseRuntimeClasspathCopy
|
||||
org.jetbrains:annotations:23.0.0=androidLintTool,debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath,unified-test-platform-android-device-provider-ddmlib,unified-test-platform-android-driver-instrumentation,unified-test-platform-android-test-plugin,unified-test-platform-android-test-plugin-host-additional-test-output,unified-test-platform-android-test-plugin-host-apk-installer,unified-test-platform-android-test-plugin-host-coverage,unified-test-platform-android-test-plugin-host-device-info,unified-test-platform-android-test-plugin-host-emulator-control,unified-test-platform-android-test-plugin-host-logcat,unified-test-platform-android-test-plugin-result-listener-gradle,unified-test-platform-core,unified-test-platform-gradle-work-action,unified-test-platform-launcher
|
||||
org.jspecify:jspecify:1.0.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
org.jetbrains.kotlinx:kotlinx-coroutines-test-jvm:1.11.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestCompileClasspath,releaseAndroidTestRuntimeClasspath
|
||||
org.jetbrains.kotlinx:kotlinx-coroutines-test:1.11.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestCompileClasspath,releaseAndroidTestRuntimeClasspath
|
||||
org.jetbrains.kotlinx:kotlinx-serialization-bom:1.7.3=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestCompileClasspath,releaseAndroidTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
org.jetbrains.kotlinx:kotlinx-serialization-core-jvm:1.7.3=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestCompileClasspath,releaseAndroidTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
org.jetbrains.kotlinx:kotlinx-serialization-core:1.7.3=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestCompileClasspath,releaseAndroidTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
org.jetbrains:annotations:13.0=composeMappingProducerClasspath,debugRuntimeClasspathCopy,kotlinAbiValidationCompatClasspath,kotlinBuildToolsApiClasspath,kotlinCompilerClasspath,kotlinCompilerPluginClasspathDebug,kotlinCompilerPluginClasspathDebugAndroidTest,kotlinCompilerPluginClasspathDebugUnitTest,kotlinCompilerPluginClasspathRelease,kotlinCompilerPluginClasspathReleaseAndroidTest,releaseRuntimeClasspathCopy
|
||||
org.jetbrains:annotations:23.0.0=androidLintTool,debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestCompileClasspath,releaseAndroidTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath,unified-test-platform-android-device-provider-ddmlib,unified-test-platform-android-driver-instrumentation,unified-test-platform-android-test-plugin,unified-test-platform-android-test-plugin-host-additional-test-output,unified-test-platform-android-test-plugin-host-apk-installer,unified-test-platform-android-test-plugin-host-coverage,unified-test-platform-android-test-plugin-host-device-info,unified-test-platform-android-test-plugin-host-emulator-control,unified-test-platform-android-test-plugin-host-logcat,unified-test-platform-android-test-plugin-result-listener-gradle,unified-test-platform-core,unified-test-platform-gradle-work-action,unified-test-platform-launcher
|
||||
org.jspecify:jspecify:1.0.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestCompileClasspath,releaseAndroidTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
org.jvnet.staxex:stax-ex:1.8.1=androidLintTool,unified-test-platform-android-test-plugin-result-listener-gradle
|
||||
org.mockito.kotlin:mockito-kotlin:6.3.0=debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath
|
||||
org.mockito:mockito-core:5.23.0=debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath
|
||||
org.nanohttpd:nanohttpd:2.3.1=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
org.nanohttpd:nanohttpd:2.3.1=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseAndroidTestCompileClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
|
||||
org.objenesis:objenesis:3.3=debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath
|
||||
org.ow2.asm:asm-analysis:9.9=androidLintTool
|
||||
org.ow2.asm:asm-commons:9.8=debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath
|
||||
@ -461,4 +461,4 @@ org.robolectric:shadows-framework:4.15=debugUnitTestCompileClasspath,debugUnitTe
|
||||
org.robolectric:utils-reflector:4.15=debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath
|
||||
org.robolectric:utils:4.15=debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath
|
||||
org.yaml:snakeyaml:2.4=debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath
|
||||
empty=androidApis,androidJdkImage,androidTestUtil,coreLibraryDesugaring,debugAndroidTestAnnotationProcessorClasspath,debugAndroidTestImplementationDependenciesMetadata,debugAnnotationProcessorClasspath,debugImplementationDependenciesMetadata,debugReverseMetadataValues,debugUnitTestAnnotationProcessorClasspath,debugUnitTestImplementationDependenciesMetadata,kotlinCompilerPluginClasspath,lintChecks,lintPublish,releaseAnnotationProcessorClasspath,releaseImplementationDependenciesMetadata,releaseReverseMetadataValues
|
||||
empty=androidApis,androidJdkImage,androidTestUtil,coreLibraryDesugaring,debugAndroidTestAnnotationProcessorClasspath,debugAndroidTestImplementationDependenciesMetadata,debugAnnotationProcessorClasspath,debugImplementationDependenciesMetadata,debugReverseMetadataValues,debugUnitTestAnnotationProcessorClasspath,debugUnitTestImplementationDependenciesMetadata,kotlinCompilerPluginClasspath,lintChecks,lintPublish,releaseAndroidTestAnnotationProcessorClasspath,releaseAnnotationProcessorClasspath,releaseImplementationDependenciesMetadata,releaseReverseMetadataValues
|
||||
|
||||
8
app/proguard-rules.pro
vendored
8
app/proguard-rules.pro
vendored
@ -24,6 +24,14 @@
|
||||
<init>();
|
||||
}
|
||||
|
||||
# ML Kit discovers Firebase component registrars from manifest metadata and calls
|
||||
# their no-argument constructors reflectively. Keeping the class alone does not
|
||||
# retain that constructor in R8 full mode, leaving BarcodeScanning without its
|
||||
# registered components and crashing when the verification scanner opens.
|
||||
-keepclassmembers class * implements com.google.firebase.components.ComponentRegistrar {
|
||||
public <init>();
|
||||
}
|
||||
|
||||
# Keep Tor implementation (always included)
|
||||
-keep class com.bitchat.android.net.RealTorProvider { *; }
|
||||
|
||||
|
||||
9
app/release-instrumentation.pro
Normal file
9
app/release-instrumentation.pro
Normal file
@ -0,0 +1,9 @@
|
||||
# Keep only the real analyzer entry point for tests. Do not keep ML Kit internals:
|
||||
# doing so would hide regressions in the production dependency-discovery graph.
|
||||
-keep class com.bitchat.android.ui.QRCodeAnalyzer {
|
||||
<init>(kotlin.jvm.functions.Function1);
|
||||
public void analyze(androidx.camera.core.ImageProxy);
|
||||
}
|
||||
# CameraX's public frame interfaces are implemented by the synthetic test source.
|
||||
-keep interface androidx.camera.core.ImageProxy { *; }
|
||||
-keep interface androidx.camera.core.ImageInfo { *; }
|
||||
@ -0,0 +1,122 @@
|
||||
package com.bitchat.android.ui
|
||||
|
||||
import android.graphics.ImageFormat
|
||||
import android.media.ImageReader
|
||||
import android.media.ImageWriter
|
||||
import android.os.Handler
|
||||
import android.os.HandlerThread
|
||||
import androidx.camera.core.ImageInfo
|
||||
import androidx.camera.core.ImageProxy
|
||||
import java.lang.reflect.Proxy
|
||||
import java.util.concurrent.CountDownLatch
|
||||
import java.util.concurrent.TimeUnit
|
||||
import java.util.concurrent.atomic.AtomicReference
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
|
||||
/** Calls the same analyzer constructor as ScannerView, with minified ML Kit dependencies. */
|
||||
class BarcodeScannerInstrumentedTest {
|
||||
@Test
|
||||
fun scannerCreationCompletesAfterColdStart() {
|
||||
QRCodeAnalyzer { }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun realAnalyzerDecodesSyntheticCameraFrameAndClosesIt() {
|
||||
val decoded = AtomicReference<String>()
|
||||
val codeReceived = CountDownLatch(1)
|
||||
val frameClosed = CountDownLatch(1)
|
||||
val analyzer = QRCodeAnalyzer {
|
||||
decoded.set(it)
|
||||
codeReceived.countDown()
|
||||
}
|
||||
val thread = HandlerThread("synthetic-scanner-frame").apply { start() }
|
||||
// A fixed, synthetic QR encodes "bitchat:test-scanner". No camera or permissions needed.
|
||||
val modules = java.util.ArrayList<String>()
|
||||
val rows = java.util.StringTokenizer(QR_MODULES)
|
||||
while (rows.hasMoreTokens()) modules.add(rows.nextToken())
|
||||
val scale = 8
|
||||
val size = modules.size * scale
|
||||
val reader = ImageReader.newInstance(size, size, ImageFormat.YUV_420_888, 2)
|
||||
val writer = ImageWriter.newInstance(reader.surface, 2)
|
||||
try {
|
||||
reader.setOnImageAvailableListener({ source ->
|
||||
val image = source.acquireNextImage() ?: return@setOnImageAvailableListener
|
||||
val info = Proxy.newProxyInstance(ImageInfo::class.java.classLoader,
|
||||
arrayOf(ImageInfo::class.java)) { _, method, _ ->
|
||||
when (method.name) {
|
||||
"getRotationDegrees" -> 0
|
||||
else -> error("Unexpected frame info: ${method.name}")
|
||||
}
|
||||
} as ImageInfo
|
||||
val proxy = Proxy.newProxyInstance(ImageProxy::class.java.classLoader,
|
||||
arrayOf(ImageProxy::class.java)) { _, method, _ ->
|
||||
when (method.name) {
|
||||
"getImage" -> image
|
||||
"getImageInfo" -> info
|
||||
"close" -> { image.close(); frameClosed.countDown(); null }
|
||||
else -> error("Unexpected frame access: ${method.name}")
|
||||
}
|
||||
} as ImageProxy
|
||||
analyzer.analyze(proxy)
|
||||
}, Handler(thread.looper))
|
||||
val input = writer.dequeueInputImage()
|
||||
input.planes.forEachIndexed { planeIndex, plane ->
|
||||
val planeSize = if (planeIndex == 0) size else size / 2
|
||||
for (y in 0 until planeSize) for (x in 0 until planeSize) {
|
||||
val value = if (planeIndex != 0) 128 else
|
||||
if (modules[y / scale][x / scale] == '1') 0 else 255
|
||||
plane.buffer.put(y * plane.rowStride + x * plane.pixelStride, value.toByte())
|
||||
}
|
||||
}
|
||||
writer.queueInputImage(input)
|
||||
assertTrue("Real release scanner must decode the frame", codeReceived.await(20, TimeUnit.SECONDS))
|
||||
assertEquals("bitchat:test-scanner", decoded.get())
|
||||
assertTrue("Analyzer must release the camera frame", frameClosed.await(5, TimeUnit.SECONDS))
|
||||
} finally {
|
||||
writer.close()
|
||||
reader.close()
|
||||
thread.quitSafely()
|
||||
thread.join(5_000)
|
||||
}
|
||||
}
|
||||
|
||||
private companion object {
|
||||
const val QR_MODULES = """
|
||||
000000000000000000000000000000000
|
||||
000000000000000000000000000000000
|
||||
000000000000000000000000000000000
|
||||
000000000000000000000000000000000
|
||||
000011111110110001001011111110000
|
||||
000010000010000001000010000010000
|
||||
000010111010100111010010111010000
|
||||
000010111010110101100010111010000
|
||||
000010111010111100111010111010000
|
||||
000010000010010011100010000010000
|
||||
000011111110101010101011111110000
|
||||
000000000000010100000000000000000
|
||||
000011110010100000101100111010000
|
||||
000011001001110000101001000100000
|
||||
000011101010000001101000100000000
|
||||
000010001001000111011100011100000
|
||||
000010100011110100010111111000000
|
||||
000000110101011101111011100110000
|
||||
000001101110100010100001110100000
|
||||
000010111101011011000100100000000
|
||||
000000101111110100001111101100000
|
||||
000000000000101011011000100010000
|
||||
000011111110000110001010110110000
|
||||
000010000010010010011000110100000
|
||||
000010111010010111001111100100000
|
||||
000010111010100011100111110110000
|
||||
000010111010101111011010110100000
|
||||
000010000010110000000101011000000
|
||||
000011111110101010110011001110000
|
||||
000000000000000000000000000000000
|
||||
000000000000000000000000000000000
|
||||
000000000000000000000000000000000
|
||||
000000000000000000000000000000000
|
||||
"""
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,29 @@
|
||||
package com.bitchat.android.ui
|
||||
|
||||
import android.app.Application
|
||||
import android.app.Instrumentation
|
||||
import android.content.Context
|
||||
import android.os.Bundle
|
||||
import org.junit.runner.JUnitCore
|
||||
|
||||
/** Minimal runner avoids instrumentation-only tracing dependencies removed by target R8. */
|
||||
class ReleaseScannerTestRunner : Instrumentation() {
|
||||
override fun newApplication(cl: ClassLoader, className: String, context: Context): Application =
|
||||
super.newApplication(cl, Application::class.java.name, context)
|
||||
|
||||
override fun onCreate(arguments: Bundle?) {
|
||||
super.onCreate(arguments)
|
||||
start()
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
val result = JUnitCore.runClasses(BarcodeScannerInstrumentedTest::class.java)
|
||||
val report = Bundle().apply {
|
||||
putString("stream", buildString {
|
||||
append("Tests run: ${result.runCount}, failures: ${result.failureCount}\n")
|
||||
result.failures.forEach { append(it.trace).append('\n') }
|
||||
})
|
||||
}
|
||||
finish(if (result.wasSuccessful()) -1 else 0, report)
|
||||
}
|
||||
}
|
||||
@ -1,398 +1,378 @@
|
||||
Relay URL,Latitude,Longitude
|
||||
inbox.scuba323.com,40.8218,-74.45
|
||||
relay.pornstr.net,53.3498,-6.26031
|
||||
relay.btcforplebs.com,43.6532,-79.3832
|
||||
nostr-01.yakihonne.com,1.32123,103.695
|
||||
relay.agentry.com,42.8864,-78.8784
|
||||
offchain.pub:443,39.1585,-94.5728
|
||||
dev.relay.stream,43.6532,-79.3832
|
||||
cyberspace.nostr1.com,40.7057,-74.0136
|
||||
relay.shadowbip.com,54.352,18.6466
|
||||
nip85-staging.nosfabrica.com,39.0997,-94.5786
|
||||
portal-relay.pareto.space,53.5488,9.98717
|
||||
relay.ru.ac.th,13.7607,100.627
|
||||
nostr-relay.corb.net,39.6478,-104.988
|
||||
nostrelay.circum.space,52.2245,8.826
|
||||
relay.satsapp.me,50.4754,12.3683
|
||||
nostr-01.uid.ovh,50.9871,2.12554
|
||||
nostr.dzo-hadar.ts.net,35.6764,139.65
|
||||
vault.iris.to:443,43.6532,-79.3832
|
||||
relay.nmail.li,50.9871,2.12554
|
||||
nostr.janx.com,43.6532,-79.3832
|
||||
relay.satmaxt.xyz:443,43.6532,-79.3832
|
||||
nos.lol:443,50.4754,12.3683
|
||||
nostr.mad-social.net,50.4754,12.3683
|
||||
relay.flashapp.me,43.6548,-79.3885
|
||||
relay.layer.systems,43.6532,-79.3832
|
||||
relay.novospes.com,43.6532,-79.3832
|
||||
relay.satmaxt.xyz,43.6532,-79.3832
|
||||
relay.getvia.xyz,60.1699,24.9384
|
||||
vidar.nestoris.online,52.3676,4.90414
|
||||
nostr.yutakobayashi.com,43.6532,-79.3832
|
||||
dev-relay.nostreon.com,60.1699,24.9384
|
||||
nostrcity-club.fly.dev:443,38.7946,-106.535
|
||||
nostr.oxtr.dev,50.4754,12.3683
|
||||
relay.lacrypta.ar,43.6532,-79.3832
|
||||
node.kommonzenze.de,49.4521,11.0767
|
||||
bitchat.nostr1.com,40.7057,-74.0136
|
||||
nostr.trepechov.com,49.4543,11.0746
|
||||
articles.layer3.news:443,37.3387,-121.885
|
||||
relay.ditto.pub,43.6532,-79.3832
|
||||
treuzkas.branruz.com,48.8575,2.35138
|
||||
nostr.plantroon.com:443,50.1013,8.62643
|
||||
nostr.mas-family.eu,60.3478,15.7505
|
||||
relay.nostrfy.org,35.6916,139.768
|
||||
relay.nostrmap.net,60.1699,24.9384
|
||||
woods-leader-permits-yamaha.trycloudflare.com,43.6532,-79.3832
|
||||
nostr.middling.mydns.jp,35.8099,140.12
|
||||
adre.su,59.9311,30.3609
|
||||
relay.s-w.art,43.6532,-79.3832
|
||||
nostr.thebiglake.org,32.71,-96.6745
|
||||
relay.satlantis.io,39.0438,-77.4874
|
||||
relay.routstr.com,59.4016,17.9455
|
||||
relay.nostr.space,43.6532,-79.3832
|
||||
relay.wavlake.com:443,41.2619,-95.8608
|
||||
relay.islandbitcoin.com,20.3898,78.0903
|
||||
nostr.tabordalab.com,60.1699,24.9384
|
||||
nostr-relay.cbrx.io,43.6532,-79.3832
|
||||
testnet.samt.st,43.6532,-79.3832
|
||||
temp.iris.to,43.6532,-79.3832
|
||||
freelay.sovbit.host,60.1699,24.9384
|
||||
relay.grammstr.com,38.7946,-106.535
|
||||
relay.nostronchain.com,43.6532,-79.3832
|
||||
relay.hazeem.xyz,43.6532,-79.3832
|
||||
relayone.geektank.ai,39.0997,-94.5786
|
||||
nostr-relay.corb.net:443,39.6478,-104.988
|
||||
relay.olas.app,60.1699,24.9384
|
||||
relay.44billion.net,43.6532,-79.3832
|
||||
relay.mitosystem.net,-23.5558,-46.6396
|
||||
relay.ltgnet.work:8443,51.0511,-114.075
|
||||
nostr.chaima.info:443,50.1109,8.68213
|
||||
nostrcity-club.fly.dev,38.7946,-106.535
|
||||
nostr.sathoarder.com,48.5734,7.75211
|
||||
relay.fountain.fm:443,43.6532,-79.3832
|
||||
nostr.txlyre.website,60.1699,24.9384
|
||||
nostr.planix.org,43.6532,-79.3832
|
||||
relay.chorus.community,50.1109,8.68213
|
||||
dev-relay.nostr.space,43.6532,-79.3832
|
||||
relay.satsmarkt.club,52.3676,4.90414
|
||||
relay.bullishbounty.com,43.6532,-79.3832
|
||||
relay.lanacoin-eternity.com,40.8302,-74.1299
|
||||
social.amanah.eblessing.co,48.1046,11.6002
|
||||
relay-arg.zombi.cloudrodion.com,1.35208,103.82
|
||||
relay.primal.net,43.6532,-79.3832
|
||||
nostr-relay.zimage.com,34.282,-118.439
|
||||
relay.veganostr.com:443,60.1699,24.9384
|
||||
relay.hivetalk.org,40.8302,-74.1299
|
||||
nostr.whitenode45.ddns.net,40.55,-74.4758
|
||||
nostr-rs-relay-ishosta.phamthanh.me,43.6532,-79.3832
|
||||
relay.edufeed.org,49.4521,11.0767
|
||||
nostr.pbfs.io:443,50.4754,12.3683
|
||||
relay.substation.ninja,43.6532,-79.3832
|
||||
nostr.liberty.fans,36.8767,-89.5879
|
||||
nostr-verified.wellorder.net,45.5201,-122.99
|
||||
relay.hivescope.xyz,40.4332,-3.62431
|
||||
nostr2.girino.org:443,43.6532,-79.3832
|
||||
relay.cloistr.xyz,42.337,-77.3178
|
||||
relay.agora.social,50.7383,15.0648
|
||||
relayone.soundhsa.com,39.0997,-94.5786
|
||||
nostr.0x7e.xyz,47.4949,8.71954
|
||||
nostr-rs-relay.dev.fedibtc.com:443,39.0438,-77.4874
|
||||
relay-na1.metanomalist.com,43.6532,-79.3832
|
||||
relay.bitmacro.cloud,43.6532,-79.3832
|
||||
relay.iyou.me,64.1466,-21.9426
|
||||
nostr.bitcoiner.social,47.6743,-117.112
|
||||
relay.wisp.talk:443,49.4543,11.0746
|
||||
relay.valleirug.nl,52.0274,5.62427
|
||||
chat-relay.zap-work.com,43.6532,-79.3832
|
||||
nostr.2b9t.xyz,34.0549,-118.243
|
||||
groups.beginningend.com,35.2227,-97.4786
|
||||
relay.edufeed.org:443,49.4521,11.0767
|
||||
nrs-02.darkcloudarcade.com,39.9526,-75.1652
|
||||
relay.angor.io,48.1046,11.6002
|
||||
relay.jmoose.rocks,32.7767,-96.797
|
||||
nostr.mom,50.4754,12.3683
|
||||
x.kojira.io,43.6532,-79.3832
|
||||
cs-relay.nostrdev.com:443,50.4754,12.3683
|
||||
nostr.computingcache.com,45.5341,-122.956
|
||||
relay-dev.satlantis.io,39.0438,-77.4874
|
||||
nostr.jcera.eu,43.6532,-79.3832
|
||||
relay.aarpia.com,37.3986,-121.964
|
||||
relay.nostrian-conquest.com,41.223,-111.974
|
||||
relay.fiatdenier.com,43.7787,-79.5393
|
||||
relayrs.notoshi.win:443,43.6532,-79.3832
|
||||
bread.nostrsms.com,40.8218,-74.45
|
||||
test.relays.nuts.cash,52.3676,4.90414
|
||||
nostrrelay.taylorperron.com,45.5029,-73.5723
|
||||
relay.jmoose.rocks:443,32.7767,-96.797
|
||||
relay.tapestry.ninja,40.8054,-74.0241
|
||||
nostr.hetuniversum.nl,43.6532,-79.3832
|
||||
public.crostr.com:443,43.6532,-79.3832
|
||||
nostr.trevena.info,43.6532,-79.3832
|
||||
relay.chorus.community:443,50.1109,8.68213
|
||||
relay.sharegap.net,43.6532,-79.3832
|
||||
relayone.geektank.ai:443,39.0997,-94.5786
|
||||
nip85.nosfabrica.com,39.0997,-94.5786
|
||||
yabu.me,35.6096,139.73
|
||||
nostr.bitcoiner.social:443,47.6743,-117.112
|
||||
support.flotilla.social,32.9483,-96.7299
|
||||
strfry.bonsai.com:443,39.0438,-77.4874
|
||||
nostr-relayflare.enpitsulin.workers.dev,43.6532,-79.3832
|
||||
relay.crovaxon.de,53.0777,7.41516
|
||||
relay.froth.zone,60.1699,24.9384
|
||||
r.senx.io,50.6924,3.20113
|
||||
relay2.nostrasia.net,43.6532,-79.3832
|
||||
relay.decentralia.fr,48.122,11.589
|
||||
relay.mappingbitcoin.com,43.6532,-79.3832
|
||||
nosflare-leefcore.leefcore.workers.dev,43.6532,-79.3832
|
||||
chorus.mikedilger.com:444,-36.8906,174.794
|
||||
nostr.wild-vibes.ts.net,48.8566,2.35222
|
||||
spamspamspamspam.rest,43.6532,-79.3832
|
||||
relay.wisp.talk,49.4543,11.0746
|
||||
relay.bornheimer.app,50.1109,8.68213
|
||||
nostr-dev.wellorder.net,45.5201,-122.99
|
||||
strfry.shock.network:443,39.0438,-77.4874
|
||||
nostr.mikoshi.de,52.52,13.405
|
||||
nostr.christiansass.de,50.1109,8.68213
|
||||
relay.14898.hns.one,42.8864,-78.8784
|
||||
porchlight.social,43.6532,-79.3832
|
||||
nostr.twinkle.lol,51.902,7.6657
|
||||
nostr.pbfs.io,50.4754,12.3683
|
||||
zencrown.relay2aeon.quest,52.3061,4.6907
|
||||
21milionidinostr.duckdns.org,45.519,9.08615
|
||||
test-relay.uid.ovh,48.5734,7.75211
|
||||
nexus.libernet.app,43.6532,-79.3832
|
||||
bitcoiner.social,47.6743,-117.112
|
||||
syb.lol,43.6532,-79.3832
|
||||
relay.directsponsor.net,42.8864,-78.8784
|
||||
budabit.nostr1.com,40.7057,-74.0136
|
||||
relay.budabit.club,49.4543,11.0746
|
||||
relay.monostr.com,50.4754,12.3683
|
||||
relay.rebelbase.social,50.965,5.50082
|
||||
nostr.hifish.org,47.4009,8.57814
|
||||
nostr.21crypto.ch,47.5356,8.73209
|
||||
nostr.emanuelemiani.it,45.778,8.79414
|
||||
relay.kizuna-miki.com,35.694,139.754
|
||||
relay.binaryrobot.com:443,43.6532,-79.3832
|
||||
spurline.safebox.dev,43.6532,-79.3832
|
||||
buzz.cashu.space,43.6532,-79.3832
|
||||
relay.plebeian.market,50.1109,8.68213
|
||||
relay.mleku.dev,32.7767,-96.797
|
||||
nostr.purpura.cloud,43.6532,-79.3832
|
||||
relay.binaryrobot.com,43.6532,-79.3832
|
||||
relay.paulstephenborile.com,49.4543,11.0746
|
||||
relay2.fiatdenier.com,50.1013,8.62643
|
||||
cs-relay.nostrdev.com,50.4754,12.3683
|
||||
nostr.davenov.com,50.1109,8.68213
|
||||
relay.nostrhub.fr,48.1045,11.6004
|
||||
nostr.spaceshell.xyz,43.6532,-79.3832
|
||||
relay.gorrdy.cz,50.1109,8.68213
|
||||
relay.nostrmap.net:443,60.1699,24.9384
|
||||
relay.getsafebox.app,43.6532,-79.3832
|
||||
relay.lanavault.space,60.1699,24.9384
|
||||
relay.veganostr.com,60.1699,24.9384
|
||||
prl.plus,54.9486,21.7283
|
||||
relay.nostr.blockhenge.com,39.0438,-77.4874
|
||||
nostr.quali.chat,60.1699,24.9384
|
||||
nostr.nodesmap.com,59.3327,18.0656
|
||||
relay.beginningend.com,35.2227,-97.4786
|
||||
relay2.0xprivacy.online,43.6532,-79.3832
|
||||
relay1.privkey.io,25.7617,-80.1918
|
||||
relay-dev.gulugulu.moe:443,43.6532,-79.3832
|
||||
relay.lotek-distro.com,43.6532,-79.3832
|
||||
relay.noeudlibre.fr,50.6924,3.20113
|
||||
relay.cyberguy.fyi,52.3676,4.90414
|
||||
nostr-relay.xbytez.io,50.6924,3.20113
|
||||
relay.kaleidoswap.com,47.3769,8.54169
|
||||
relay.bnos.space,43.6532,-79.3832
|
||||
purplerelay.com,43.6532,-79.3832
|
||||
hasky.chat,40.7862,-74.0743
|
||||
relay.snort.social,53.3498,-6.26031
|
||||
nostr.linky.fit,50.1109,8.68213
|
||||
nostr.plantroon.com,50.1013,8.62643
|
||||
testr.nymble.world,40.8054,-74.0241
|
||||
nostr.tac.lol,47.4748,-122.273
|
||||
cdn.satellite.earth,40.8302,-74.1299
|
||||
dm-test-strfry-discovery.samt.st,43.6532,-79.3832
|
||||
wisp.djorivaltech.com,43.6532,-79.3832
|
||||
nostr.trcjr.xyz,35.3821,-78.5486
|
||||
offchain.pub,39.1585,-94.5728
|
||||
nostras.app,38.7946,-106.535
|
||||
relay.manneken.brussels,49.4543,11.0746
|
||||
relay.laantungir.net,-19.4692,-42.5315
|
||||
nostr-relay.xbytez.io:443,50.6924,3.20113
|
||||
relay.gulugulu.moe,43.6532,-79.3832
|
||||
relayrs.notoshi.win,43.6532,-79.3832
|
||||
vault.iris.to,43.6532,-79.3832
|
||||
relay.getsafebox.app:443,43.6532,-79.3832
|
||||
top.testrelay.top,43.6532,-79.3832
|
||||
relay.t6x.host,40.8302,-74.1299
|
||||
relay.vrtmrz.net:443,43.6532,-79.3832
|
||||
relay.ohstr.com,43.6532,-79.3832
|
||||
nostr.wecsats.io,43.6532,-79.3832
|
||||
ynostr.yael.at,60.1699,24.9384
|
||||
infinity-signal-relay.digitalforlifeagency.workers.dev,43.6532,-79.3832
|
||||
nostr.islandarea.net:443,35.4669,-97.6473
|
||||
relay.favillakey.io,-27.4705,153.026
|
||||
nostr.self-determined.de,51.4379,14.2355
|
||||
nostr-pub.wellorder.net,45.5201,-122.99
|
||||
relay.trotters.cc,43.6532,-79.3832
|
||||
nostr-rs-relay.dev.fedibtc.com,39.0438,-77.4874
|
||||
nostr2.girino.org,43.6532,-79.3832
|
||||
nostr.rtvslawenia.com,49.4543,11.0746
|
||||
nostr.stakey.net:443,52.6485,4.73787
|
||||
relay.artx.market,43.6548,-79.3885
|
||||
nos.lol,50.4754,12.3683
|
||||
relay.nearhood.co.uk,51.5134,-0.0890675
|
||||
nostr.pro.aw,43.6532,-79.3832
|
||||
relay.piazza.today,48.122,11.589
|
||||
relay.underorion.se,51.4998,-0.011072
|
||||
nostr.oxtr.dev:443,50.4754,12.3683
|
||||
relay.bitos.space,43.6532,-79.3832
|
||||
nostrelay.circum.space:443,52.2245,8.826
|
||||
relay.klabo.world,47.674,-122.122
|
||||
nostr.spicyz.io,43.6532,-79.3832
|
||||
relay.pyramid.li,47.4093,8.46503
|
||||
strfry.shock.network,39.0438,-77.4874
|
||||
relay.lightning.pub:443,39.0438,-77.4874
|
||||
nostr.snowbla.de,50.4754,12.3683
|
||||
relay.wavlake.com,41.2619,-95.8608
|
||||
relay2.orangesync.tech,40.7128,-74.006
|
||||
relay.mostro.network,40.8302,-74.1299
|
||||
relay-dev.gulugulu.moe,43.6532,-79.3832
|
||||
relay.orangesync.tech,40.7128,-74.006
|
||||
chat-relay.zap-work.com:443,43.6532,-79.3832
|
||||
relay.wasabiwallet.io,43.6532,-79.3832
|
||||
relay.kilombino.com,43.6532,-79.3832
|
||||
bridge.tagomago.me,42.3601,-71.0589
|
||||
relay.loveisbitcoin.com,43.6532,-79.3832
|
||||
relay.staging.plebeian.market,51.5072,-0.127586
|
||||
relay.nostrcade.com,37.79,-122.401
|
||||
relay.nostr.com,50.1109,8.68213
|
||||
relay02.lnfi.network,35.6764,139.65
|
||||
nostr.debate.report,50.1109,8.68213
|
||||
relay.gulugulu.moe:443,43.6532,-79.3832
|
||||
n.bogs.ccwu.cc,43.6532,-79.3832
|
||||
relay.twentyone.ist,43.7696,-79.3309
|
||||
nostr.mom:443,50.4754,12.3683
|
||||
relay.czas.plus,50.1109,8.68213
|
||||
relay.bullishbounty.com:443,43.6532,-79.3832
|
||||
no.str.cr,8.96171,-83.5246
|
||||
nostr.88mph.life,52.1941,-2.21905
|
||||
relay.trotters.cc:443,43.6532,-79.3832
|
||||
nostr.unkn0wn.world,47.4464,8.21073
|
||||
relay.dyne.org,49.0291,8.35705
|
||||
nostr.n7ekb.net,47.4941,-122.294
|
||||
nostr.sathoarder.com:443,48.5734,7.75211
|
||||
relay.guggero.org,46.5971,9.59652
|
||||
relay.bowlafterbowl.com,32.9483,-96.7299
|
||||
wheat.oslim.dev,43.6532,-79.3832
|
||||
nostr.myshosholoza.co.za,52.3676,4.90414
|
||||
nostr.tagomago.me,42.3601,-71.0589
|
||||
relay.snotr.nl:49999,51.9758,4.31389
|
||||
relay.lightning.pub,39.0438,-77.4874
|
||||
0x-nostr-relay.fly.dev,38.7946,-106.535
|
||||
relay.nostrconnect.com,41.5695,-83.9786
|
||||
relay-can.zombi.cloudrodion.com,43.6532,-79.3832
|
||||
relay.nostr.dev.br,48.1046,11.6002
|
||||
relay.mrmave.work,43.6532,-79.3832
|
||||
nostr.rtvslawenia.com:443,49.4543,11.0746
|
||||
nostr.data.haus,50.4754,12.3683
|
||||
nostr.chaima.info,50.1109,8.68213
|
||||
nostr.relay.hedwig.sh,60.1699,24.9384
|
||||
social.hns.one,42.8864,-78.8784
|
||||
relay.pacsarcade.org,32.7767,-96.797
|
||||
auth.nostr1.com,40.7057,-74.0136
|
||||
relayone.soundhsa.com:443,39.0997,-94.5786
|
||||
nostr.azzamo.net,52.2633,21.0283
|
||||
nostr.eu.freedomrelay.dev,49.0524,14.4347
|
||||
basspistol.org,49.0291,8.35696
|
||||
relay.nostrops.com,32.71,-96.6745
|
||||
dm-test-nostr-rs-42-disabled.samt.st,43.6532,-79.3832
|
||||
schnorr.me,43.6532,-79.3832
|
||||
no.str.cr:443,8.96171,-83.5246
|
||||
nostr.snowbla.de:443,50.4754,12.3683
|
||||
relay.openspecs.uid.ovh,50.9871,2.12554
|
||||
nostr.nestoris.online,55.568,37.5853
|
||||
relay.plebeian.market:443,50.1109,8.68213
|
||||
relay.mwaters.net,50.9871,2.12554
|
||||
relay.conduit.market,38.7946,-106.535
|
||||
relay.wellorder.net,45.5201,-122.99
|
||||
relay.nostriot.com,41.5695,-83.9786
|
||||
nostr.tbxnetworx.de,47.8226,10.0064
|
||||
relay.zone667.com,60.1699,24.9384
|
||||
public.crostr.com,43.6532,-79.3832
|
||||
relay.colombiap2p.com,43.6532,-79.3832
|
||||
soloco.nl,43.6532,-79.3832
|
||||
relay.orly.dev,32.7767,-96.797
|
||||
relay01.lnfi.network,35.6764,139.65
|
||||
relay-fra.zombi.cloudrodion.com,48.8566,2.35222
|
||||
nostr.notribe.net,40.8302,-74.1299
|
||||
relay.shychat.lol,39.8965,-80.1792
|
||||
relay.cypherguide.org,1.32123,103.695
|
||||
strfry.bonsai.com,39.0438,-77.4874
|
||||
relay.decentnewsroom.com,50.4754,12.3683
|
||||
relay.atsocy.com,43.6532,-79.3832
|
||||
scores.brainstorm.world,39.0997,-94.5786
|
||||
relay.nostr.bond,40.1885,29.061
|
||||
nostr.fullstackcash.net,45.5201,-122.99
|
||||
nostr.islandarea.net,35.4669,-97.6473
|
||||
relay.illuminodes.com,43.6532,-79.3832
|
||||
relay.samt.st,40.8302,-74.1299
|
||||
relay.pprgb.app,52.52,13.405
|
||||
relay.jcera.eu,43.6532,-79.3832
|
||||
relay.nostreon.com,60.1699,24.9384
|
||||
myvoiceourstory.org,44.8484,-123.234
|
||||
relay.mypathtofire.de,42.8864,-78.8784
|
||||
nostr.stakey.net,52.6485,4.73787
|
||||
schnorr.me:443,43.6532,-79.3832
|
||||
conduit-congee.fly.dev,38.7946,-106.535
|
||||
offchain.bostr.online,43.6532,-79.3832
|
||||
staging.yabu.me,35.6096,139.73
|
||||
nostr.synalysis.com,50.4754,12.3683
|
||||
relay.scuba323.com,40.8218,-74.45
|
||||
nostr.hoppe-relay.it.com,42.8864,-78.8784
|
||||
relay.paulstephenborile.com:443,49.4543,11.0746
|
||||
memlay.v0l.io,53.3498,-6.26031
|
||||
nostr-02.uid.ovh,50.9871,2.12554
|
||||
articles.layer3.news,37.3387,-121.885
|
||||
nostr.2b9t.xyz:443,34.0549,-118.243
|
||||
relay.chatbett.de,40.7128,-74.006
|
||||
relay.staging.plebeian.market:443,51.5072,-0.127586
|
||||
relay.nostr.black,43.6532,-79.3832
|
||||
relay.solomonstr.com,43.6532,-79.3832
|
||||
relay.nostr.net,43.6532,-79.3832
|
||||
dm-test-strfry-generic.samt.st,43.6532,-79.3832
|
||||
nostr.girino.org,43.6532,-79.3832
|
||||
bcast.girino.org,43.6532,-79.3832
|
||||
n.kojira.io,43.6532,-79.3832
|
||||
relay.sigit.io,50.4754,12.3683
|
||||
bruh.samt.st,43.6532,-79.3832
|
||||
relay.i9.eti.br,43.6532,-79.3832
|
||||
relay.degmods.com,50.4754,12.3683
|
||||
pymd.kamp.site,38.9072,-77.0369
|
||||
relay.internationalright-wing.org,-22.4692,-48.9875
|
||||
relay.earthly.city,34.1749,-118.54
|
||||
relay-us.zombi.cloudrodion.com,40.7862,-74.0743
|
||||
relay.yoinekodo.jp,43.6532,-79.3832
|
||||
cache.trustr.ing,43.6548,-79.3885
|
||||
relay2.angor.io,48.1046,11.6002
|
||||
relay.lanacoin-eternity.com:443,40.8302,-74.1299
|
||||
relay.nostar.org,43.6532,-79.3832
|
||||
nostr-kyomu-haskell.onrender.com,37.7775,-122.397
|
||||
relay.nostx.io,43.6532,-79.3832
|
||||
relay.sincensura.org,43.6532,-79.3832
|
||||
relay.qstr.app,50.1109,8.68213
|
||||
relay2.veganostr.com,60.1699,24.9384
|
||||
denimroad.feeds.relay.tools,38.6327,-90.1961
|
||||
relay.nostr-check.me,43.6532,-79.3832
|
||||
nostr.eu.freedomrelay.dev,49.0524,14.4347
|
||||
relay.44billion.net,43.6532,-79.3832
|
||||
nostr.debate.report,50.1109,8.68213
|
||||
relayrs.notoshi.win:443,43.6532,-79.3832
|
||||
top.testrelay.top,43.6532,-79.3832
|
||||
relay.satsmarkt.club,52.3676,4.90414
|
||||
nostr.plantroon.com:443,50.1013,8.62643
|
||||
relay.lightning.pub,39.0438,-77.4874
|
||||
nostr-rs-relay-ishosta.phamthanh.me,43.6532,-79.3832
|
||||
auth.nostr1.com,40.7057,-74.0136
|
||||
offchain.pub:443,39.1585,-94.5728
|
||||
relay.fundstr.me,42.3601,-71.0589
|
||||
openrelay.ziomc.com,41.8967,12.4822
|
||||
nostrride.io,37.3986,-121.964
|
||||
relay.vrtmrz.net,43.6532,-79.3832
|
||||
relay-testnet.k8s.layer3.news,37.3387,-121.885
|
||||
relay.keykeeper.world,40.7824,-74.0711
|
||||
relay.minibolt.info,43.6532,-79.3832
|
||||
relay.pacsarcade.org,32.7767,-96.797
|
||||
relay.tapestry.ninja,40.8054,-74.0241
|
||||
nostr.mom:443,50.4754,12.3683
|
||||
strfry.bonsai.com,39.0438,-77.4874
|
||||
relay.zone667.com,60.1699,24.9384
|
||||
relay.directsponsor.net,42.8864,-78.8784
|
||||
relay.veganostr.com,60.1699,24.9384
|
||||
relay.nostrmap.net:443,60.1699,24.9384
|
||||
relay.nostrconnect.com,41.5695,-83.9786
|
||||
nostr.hifish.org,47.4009,8.57814
|
||||
nostr.dzo-hadar.ts.net,35.6764,139.65
|
||||
relay.openspecs.uid.ovh,50.9871,2.12554
|
||||
no.str.cr,8.96171,-83.5246
|
||||
nostr.mikoshi.de,51.2829,6.7827
|
||||
nostr.chaima.info,50.1109,8.68213
|
||||
relay.wavlake.com:443,41.2619,-95.8608
|
||||
nostr-relay.xbytez.io,50.6924,3.20113
|
||||
relay5.bitransfer.org,43.6532,-79.3832
|
||||
relay.degmods.com,50.4754,12.3683
|
||||
nostr-relay.xbytez.io:443,50.6924,3.20113
|
||||
relay.lanacoin-eternity.com,40.8302,-74.1299
|
||||
relay.illuminodes.com,43.6532,-79.3832
|
||||
nostr.overmind.lol,43.6532,-79.3832
|
||||
nostr.hetuniversum.nl,43.6532,-79.3832
|
||||
relay.samt.st,40.8302,-74.1299
|
||||
nos.lol,50.4754,12.3683
|
||||
relay.valleirug.nl,52.0274,5.62427
|
||||
relay.nostar.org,43.6532,-79.3832
|
||||
wheat.oslim.dev,43.6532,-79.3832
|
||||
nostr.thebiglake.org,32.71,-96.6745
|
||||
relay.budabit.club,49.4543,11.0746
|
||||
cdn.satellite.earth,40.8302,-74.1299
|
||||
n.kojira.io,43.6532,-79.3832
|
||||
dm-test-strfry-discovery.samt.st,43.6532,-79.3832
|
||||
relay.nostrfy.org,35.6916,139.768
|
||||
nostr.janx.com,43.6532,-79.3832
|
||||
nostr.mad-social.net,50.4754,12.3683
|
||||
nostr.girino.org,43.6532,-79.3832
|
||||
relay.jmoose.rocks,32.7767,-96.797
|
||||
nostr.2b9t.xyz:443,34.0549,-118.243
|
||||
chat-relay.zap-work.com,43.6532,-79.3832
|
||||
relayrs.notoshi.win,43.6532,-79.3832
|
||||
relay1.privkey.io,25.7617,-80.1918
|
||||
nostr.emanuelemiani.it,45.778,8.79414
|
||||
relay.snort.social,53.3498,-6.26031
|
||||
relay.ohstr.com,43.6532,-79.3832
|
||||
relay.lightning.pub:443,39.0438,-77.4874
|
||||
nostr.islandarea.net:443,35.4669,-97.6473
|
||||
nostr.tagomago.me,42.3601,-71.0589
|
||||
fanfares.nostr1.com:443,40.7057,-74.0136
|
||||
nostr.snowbla.de:443,50.4754,12.3683
|
||||
vault.iris.to:443,43.6532,-79.3832
|
||||
test.relays.nuts.cash,52.3676,4.90414
|
||||
relay-us.zombi.cloudrodion.com,40.7862,-74.0743
|
||||
relay.nostrmap.net,60.1699,24.9384
|
||||
relay.nostrcade.com,37.79,-122.401
|
||||
relay.crovaxon.de,53.0777,7.41516
|
||||
relay.flashapp.me,43.6548,-79.3885
|
||||
nostr.oxtr.dev:443,50.4754,12.3683
|
||||
relay.decentnewsroom.com,50.4754,12.3683
|
||||
relay.liberbitworld.org,43.6532,-79.3832
|
||||
relayone.soundhsa.com:443,39.0997,-94.5786
|
||||
bucket.coracle.social,37.7775,-122.397
|
||||
nrl.ceskar.xyz,50.5145,16.0119
|
||||
relay.lanavault.space,60.1699,24.9384
|
||||
public.crostr.com,43.6532,-79.3832
|
||||
relay.thehakon.com,43.6532,-79.3832
|
||||
relay.wisp.talk,49.4543,11.0746
|
||||
nostr.trevena.info,43.6532,-79.3832
|
||||
relay.qstr.app,50.1109,8.68213
|
||||
relay.chorus.community:443,50.1109,8.68213
|
||||
relay.plebeian.market:443,50.1109,8.68213
|
||||
relay.nostrops.com,32.71,-96.6745
|
||||
nostr.chaima.info:443,50.1109,8.68213
|
||||
relay.nostr.dev.br,48.1046,11.6002
|
||||
relay.camelus.app,45.5201,-122.99
|
||||
nostr-pub.wellorder.net,45.5201,-122.99
|
||||
cyberspace.nostr1.com,40.7057,-74.0136
|
||||
relay2.angor.io,48.1046,11.6002
|
||||
relay.edufeed.org:443,49.4521,11.0767
|
||||
relay.bnos.space,43.6532,-79.3832
|
||||
groups.beginningend.com,35.2227,-97.4786
|
||||
relay.gulugulu.moe:443,43.6532,-79.3832
|
||||
relayone.soundhsa.com,39.0997,-94.5786
|
||||
relay.noeudlibre.fr,50.6924,3.20113
|
||||
nostr.christiansass.de,50.1109,8.68213
|
||||
chat-relay.zap-work.com:443,43.6532,-79.3832
|
||||
conduit-congee.fly.dev,38.7946,-106.535
|
||||
relay.satmaxt.xyz:443,43.6532,-79.3832
|
||||
relay.rebelbase.social,50.965,5.50082
|
||||
nostr.relay.hedwig.sh,60.1699,24.9384
|
||||
dev.relay.stream,43.6532,-79.3832
|
||||
relay.edufeed.org,49.4521,11.0767
|
||||
relay.s-w.art,43.6532,-79.3832
|
||||
nostr.wecsats.io,43.6532,-79.3832
|
||||
relay.nostreon.com,60.1699,24.9384
|
||||
relay.agentry.com,42.8864,-78.8784
|
||||
denimroad.feeds.relay.tools,38.6327,-90.1961
|
||||
relay.artx.market,43.6548,-79.3885
|
||||
nostr.unkn0wn.world,47.4464,8.21073
|
||||
relay.mappingbitcoin.com,43.6532,-79.3832
|
||||
relay-na1.metanomalist.com,43.6532,-79.3832
|
||||
nostr.2b9t.xyz,34.0549,-118.243
|
||||
relay.artio.inf.unibe.ch,46.9501,7.43678
|
||||
relay.mostro.network,40.8302,-74.1299
|
||||
relay.nmail.li,50.9871,2.12554
|
||||
bruh.samt.st,43.6532,-79.3832
|
||||
relay.conduit.market,38.7946,-106.535
|
||||
relay.mwaters.net,50.9871,2.12554
|
||||
nostr.twinkle.lol,51.902,7.6657
|
||||
relay.selfrule.co.za,43.6532,-79.3832
|
||||
relay.btcforplebs.com,43.6532,-79.3832
|
||||
relay.plebeian.market,50.1109,8.68213
|
||||
nostr.n7ekb.net,47.4941,-122.294
|
||||
nostr.easycryptosend.it,43.6532,-79.3832
|
||||
relay.primal.net,43.6532,-79.3832
|
||||
dev-relay.nostreon.com,60.1699,24.9384
|
||||
nostr-kyomu-haskell.onrender.com,37.7775,-122.397
|
||||
relay.kilombino.com,43.6532,-79.3832
|
||||
nostr.bitcoiner.social:443,47.6743,-117.112
|
||||
relay.underorion.se,51.4998,-0.011072
|
||||
relay.paulstephenborile.com:443,49.4543,11.0746
|
||||
relay.nostr.net,43.6532,-79.3832
|
||||
syb.lol,43.6532,-79.3832
|
||||
articles.layer3.news,37.3387,-121.885
|
||||
ec1.f7z.io,60.1699,24.9384
|
||||
relay.manneken.brussels,49.4543,11.0746
|
||||
nostr.tac.lol,47.4748,-122.273
|
||||
relay.agora.social,50.7383,15.0648
|
||||
nostr.oxtr.dev,50.4754,12.3683
|
||||
relay.hivetalk.org,40.8302,-74.1299
|
||||
income-strongly-active-systematic.trycloudflare.com,43.6532,-79.3832
|
||||
nostr.jcera.eu,43.6532,-79.3832
|
||||
relay-dev.satlantis.io,39.0438,-77.4874
|
||||
nostr.sovereignservices.xyz,43.6532,-79.3832
|
||||
relay.lacrypta.ar,43.6532,-79.3832
|
||||
nostr.data.haus,50.4754,12.3683
|
||||
relay.monostr.com,50.4754,12.3683
|
||||
relay.nostrhub.fr,48.1045,11.6004
|
||||
relay.loveisbitcoin.com,43.6532,-79.3832
|
||||
portal-relay.pareto.space,53.5488,9.98717
|
||||
relay.minibolt.info,43.6532,-79.3832
|
||||
relay.czas.plus,50.1109,8.68213
|
||||
relay2.veganostr.com,60.1699,24.9384
|
||||
relay.gorrdy.cz,50.1109,8.68213
|
||||
relay.paulstephenborile.com,49.4543,11.0746
|
||||
temp.iris.to,43.6532,-79.3832
|
||||
relay.formstr.app,43.6532,-79.3832
|
||||
talon.quest,43.6532,-79.3832
|
||||
relay.ltgnet.work:8443,51.0511,-114.075
|
||||
relay.guggero.org,46.5971,9.59652
|
||||
relay.nostr.bond,40.1885,29.061
|
||||
bitchat.nostr1.com,40.7057,-74.0136
|
||||
prl.plus,54.9486,21.7283
|
||||
relay.sharegap.net,43.6532,-79.3832
|
||||
nostr.21crypto.ch,47.5356,8.73209
|
||||
relay.jcera.eu,43.6532,-79.3832
|
||||
r.senx.io,50.6924,3.20113
|
||||
porchlight.social,43.6532,-79.3832
|
||||
bread.nostrsms.com,40.8218,-74.45
|
||||
relay.nostrian-conquest.com,41.223,-111.974
|
||||
relay.getsafebox.app:443,43.6532,-79.3832
|
||||
nostr.quali.chat,60.1699,24.9384
|
||||
relay.laantungir.net,-19.4692,-42.5315
|
||||
relay.hivescope.xyz,40.4332,-3.62431
|
||||
nostr.fullstackcash.net,45.5201,-122.99
|
||||
nip85-staging.nosfabrica.com,39.0997,-94.5786
|
||||
relay-fra.zombi.cloudrodion.com,48.8566,2.35222
|
||||
relay.nostr.black,43.6532,-79.3832
|
||||
nostr.trepechov.com,49.4543,11.0746
|
||||
relay.binaryrobot.com:443,43.6532,-79.3832
|
||||
basspistol.org,49.0291,8.35696
|
||||
free.relayted.de,50.1109,8.68213
|
||||
relay.beginningend.com,35.2227,-97.4786
|
||||
nostrelay.circum.space:443,52.2245,8.826
|
||||
strfry.bonsai.com:443,39.0438,-77.4874
|
||||
relay.wasabiwallet.io,43.6532,-79.3832
|
||||
relay.scuba323.com,40.8218,-74.45
|
||||
vidar.nestoris.online,52.3676,4.90414
|
||||
nostr.pbfs.io:443,50.4754,12.3683
|
||||
relay.pyramid.li,47.4093,8.46503
|
||||
relay.olas.app,60.1699,24.9384
|
||||
relay.internationalright-wing.org,-22.4692,-48.9875
|
||||
nostr-relay.corb.net,39.6478,-104.988
|
||||
relay.plebdevs.com,40.7128,-74.006
|
||||
relay.ditto.pub,43.6532,-79.3832
|
||||
relay.i9.eti.br,43.6532,-79.3832
|
||||
nostr.pro.aw,43.6532,-79.3832
|
||||
relay.hazeem.xyz,43.6532,-79.3832
|
||||
zencrown.relay2aeon.quest,52.3061,4.6907
|
||||
relay.npubhaus.com,43.6532,-79.3832
|
||||
relay.nostriot.com,41.5695,-83.9786
|
||||
cs-relay.nostrdev.com,50.4754,12.3683
|
||||
relay.substation.ninja,43.6532,-79.3832
|
||||
relay1.nostrchat.io,60.1699,24.9384
|
||||
nostr-relay.cbrx.io,43.6532,-79.3832
|
||||
nostr.stakey.net:443,52.6485,4.73787
|
||||
relayone.geektank.ai:443,39.0997,-94.5786
|
||||
adre.su,59.9311,30.3609
|
||||
nostr.islandarea.net,35.4669,-97.6473
|
||||
relay.cypherguide.org,1.32123,103.695
|
||||
fanfares.nostr1.com,40.7057,-74.0136
|
||||
nostrride.io,43.6532,-79.3832
|
||||
relay.littlebitstudios.com,34.8688,-86.5502
|
||||
relay.snotr.nl:49999,51.9758,4.31389
|
||||
relay02.lnfi.network,35.6764,139.65
|
||||
nostr.rtvslawenia.com,49.4543,11.0746
|
||||
relay.staging.plebeian.market:443,51.5072,-0.127586
|
||||
relay.earthly.city,34.1749,-118.54
|
||||
relay.binaryrobot.com,43.6532,-79.3832
|
||||
relay.artiostr.ch,43.6532,-79.3832
|
||||
nostr.nestoris.online,55.568,37.5853
|
||||
nostr.liberty.fans,36.8767,-89.5879
|
||||
n.bogs.ccwu.cc,43.6532,-79.3832
|
||||
relay.t6x.host,40.8302,-74.1299
|
||||
relay.nostrverse.net,43.6532,-79.3832
|
||||
relay.bullishbounty.com:443,43.6532,-79.3832
|
||||
nostr.mom,50.4754,12.3683
|
||||
scores.brainstorm.world,39.0997,-94.5786
|
||||
nostr.tabordalab.com,60.1699,24.9384
|
||||
relay.favillakey.io,-27.4705,153.026
|
||||
relay.nostr.blockhenge.com,39.0438,-77.4874
|
||||
relay.bullishbounty.com,43.6532,-79.3832
|
||||
vm-1734.lnvps.cloud,53.3498,-6.26031
|
||||
nostr.bitcoiner.social,47.6743,-117.112
|
||||
budabit.nostr1.com,40.7057,-74.0136
|
||||
soloco.nl,43.6532,-79.3832
|
||||
relay.iyou.me,64.1466,-21.9426
|
||||
dm-test-nostr-rs-42-disabled.samt.st,43.6532,-79.3832
|
||||
relay.cloistr.xyz,42.337,-77.3178
|
||||
relay.nearhood.co.uk,51.5134,-0.0890675
|
||||
offchain.bostr.online,43.6532,-79.3832
|
||||
relay.keykeeper.world,40.7824,-74.0711
|
||||
nostras.app,38.7946,-106.535
|
||||
nostr.wild-vibes.ts.net,48.8566,2.35222
|
||||
nostr-relayflare.enpitsulin.workers.dev,43.6532,-79.3832
|
||||
relay.lanacoin-eternity.com:443,40.8302,-74.1299
|
||||
greenart7c3.feeds.relay.tools,38.6327,-90.1961
|
||||
relay.mitosystem.net,-23.5558,-46.6396
|
||||
nostr.snowbla.de,50.4754,12.3683
|
||||
cs-relay.nostrdev.com:443,50.4754,12.3683
|
||||
nostrelay.circum.space,52.2245,8.826
|
||||
0x-nostr-relay.fly.dev,38.7946,-106.535
|
||||
nip85.nosfabrica.com,39.0997,-94.5786
|
||||
chorus.mikedilger.com:444,-36.8906,174.794
|
||||
nostr.rtvslawenia.com:443,49.4543,11.0746
|
||||
yabu.me,35.6096,139.73
|
||||
nostr.planix.org,43.6532,-79.3832
|
||||
relay2.orangesync.tech,40.7128,-74.006
|
||||
relay.dyne.org,49.0291,8.35705
|
||||
relay.aarpia.com,37.3986,-121.964
|
||||
relay.fiatdenier.com,43.7787,-79.5393
|
||||
relay.wisp.talk:443,49.4543,11.0746
|
||||
nostr.azzamo.net,52.2633,21.0283
|
||||
relay.satmaxt.xyz,43.6532,-79.3832
|
||||
relay.jmoose.rocks:443,32.7767,-96.797
|
||||
nostr-01.uid.ovh,50.9871,2.12554
|
||||
nostr-relay.corb.net:443,39.6478,-104.988
|
||||
buzz.cashu.space,43.6532,-79.3832
|
||||
dev-relay.nostr.space,43.6532,-79.3832
|
||||
relay.cyberguy.fyi,52.3676,4.90414
|
||||
freelay.sovbit.host,60.1699,24.9384
|
||||
hasky.chat,40.7862,-74.0743
|
||||
treuzkas.branruz.com,48.8575,2.35138
|
||||
nostr-verified.wellorder.net,45.5201,-122.99
|
||||
nexus.libernet.app,43.6532,-79.3832
|
||||
relay.gulugulu.moe,43.6532,-79.3832
|
||||
bitcoiner.social,47.6743,-117.112
|
||||
nostr.stakey.net,52.6485,4.73787
|
||||
schnorr.me,43.6532,-79.3832
|
||||
nos.xmark.cc,50.6916,3.20151
|
||||
vault.iris.to,43.6532,-79.3832
|
||||
nostr.nodesmap.com,59.3327,18.0656
|
||||
nostr.88mph.life,52.1941,-2.21905
|
||||
nostr.yutakobayashi.com,43.6532,-79.3832
|
||||
relay.layer.systems,43.6532,-79.3832
|
||||
relay-can.zombi.cloudrodion.com,43.6532,-79.3832
|
||||
bcast.girino.org,43.6532,-79.3832
|
||||
relay.chorus.community,50.1109,8.68213
|
||||
strfry.shock.network,39.0438,-77.4874
|
||||
ynostr.yael.at,60.1699,24.9384
|
||||
relay.staging.plebeian.market,51.5072,-0.127586
|
||||
cache.trustr.ing,43.6548,-79.3885
|
||||
nos.lol:443,50.4754,12.3683
|
||||
relay.shadowbip.com,54.352,18.6466
|
||||
nostr.computingcache.com,45.5341,-122.956
|
||||
memlay.v0l.io,53.3498,-6.26031
|
||||
relay.colombiap2p.com,43.6532,-79.3832
|
||||
dm-test-strfry-generic.samt.st,43.6532,-79.3832
|
||||
borgar.lol,52.3676,4.90414
|
||||
relay.wellorder.net,45.5201,-122.99
|
||||
node.kommonzenze.de,49.4521,11.0767
|
||||
relay.ru.ac.th,13.7607,100.627
|
||||
nostr.davenov.com,50.1109,8.68213
|
||||
nostr.linky.fit,50.1109,8.68213
|
||||
nostr-relay.zimage.com,34.282,-118.439
|
||||
relay.nostx.io,43.6532,-79.3832
|
||||
relay.dwadziesciajeden.pl,52.2297,21.0122
|
||||
relay.pornstr.net,53.3498,-6.26031
|
||||
nostr-rs-relay.dev.fedibtc.com,39.0438,-77.4874
|
||||
schnorr.me:443,43.6532,-79.3832
|
||||
nostr.plantroon.com,50.1013,8.62643
|
||||
relay.routstr.com,59.4016,17.9455
|
||||
relay.shychat.lol,39.8965,-80.1792
|
||||
nr.yay.so,46.2126,6.1154
|
||||
pymd.kamp.site,38.9072,-77.0369
|
||||
relay.decentralia.fr,48.122,11.589
|
||||
relay.klabo.world,47.674,-122.122
|
||||
spurline.safebox.dev,43.6532,-79.3832
|
||||
bridge.tagomago.me,42.3601,-71.0589
|
||||
inbox.scuba323.com,40.8218,-74.45
|
||||
no.str.cr:443,8.96171,-83.5246
|
||||
nostr2.girino.org:443,43.6532,-79.3832
|
||||
nostr-dev.wellorder.net,45.5201,-122.99
|
||||
relay.mypathtofire.de,42.8864,-78.8784
|
||||
nostr-02.uid.ovh,50.9871,2.12554
|
||||
support.flotilla.social,32.9483,-96.7299
|
||||
relay.mccormick.cx,52.3563,4.95714
|
||||
relay.getsafebox.app,43.6532,-79.3832
|
||||
relay.bitos.space,43.6532,-79.3832
|
||||
nostrcity-club.fly.dev,38.7946,-106.535
|
||||
spamspamspamspam.rest,43.6532,-79.3832
|
||||
relay.solomonstr.com,43.6532,-79.3832
|
||||
relay.angor.io,48.1046,11.6002
|
||||
relay.islandbitcoin.com,20.3898,78.0903
|
||||
relay.bornheimer.app,50.1109,8.68213
|
||||
relay.atsocy.com,43.6532,-79.3832
|
||||
relay.wavlake.com,41.2619,-95.8608
|
||||
relay.nostrcheck.me,43.6532,-79.3832
|
||||
relay.bowlafterbowl.com,32.9483,-96.7299
|
||||
relay.novospes.com,43.6532,-79.3832
|
||||
nostr.spaceshell.xyz,43.6532,-79.3832
|
||||
purplerelay.com,43.6532,-79.3832
|
||||
relay.satsapp.me,50.4754,12.3683
|
||||
nostr.pbfs.io,50.4754,12.3683
|
||||
relay2.nostrasia.net,43.6532,-79.3832
|
||||
relay.piazza.today,48.122,11.589
|
||||
relay.nostr.space,43.6532,-79.3832
|
||||
relay.duck1123.com,43.6532,-79.3832
|
||||
relay.kizuna-miki.com,35.694,139.754
|
||||
relay.dreamith.to,43.6532,-79.3832
|
||||
relay.chatbett.de,40.7128,-74.006
|
||||
public.crostr.com:443,43.6532,-79.3832
|
||||
relay.satlantis.io,39.0438,-77.4874
|
||||
nostr2.girino.org,43.6532,-79.3832
|
||||
relay.lotek-distro.com,43.6532,-79.3832
|
||||
nostr-rs-relay.dev.fedibtc.com:443,39.0438,-77.4874
|
||||
nostrrelay.taylorperron.com,45.5029,-73.5723
|
||||
nosflare-leefcore.leefcore.workers.dev,43.6532,-79.3832
|
||||
nostr.spicyz.io,43.6532,-79.3832
|
||||
x.kojira.io,43.6532,-79.3832
|
||||
relay.veganostr.com:443,60.1699,24.9384
|
||||
nostr.alex71btc.com,43.6532,-79.3832
|
||||
relay.thewired.app,39.0438,-77.4874
|
||||
relay.getvia.xyz,60.1699,24.9384
|
||||
relayone.geektank.ai,39.0997,-94.5786
|
||||
nostr.hoppe-relay.it.com,42.8864,-78.8784
|
||||
relay2.0xprivacy.online,43.6532,-79.3832
|
||||
nostr-01.yakihonne.com,1.32123,103.695
|
||||
offchain.pub,39.1585,-94.5728
|
||||
nostrcity-club.fly.dev:443,38.7946,-106.535
|
||||
testr.nymble.world,40.8054,-74.0241
|
||||
relay.fountain.fm,43.6532,-79.3832
|
||||
relay.nostr.com,50.1109,8.68213
|
||||
|
||||
|
@ -45,6 +45,9 @@ class BitchatApplication : Application() {
|
||||
com.bitchat.android.nostr.NostrIdentityBridge.getCurrentNostrIdentity(this)
|
||||
} catch (_: Exception) { }
|
||||
|
||||
// Restore the spam filter toggle before transports start admitting public messages
|
||||
com.bitchat.android.spam.SpamFilterPreferences.init(this)
|
||||
|
||||
// Initialize theme preference
|
||||
ThemePreferenceManager.init(this)
|
||||
|
||||
|
||||
@ -34,7 +34,8 @@ fun BitchatSheetTopBar(
|
||||
val dismiss = LocalSheetDismiss.current
|
||||
CloseButton(
|
||||
onClick = { dismiss?.invoke() ?: onClose() },
|
||||
modifier = Modifier.padding(horizontal = 16.dp)
|
||||
// 2.dp from any preceding action, matching the conversation header's action spacing.
|
||||
modifier = Modifier.padding(start = 2.dp, end = 16.dp)
|
||||
)
|
||||
},
|
||||
colors = TopAppBarDefaults.topAppBarColors(
|
||||
@ -64,7 +65,8 @@ fun BitchatSheetCenterTopBar(
|
||||
val dismiss = LocalSheetDismiss.current
|
||||
CloseButton(
|
||||
onClick = { dismiss?.invoke() ?: onClose() },
|
||||
modifier = Modifier.padding(horizontal = 16.dp)
|
||||
// 2.dp from any preceding action, matching the conversation header's action spacing.
|
||||
modifier = Modifier.padding(start = 2.dp, end = 16.dp)
|
||||
)
|
||||
},
|
||||
colors = TopAppBarDefaults.topAppBarColors(
|
||||
|
||||
@ -97,8 +97,10 @@ class SecurityManager(private val encryptionService: EncryptionService, private
|
||||
// Record only authenticated packets. Recording an attacker-controlled
|
||||
// invalid packet first would let it poison duplicate detection for a
|
||||
// later legitimate packet with the same timestamp and payload.
|
||||
processedMessages.add(messageID)
|
||||
messageTimestamps[messageID] = currentTime
|
||||
synchronized(messageTimestamps) {
|
||||
processedMessages.add(messageID)
|
||||
messageTimestamps[messageID] = currentTime
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
@ -136,8 +138,10 @@ class SecurityManager(private val encryptionService: EncryptionService, private
|
||||
// flow and reports whether this exact frame completed authentication. Never infer that
|
||||
// from ambient session state: a rejected replacement may leave the old session active.
|
||||
val result = encryptionService.processHandshakeMessageWithResult(packet.payload, peerID)
|
||||
processedKeyExchanges.add(exchangeKey)
|
||||
keyExchangeTimestamps[exchangeKey] = System.currentTimeMillis()
|
||||
synchronized(keyExchangeTimestamps) {
|
||||
processedKeyExchanges.add(exchangeKey)
|
||||
keyExchangeTimestamps[exchangeKey] = System.currentTimeMillis()
|
||||
}
|
||||
|
||||
if (result.response != null) {
|
||||
// Send handshake response through delegate
|
||||
@ -389,42 +393,45 @@ class SecurityManager(private val encryptionService: EncryptionService, private
|
||||
internal fun cleanupOldData(nowMs: Long = System.currentTimeMillis()) {
|
||||
val cutoffTime = nowMs - MESSAGE_TIMEOUT
|
||||
|
||||
// Clean up old message timestamps and corresponding processed messages
|
||||
val messagesToRemove = messageTimestamps.entries.filter { (_, timestamp) ->
|
||||
timestamp < cutoffTime
|
||||
}.map { it.key }
|
||||
|
||||
messagesToRemove.forEach { messageId ->
|
||||
messageTimestamps.remove(messageId)
|
||||
processedMessages.remove(messageId)
|
||||
// Synchronized collection wrappers protect individual operations, not iteration.
|
||||
// Keep each timestamp/set pair locked through expiry and capacity eviction so
|
||||
// cleanup cannot remove a freshly recorded entry or traverse a mutating set.
|
||||
synchronized(messageTimestamps) {
|
||||
synchronized(processedMessages) {
|
||||
val messagesToRemove = messageTimestamps.entries.filter { (_, timestamp) ->
|
||||
timestamp < cutoffTime
|
||||
}.map { it.key }
|
||||
messagesToRemove.forEach { messageId ->
|
||||
messageTimestamps.remove(messageId)
|
||||
processedMessages.remove(messageId)
|
||||
}
|
||||
if (processedMessages.size > MAX_PROCESSED_MESSAGES) {
|
||||
val excess = processedMessages.size - MAX_PROCESSED_MESSAGES
|
||||
val toRemove = processedMessages.take(excess)
|
||||
processedMessages.removeAll(toRemove.toSet())
|
||||
removeFromMessageTimestamps(toRemove)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Limit the size of processed messages set
|
||||
if (processedMessages.size > MAX_PROCESSED_MESSAGES) {
|
||||
val excess = processedMessages.size - MAX_PROCESSED_MESSAGES
|
||||
val toRemove = processedMessages.take(excess)
|
||||
processedMessages.removeAll(toRemove.toSet())
|
||||
removeFromMessageTimestamps(toRemove)
|
||||
}
|
||||
|
||||
// Expire handshake dedup entries by time so a delayed same-ephemeral delivery
|
||||
// (e.g. a re-handshake retry after a failed attempt) is not blocked forever.
|
||||
val keyExchangeCutoff = nowMs - KEY_EXCHANGE_DEDUP_TIMEOUT
|
||||
val keyExchangesToRemove = keyExchangeTimestamps.entries.filter { (_, timestamp) ->
|
||||
timestamp < keyExchangeCutoff
|
||||
}.map { it.key }
|
||||
|
||||
keyExchangesToRemove.forEach { exchangeKey ->
|
||||
keyExchangeTimestamps.remove(exchangeKey)
|
||||
processedKeyExchanges.remove(exchangeKey)
|
||||
}
|
||||
|
||||
// Limit the size of processed key exchanges set
|
||||
if (processedKeyExchanges.size > MAX_PROCESSED_KEY_EXCHANGES) {
|
||||
val excess = processedKeyExchanges.size - MAX_PROCESSED_KEY_EXCHANGES
|
||||
val toRemove = processedKeyExchanges.take(excess)
|
||||
processedKeyExchanges.removeAll(toRemove.toSet())
|
||||
toRemove.forEach { keyExchangeTimestamps.remove(it) }
|
||||
synchronized(keyExchangeTimestamps) {
|
||||
synchronized(processedKeyExchanges) {
|
||||
// Expire delayed handshake retries as well as bounding the cache size.
|
||||
val keyExchangeCutoff = nowMs - KEY_EXCHANGE_DEDUP_TIMEOUT
|
||||
val keyExchangesToRemove = keyExchangeTimestamps.entries.filter { (_, timestamp) ->
|
||||
timestamp < keyExchangeCutoff
|
||||
}.map { it.key }
|
||||
keyExchangesToRemove.forEach { exchangeKey ->
|
||||
keyExchangeTimestamps.remove(exchangeKey)
|
||||
processedKeyExchanges.remove(exchangeKey)
|
||||
}
|
||||
if (processedKeyExchanges.size > MAX_PROCESSED_KEY_EXCHANGES) {
|
||||
val excess = processedKeyExchanges.size - MAX_PROCESSED_KEY_EXCHANGES
|
||||
val toRemove = processedKeyExchanges.take(excess)
|
||||
processedKeyExchanges.removeAll(toRemove.toSet())
|
||||
toRemove.forEach { keyExchangeTimestamps.remove(it) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -441,10 +448,14 @@ class SecurityManager(private val encryptionService: EncryptionService, private
|
||||
* Clear all security data
|
||||
*/
|
||||
fun clearAllData() {
|
||||
processedMessages.clear()
|
||||
processedKeyExchanges.clear()
|
||||
messageTimestamps.clear()
|
||||
keyExchangeTimestamps.clear()
|
||||
synchronized(messageTimestamps) {
|
||||
processedMessages.clear()
|
||||
messageTimestamps.clear()
|
||||
}
|
||||
synchronized(keyExchangeTimestamps) {
|
||||
processedKeyExchanges.clear()
|
||||
keyExchangeTimestamps.clear()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -3,6 +3,7 @@ package com.bitchat.android.nostr
|
||||
import android.app.Application
|
||||
import android.util.Log
|
||||
import com.bitchat.android.model.BitchatMessage
|
||||
import com.bitchat.android.spam.SpamFilter
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
@ -14,7 +15,7 @@ import java.util.Date
|
||||
* GeohashMessageHandler
|
||||
* - Processes kind=20000 Nostr events for geohash channels
|
||||
* - Updates repository for participants + nicknames
|
||||
* - Emits messages to MessageManager
|
||||
* - Hides spam via [SpamFilter], then emits messages to MessageManager
|
||||
*/
|
||||
class GeohashMessageHandler(
|
||||
private val application: Application,
|
||||
@ -22,7 +23,9 @@ class GeohashMessageHandler(
|
||||
private val scope: CoroutineScope,
|
||||
private val dataManager: com.bitchat.android.ui.DataManager,
|
||||
private val addChannelMessage: (String, BitchatMessage) -> Unit,
|
||||
private val signatureVerificationDispatcher: CoroutineDispatcher = Dispatchers.Default
|
||||
private val signatureVerificationDispatcher: CoroutineDispatcher = Dispatchers.Default,
|
||||
// The foreground handler runs on the main thread; the spam checks are CPU work.
|
||||
private val spamFilterDispatcher: CoroutineDispatcher = signatureVerificationDispatcher
|
||||
) {
|
||||
companion object { private const val TAG = "GeohashMessageHandler" }
|
||||
|
||||
@ -111,6 +114,13 @@ class GeohashMessageHandler(
|
||||
if (hasNonce) NostrProofOfWork.calculateDifficulty(event.id).takeIf { it > 0 } else null
|
||||
} catch (_: Exception) { null }
|
||||
)
|
||||
val hide = withContext(spamFilterDispatcher) {
|
||||
SpamFilter.shared.shouldHide(msg, SpamFilter.nostrSenderKey(pubkey))
|
||||
}
|
||||
if (hide) {
|
||||
Log.d(TAG, "Hiding geohash spam ${event.id.take(8)}...")
|
||||
return@launch
|
||||
}
|
||||
addChannelMessage("geo:$subscribedGeohash", msg)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "onEvent error: ${e.message}")
|
||||
|
||||
@ -424,7 +424,7 @@ class NostrRelayManager private constructor() {
|
||||
liveLocationConnectionJobs.forEach(Job::cancel)
|
||||
liveLocationConnectionJobs.clear()
|
||||
|
||||
val sockets = connections.values.toList()
|
||||
val sockets = buildList { connections.values.forEach { add(it) } }
|
||||
connections.clear()
|
||||
sockets.forEach { webSocket ->
|
||||
webSocket.close(1000, "Manual disconnect")
|
||||
@ -514,7 +514,10 @@ class NostrRelayManager private constructor() {
|
||||
|
||||
scope.launch {
|
||||
if (!isNetworkActionAllowed(subscriptionInfo.liveLocationToken)) return@launch
|
||||
val targetRelays = subscriptionInfo.targetRelayUrls?.toList() ?: connections.keys.toList()
|
||||
// A concurrent view can shrink between Collection.size and iterator.next.
|
||||
// Build by iteration instead of Kotlin's size-based toList fast paths.
|
||||
val targetRelays = subscriptionInfo.targetRelayUrls?.toList()
|
||||
?: buildList { connections.keys.forEach { add(it) } }
|
||||
|
||||
targetRelays.forEach { relayUrl ->
|
||||
val webSocket = connections[relayUrl]
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
package com.bitchat.android.services
|
||||
|
||||
import com.bitchat.android.model.BitchatMessage
|
||||
import com.bitchat.android.spam.SpamFilter
|
||||
import kotlinx.coroutines.runBlocking
|
||||
|
||||
/**
|
||||
@ -8,7 +9,8 @@ import kotlinx.coroutines.runBlocking
|
||||
*
|
||||
* Private-message admission is authoritative: a duplicate or a message rejected while panic mode
|
||||
* is wiping state must not continue to UI delegates, unread tracking, haptics, or notifications.
|
||||
* Public and channel messages retain their existing best-effort behavior if state reflection fails.
|
||||
* Public and channel messages retain their existing best-effort behavior if state reflection fails,
|
||||
* and are refused (no timeline row, no UI callback) when the spam filter hides them.
|
||||
*/
|
||||
internal object IncomingMessageAdmission {
|
||||
fun admitToAppState(message: BitchatMessage): Boolean = try {
|
||||
@ -24,6 +26,8 @@ internal object IncomingMessageAdmission {
|
||||
}
|
||||
}
|
||||
|
||||
isHiddenAsSpam(message) -> false
|
||||
|
||||
message.channel != null -> {
|
||||
AppStateStore.addChannelMessage(message.channel, message)
|
||||
true
|
||||
@ -39,4 +43,9 @@ internal object IncomingMessageAdmission {
|
||||
// bypass private-message admission when persistence or canonicalization fails.
|
||||
!message.isPrivate
|
||||
}
|
||||
|
||||
private fun isHiddenAsSpam(message: BitchatMessage): Boolean {
|
||||
if (SpamFilter.isLocalNotice(message)) return false
|
||||
return SpamFilter.shared.shouldHide(message, SpamFilter.meshSenderKey(message))
|
||||
}
|
||||
}
|
||||
|
||||
14
app/src/main/java/com/bitchat/android/spam/LinkCounter.kt
Normal file
14
app/src/main/java/com/bitchat/android/spam/LinkCounter.kt
Normal file
@ -0,0 +1,14 @@
|
||||
package com.bitchat.android.spam
|
||||
|
||||
/**
|
||||
* Counts links the way the chat linkifies them: scheme URLs, `www.` hosts and bare domains
|
||||
* such as `example.com/path`. Pure Kotlin so it also runs on the watch and in JVM tests.
|
||||
*/
|
||||
internal object LinkCounter {
|
||||
|
||||
private val LINK = Regex(
|
||||
"""(?i)(?<![\w@.\-])(?:https?://[^\s<>]+|www\.[^\s<>]+|[a-z0-9\-]+(?:\.[a-z0-9\-]+)*\.[a-z]{2,}(?:/[^\s<>]*)?)(?![\w@])"""
|
||||
)
|
||||
|
||||
fun count(text: String): Int = LINK.findAll(text).count()
|
||||
}
|
||||
187
app/src/main/java/com/bitchat/android/spam/RepetitionDetector.kt
Normal file
187
app/src/main/java/com/bitchat/android/spam/RepetitionDetector.kt
Normal file
@ -0,0 +1,187 @@
|
||||
package com.bitchat.android.spam
|
||||
|
||||
import java.text.Normalizer
|
||||
|
||||
/**
|
||||
* Character autocorrelation measures for repeated-content spam.
|
||||
*
|
||||
* Text is compared as Unicode code points after [normalize], so case, spacing, look-alike
|
||||
* letters, accents and invisible characters don't hide a repeat.
|
||||
*/
|
||||
internal object RepetitionDetector {
|
||||
|
||||
/**
|
||||
* Folds a message to what a reader sees, for comparison only (never for display): NFKD
|
||||
* turns fullwidth and math-styled letters into plain ones and splits off accents, then
|
||||
* combining marks and invisible characters are dropped, whitespace runs become one space
|
||||
* and the result is lowercased and capped to [maxLength] code points.
|
||||
*/
|
||||
fun normalize(text: String, maxLength: Int): IntArray {
|
||||
// Disguises at most a few code units per visible character; bound the work up front.
|
||||
val head = if (text.length > maxLength * 4) text.substring(0, maxLength * 4) else text
|
||||
val decomposed = Normalizer.normalize(head, Normalizer.Form.NFKD)
|
||||
val out = IntArray(minOf(maxLength, decomposed.length))
|
||||
var size = 0
|
||||
var pendingSpace = false
|
||||
var offset = 0
|
||||
while (offset < decomposed.length && size < out.size) {
|
||||
val point = decomposed.codePointAt(offset)
|
||||
offset += Character.charCount(point)
|
||||
when {
|
||||
isMark(point) || isInvisible(point) -> continue
|
||||
isWhitespace(point) -> pendingSpace = size > 0
|
||||
else -> {
|
||||
if (pendingSpace && size < out.size - 1) out[size++] = ' '.code
|
||||
pendingSpace = false
|
||||
out[size++] = Character.toLowerCase(point)
|
||||
}
|
||||
}
|
||||
}
|
||||
return out.copyOf(size)
|
||||
}
|
||||
|
||||
private fun isWhitespace(point: Int): Boolean =
|
||||
Character.isWhitespace(point) || Character.isSpaceChar(point)
|
||||
|
||||
/** Zero-width and direction controls, soft hyphens and other non-printing characters. */
|
||||
private fun isInvisible(point: Int): Boolean =
|
||||
Character.getType(point) == Character.FORMAT.toInt() ||
|
||||
(Character.isISOControl(point) && !isWhitespace(point))
|
||||
|
||||
private fun isMark(point: Int): Boolean = when (Character.getType(point)) {
|
||||
Character.NON_SPACING_MARK.toInt(),
|
||||
Character.COMBINING_SPACING_MARK.toInt(),
|
||||
Character.ENCLOSING_MARK.toInt() -> true
|
||||
else -> false
|
||||
}
|
||||
|
||||
/**
|
||||
* Highest autocorrelation of [text] with itself over shifts 1..n/2: the fraction of
|
||||
* overlapping positions where `text[i] == text[i + lag]`. A pattern repeated back to back
|
||||
* ("aaaa", "buy now buy now") scores 1.0; ordinary prose stays well below 0.3.
|
||||
*/
|
||||
fun selfSimilarity(text: IntArray): Double {
|
||||
val n = text.size
|
||||
var best = 0.0
|
||||
for (lag in 1..n / 2) {
|
||||
val overlap = n - lag
|
||||
var matches = 0
|
||||
for (i in 0 until overlap) if (text[i] == text[i + lag]) matches++
|
||||
val score = matches.toDouble() / overlap
|
||||
if (score > best) best = score
|
||||
}
|
||||
return best
|
||||
}
|
||||
|
||||
/** One diagonal of the cross-correlation: `b[j]` lined up under `a[j + shift]`. */
|
||||
data class Peak(val shift: Int, val matches: Int)
|
||||
|
||||
/** How two texts line up: the peaks used and the share of the longer text they cover. */
|
||||
data class Alignment(val peaks: List<Peak>, val matches: Int, val score: Double)
|
||||
|
||||
fun crossSimilarity(a: IntArray, b: IntArray, maxPeaks: Int = 3, minRun: Int = 3): Double =
|
||||
align(a, b, maxPeaks, minRun).score
|
||||
|
||||
/**
|
||||
* Cross-correlation of two texts that tolerates small edits.
|
||||
*
|
||||
* The first peak is the classic correlation maximum, the shift where the most characters
|
||||
* match. An inserted or deleted character moves the rest of the text onto a neighboring
|
||||
* shift, so up to [maxPeaks] - 1 further peaks add matches from other shifts. Those count
|
||||
* only runs of at least [minRun] consecutive characters, so coincidental single letters
|
||||
* don't add up, and no character of either text is used twice. The score is the matched
|
||||
* characters over the longer length: 1.0 for identical texts, and a short text never
|
||||
* matches a long one.
|
||||
*/
|
||||
fun align(a: IntArray, b: IntArray, maxPeaks: Int = 3, minRun: Int = 3): Alignment {
|
||||
if (a.isEmpty() || b.isEmpty()) return Alignment(emptyList(), 0, 0.0)
|
||||
val usedA = BooleanArray(a.size)
|
||||
val usedB = BooleanArray(b.size)
|
||||
val peaks = ArrayList<Peak>()
|
||||
var total = 0
|
||||
while (peaks.size < maxPeaks) {
|
||||
val run = if (peaks.isEmpty()) 1 else minRun
|
||||
var bestGain = 0
|
||||
var bestShift = 0
|
||||
for (shift in -(b.size - 1) until a.size) {
|
||||
val from = maxOf(0, -shift)
|
||||
val to = minOf(b.size, a.size - shift)
|
||||
if (to - from <= bestGain) continue
|
||||
val gain = runMatches(a, b, shift, from, to, usedA, usedB, run, mark = false)
|
||||
if (gain > bestGain) {
|
||||
bestGain = gain
|
||||
bestShift = shift
|
||||
}
|
||||
}
|
||||
if (bestGain < run) break
|
||||
val from = maxOf(0, -bestShift)
|
||||
val to = minOf(b.size, a.size - bestShift)
|
||||
runMatches(a, b, bestShift, from, to, usedA, usedB, run, mark = true)
|
||||
peaks += Peak(bestShift, bestGain)
|
||||
total += bestGain
|
||||
}
|
||||
return Alignment(peaks, total, total.toDouble() / maxOf(a.size, b.size))
|
||||
}
|
||||
|
||||
/** Unused matches at [shift] that sit in runs of at least [minRun]; [mark] claims them. */
|
||||
private fun runMatches(
|
||||
a: IntArray,
|
||||
b: IntArray,
|
||||
shift: Int,
|
||||
from: Int,
|
||||
to: Int,
|
||||
usedA: BooleanArray,
|
||||
usedB: BooleanArray,
|
||||
minRun: Int,
|
||||
mark: Boolean
|
||||
): Int {
|
||||
var gain = 0
|
||||
var runStart = -1
|
||||
for (j in from..to) {
|
||||
val hit = j < to && !usedB[j] && !usedA[j + shift] && a[j + shift] == b[j]
|
||||
if (hit) {
|
||||
if (runStart < 0) runStart = j
|
||||
} else if (runStart >= 0) {
|
||||
val length = j - runStart
|
||||
if (length >= minRun) {
|
||||
gain += length
|
||||
if (mark) for (k in runStart until j) {
|
||||
usedB[k] = true
|
||||
usedA[k + shift] = true
|
||||
}
|
||||
}
|
||||
runStart = -1
|
||||
}
|
||||
}
|
||||
return gain
|
||||
}
|
||||
|
||||
/** Distinct character trigrams as sorted hashes, for a cheap pre-check before [align]. */
|
||||
fun trigrams(text: IntArray): IntArray {
|
||||
if (text.size < 3) return IntArray(0)
|
||||
return IntArray(text.size - 2) { i -> (text[i] * 31 + text[i + 1]) * 31 + text[i + 2] }
|
||||
.distinct()
|
||||
.sorted()
|
||||
.toIntArray()
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared trigrams over the larger trigram set. Near-copies share most of theirs, unrelated
|
||||
* texts few, so pairs below a low bar can skip the quadratic [align]. Texts too short to
|
||||
* have trigrams always pass.
|
||||
*/
|
||||
fun trigramOverlap(a: IntArray, b: IntArray): Double {
|
||||
if (a.isEmpty() || b.isEmpty()) return 1.0
|
||||
var i = 0
|
||||
var j = 0
|
||||
var shared = 0
|
||||
while (i < a.size && j < b.size) {
|
||||
when {
|
||||
a[i] == b[j] -> { shared++; i++; j++ }
|
||||
a[i] < b[j] -> i++
|
||||
else -> j++
|
||||
}
|
||||
}
|
||||
return shared.toDouble() / maxOf(a.size, b.size)
|
||||
}
|
||||
}
|
||||
246
app/src/main/java/com/bitchat/android/spam/SpamFilter.kt
Normal file
246
app/src/main/java/com/bitchat/android/spam/SpamFilter.kt
Normal file
@ -0,0 +1,246 @@
|
||||
package com.bitchat.android.spam
|
||||
|
||||
import com.bitchat.android.model.BitchatMessage
|
||||
import com.bitchat.android.model.BitchatMessageType
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlin.math.abs
|
||||
|
||||
enum class SpamVerdict {
|
||||
ALLOWED,
|
||||
MESSAGE_RATE,
|
||||
CHARACTER_RATE,
|
||||
LINK_RATE,
|
||||
REPEATED_MESSAGE,
|
||||
REPEATED_ACROSS_USERS,
|
||||
REPEATED_CHARACTERS;
|
||||
|
||||
val isSpam: Boolean get() = this != ALLOWED
|
||||
}
|
||||
|
||||
/**
|
||||
* Decides whether an incoming public message should be hidden.
|
||||
*
|
||||
* Each sender gets three token buckets (messages, characters, links; see [SpamFilterConfig])
|
||||
* plus three repetition checks: a message that repeats itself internally, a near-copy of the
|
||||
* sender's own recent history, and a near-copy of what *other* senders posted recently. The last
|
||||
* one catches spam spread over many throwaway identities, which per-sender limits cannot see.
|
||||
* Hidden messages never spend tokens, but they do enter the repetition history so a spammer
|
||||
* cannot cycle the same text.
|
||||
*
|
||||
* Verdicts are remembered by message ID, so the same message arriving over BLE and Wi-Fi Aware,
|
||||
* or through both geohash handlers, is judged once and counted once.
|
||||
*/
|
||||
class SpamFilter(
|
||||
config: SpamFilterConfig = SpamFilterConfig(),
|
||||
private val clock: () -> Long = System::currentTimeMillis
|
||||
) {
|
||||
@Volatile
|
||||
var enabled: Boolean = true
|
||||
|
||||
/** Changing the limits forgets every sender, so new budgets start full. */
|
||||
@Volatile
|
||||
var config: SpamFilterConfig = config
|
||||
set(value) {
|
||||
synchronized(this) {
|
||||
field = value
|
||||
senders.clear()
|
||||
verdicts.clear()
|
||||
recentTexts.clear()
|
||||
}
|
||||
}
|
||||
|
||||
/** Debug: let spam through and record its verdict in [flagged] so the chat can mark it. */
|
||||
@Volatile
|
||||
var debugBypass: Boolean = false
|
||||
|
||||
private val _flagged = MutableStateFlow<Map<String, SpamVerdict>>(emptyMap())
|
||||
|
||||
/** Verdicts of spam shown because of [debugBypass], by message ID. */
|
||||
val flagged: StateFlow<Map<String, SpamVerdict>> = _flagged.asStateFlow()
|
||||
|
||||
/** A message prepared for comparison: its leading characters and their trigrams. */
|
||||
private class Fingerprint(val timeMs: Long, val text: IntArray, val trigrams: IntArray)
|
||||
|
||||
private class SharedEntry(val senderKey: String, val fingerprint: Fingerprint)
|
||||
|
||||
private inner class SenderState {
|
||||
val rate = TokenBucketSchedule(
|
||||
listOf(config.messageBucket, config.characterBucket, config.linkBucket)
|
||||
)
|
||||
val history = ArrayDeque<Fingerprint>()
|
||||
}
|
||||
|
||||
private val senders = object : LinkedHashMap<String, SenderState>(64, 0.75f, true) {
|
||||
override fun removeEldestEntry(eldest: MutableMap.MutableEntry<String, SenderState>) =
|
||||
size > config.maxTrackedSenders
|
||||
}
|
||||
|
||||
/** Recent texts from every sender, for the cross-user repeat check. */
|
||||
private val recentTexts = ArrayDeque<SharedEntry>()
|
||||
|
||||
private val verdicts = object : LinkedHashMap<String, SpamVerdict>(256, 0.75f, false) {
|
||||
override fun removeEldestEntry(eldest: MutableMap.MutableEntry<String, SpamVerdict>) =
|
||||
size > config.maxRememberedVerdicts
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true when [message] must be kept out of the chat. With [debugBypass] on, spam is
|
||||
* let through and recorded in [flagged] instead.
|
||||
*/
|
||||
fun shouldHide(message: BitchatMessage, senderKey: String): Boolean {
|
||||
val verdict = check(message, senderKey)
|
||||
if (!verdict.isSpam) return false
|
||||
if (!debugBypass) return true
|
||||
_flagged.update { current ->
|
||||
if (current[message.id] == verdict) current
|
||||
else (current + (message.id to verdict)).entries
|
||||
.toList()
|
||||
.takeLast(MAX_FLAGGED)
|
||||
.associate { it.key to it.value }
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/** Checks a received message; [senderKey] must identify the author stably within a transport. */
|
||||
fun check(message: BitchatMessage, senderKey: String): SpamVerdict = check(
|
||||
senderKey = senderKey,
|
||||
messageId = message.id,
|
||||
content = message.content,
|
||||
timestampMs = message.timestamp.time,
|
||||
isText = message.type == BitchatMessageType.Message
|
||||
)
|
||||
|
||||
fun check(
|
||||
senderKey: String,
|
||||
messageId: String,
|
||||
content: String,
|
||||
timestampMs: Long,
|
||||
isText: Boolean = true
|
||||
): SpamVerdict {
|
||||
if (!enabled) return SpamVerdict.ALLOWED
|
||||
synchronized(this) {
|
||||
verdicts[messageId]?.let { return it }
|
||||
|
||||
val now = clock()
|
||||
val oldestTrusted = now - config.maxBackdateMs
|
||||
val time = timestampMs.coerceIn(oldestTrusted, now)
|
||||
val state = senders.getOrPut(senderKey) { SenderState() }
|
||||
state.rate.prune(oldestTrusted)
|
||||
|
||||
val repetition = config.repetition
|
||||
val text = if (isText) RepetitionDetector.normalize(content, repetition.maxAnalyzedLength) else null
|
||||
val fingerprint = text?.let { fingerprint(it, time) }
|
||||
val verdict = when {
|
||||
text != null && hasRepeatedCharacters(text) -> SpamVerdict.REPEATED_CHARACTERS
|
||||
text != null && repeatsHistory(state, text.size, fingerprint!!) -> SpamVerdict.REPEATED_MESSAGE
|
||||
text != null && repeatsOtherSenders(senderKey, text.size, fingerprint!!) ->
|
||||
SpamVerdict.REPEATED_ACROSS_USERS
|
||||
else -> admitRate(state, content, time, isText)
|
||||
}
|
||||
|
||||
if (text != null && fingerprint != null) {
|
||||
state.history.addLast(fingerprint)
|
||||
while (state.history.size > repetition.historySize) state.history.removeFirst()
|
||||
if (text.size >= repetition.crossUserMinLength) {
|
||||
recentTexts.addLast(SharedEntry(senderKey, fingerprint))
|
||||
while (recentTexts.size > repetition.crossUserHistorySize) recentTexts.removeFirst()
|
||||
}
|
||||
}
|
||||
verdicts[messageId] = verdict
|
||||
return verdict
|
||||
}
|
||||
}
|
||||
|
||||
/** Forgets all senders, e.g. when the filter is toggled or the app is wiped. */
|
||||
fun reset() {
|
||||
synchronized(this) {
|
||||
senders.clear()
|
||||
verdicts.clear()
|
||||
recentTexts.clear()
|
||||
}
|
||||
_flagged.value = emptyMap()
|
||||
}
|
||||
|
||||
private fun hasRepeatedCharacters(text: IntArray): Boolean {
|
||||
val repetition = config.repetition
|
||||
return text.size >= repetition.minRepeatedCharactersLength &&
|
||||
RepetitionDetector.selfSimilarity(text) >= repetition.repeatedCharactersThreshold
|
||||
}
|
||||
|
||||
private fun fingerprint(text: IntArray, timeMs: Long): Fingerprint {
|
||||
val max = config.repetition.maxComparedLength
|
||||
val compared = if (text.size > max) text.copyOf(max) else text
|
||||
return Fingerprint(timeMs, compared, RepetitionDetector.trigrams(compared))
|
||||
}
|
||||
|
||||
private fun repeatsHistory(state: SenderState, length: Int, message: Fingerprint): Boolean {
|
||||
val repetition = config.repetition
|
||||
if (length < repetition.minRepeatedMessageLength) return false
|
||||
val copies = state.history.count { isNearCopy(it, message) }
|
||||
return copies > repetition.allowedRepeats
|
||||
}
|
||||
|
||||
private fun repeatsOtherSenders(senderKey: String, length: Int, message: Fingerprint): Boolean {
|
||||
val repetition = config.repetition
|
||||
if (length < repetition.crossUserMinLength) return false
|
||||
var copies = 0
|
||||
for (entry in recentTexts) {
|
||||
if (entry.senderKey == senderKey || !isNearCopy(entry.fingerprint, message)) continue
|
||||
if (++copies > repetition.crossUserAllowedRepeats) return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
private fun isNearCopy(earlier: Fingerprint, message: Fingerprint): Boolean {
|
||||
val repetition = config.repetition
|
||||
return abs(earlier.timeMs - message.timeMs) <= repetition.historyWindowMs &&
|
||||
lengthsCanMatch(earlier.text.size, message.text.size) &&
|
||||
RepetitionDetector.trigramOverlap(earlier.trigrams, message.trigrams) >= repetition.minSharedTrigrams &&
|
||||
RepetitionDetector.crossSimilarity(
|
||||
earlier.text,
|
||||
message.text,
|
||||
repetition.maxAlignmentPeaks,
|
||||
repetition.minPeakRun
|
||||
) >= repetition.repeatedMessageSimilarity
|
||||
}
|
||||
|
||||
// Cross-similarity is bounded by shorter/longer, so skip pairs that can never reach it.
|
||||
private fun lengthsCanMatch(a: Int, b: Int): Boolean =
|
||||
minOf(a, b).toDouble() / maxOf(a, b) >= config.repetition.repeatedMessageSimilarity
|
||||
|
||||
private fun admitRate(state: SenderState, content: String, timeMs: Long, isText: Boolean): SpamVerdict {
|
||||
val characters = if (isText) content.codePointCount(0, content.length).toDouble() else 0.0
|
||||
val links = if (isText) LinkCounter.count(content).toDouble() else 0.0
|
||||
return when (state.rate.tryAdmit(timeMs, doubleArrayOf(1.0, characters, links))) {
|
||||
-1 -> SpamVerdict.ALLOWED
|
||||
0 -> SpamVerdict.MESSAGE_RATE
|
||||
1 -> SpamVerdict.CHARACTER_RATE
|
||||
else -> SpamVerdict.LINK_RATE
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val MAX_FLAGGED = 500
|
||||
|
||||
/** Process-wide filter shared by every transport and geohash handler. */
|
||||
val shared = SpamFilter()
|
||||
|
||||
/**
|
||||
* True for notices this device generated itself, such as "X favorited you". Every message
|
||||
* read off the wire carries the sender's peer ID; only local notices have none. The
|
||||
* nickname is no signal, since any peer can call itself "system".
|
||||
*/
|
||||
fun isLocalNotice(message: BitchatMessage): Boolean =
|
||||
message.senderPeerID == null && message.sender == "system"
|
||||
|
||||
/** Key for public mesh and mesh channel messages. */
|
||||
fun meshSenderKey(message: BitchatMessage): String =
|
||||
"mesh:" + (message.senderPeerID ?: message.sender)
|
||||
|
||||
/** Key for geohash messages, by the sender's per-geohash Nostr pubkey. */
|
||||
fun nostrSenderKey(pubkeyHex: String): String = "nostr:" + pubkeyHex.lowercase()
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,74 @@
|
||||
package com.bitchat.android.spam
|
||||
|
||||
/**
|
||||
* Tunables for [SpamFilter]. These are code-level knobs only; the user can just turn the
|
||||
* filter on or off.
|
||||
*/
|
||||
data class SpamFilterConfig(
|
||||
/** Messages per sender: a burst of 6, then one more every 10 seconds. */
|
||||
val messageBucket: TokenBucketConfig = TokenBucketConfig(
|
||||
capacity = 6.0,
|
||||
refillPerSecond = 1.0 / 10.0
|
||||
),
|
||||
/** Characters per sender: 1200 per minute, refilled continuously (20 per second). */
|
||||
val characterBucket: TokenBucketConfig = TokenBucketConfig(
|
||||
capacity = 1200.0,
|
||||
refillPerSecond = 1200.0 / 60.0
|
||||
),
|
||||
/** Links per sender: 2 per minute, refilled continuously (one every 30 seconds). */
|
||||
val linkBucket: TokenBucketConfig = TokenBucketConfig(
|
||||
capacity = 2.0,
|
||||
refillPerSecond = 2.0 / 60.0
|
||||
),
|
||||
val repetition: RepetitionConfig = RepetitionConfig(),
|
||||
/**
|
||||
* How far in the past a message timestamp is trusted. Relay backfill (geohash subscribes to
|
||||
* the last hour) and mesh sync deliver history in bursts, so buckets run on message time;
|
||||
* older or future timestamps are clamped into [now - maxBackdateMs, now].
|
||||
*/
|
||||
val maxBackdateMs: Long = 60 * 60 * 1000L,
|
||||
/** Least recently active senders beyond this are forgotten. */
|
||||
val maxTrackedSenders: Int = 1_000,
|
||||
/** Verdicts remembered by message ID so multi-transport duplicates are judged once. */
|
||||
val maxRememberedVerdicts: Int = 4_000
|
||||
)
|
||||
|
||||
data class TokenBucketConfig(
|
||||
val capacity: Double,
|
||||
val refillPerSecond: Double
|
||||
)
|
||||
|
||||
data class RepetitionConfig(
|
||||
/** Messages shorter than this (in characters) are never checked for repeated characters. */
|
||||
val minRepeatedCharactersLength: Int = 32,
|
||||
/** Hide when some shift of the message matches itself at this fraction of positions. */
|
||||
val repeatedCharactersThreshold: Double = 0.85,
|
||||
/** Messages shorter than this are never compared with the sender's history. */
|
||||
val minRepeatedMessageLength: Int = 10,
|
||||
/** Two messages count as the same when their best alignment matches this fraction. */
|
||||
val repeatedMessageSimilarity: Double = 0.8,
|
||||
/** How many near-copies of one text a sender may post within [historyWindowMs]. */
|
||||
val allowedRepeats: Int = 1,
|
||||
/**
|
||||
* How many near-copies of a text *other* senders may already have posted within
|
||||
* [historyWindowMs] before it is hidden. Catches spam spread over many identities.
|
||||
*/
|
||||
val crossUserAllowedRepeats: Int = 2,
|
||||
/** Messages shorter than this are never compared across senders; short replies coincide. */
|
||||
val crossUserMinLength: Int = 16,
|
||||
/** Recent messages from all senders kept for the cross-user comparison. */
|
||||
val crossUserHistorySize: Int = 200,
|
||||
/** Recent messages per sender kept for comparison. */
|
||||
val historySize: Int = 10,
|
||||
val historyWindowMs: Long = 10 * 60 * 1000L,
|
||||
/** Longer messages are analyzed on their first N characters only, bounding CPU per message. */
|
||||
val maxAnalyzedLength: Int = 1_000,
|
||||
/** Messages are compared with others on their first N characters only. */
|
||||
val maxComparedLength: Int = 280,
|
||||
/** Correlation peaks combined per comparison; extra peaks absorb inserted or deleted text. */
|
||||
val maxAlignmentPeaks: Int = 3,
|
||||
/** Extra peaks only count runs of at least this many matching characters. */
|
||||
val minPeakRun: Int = 3,
|
||||
/** Pairs sharing fewer trigrams than this fraction skip the full comparison. */
|
||||
val minSharedTrigrams: Double = 0.25
|
||||
)
|
||||
@ -0,0 +1,115 @@
|
||||
package com.bitchat.android.spam
|
||||
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
|
||||
/**
|
||||
* Persists the user's on/off choice for [SpamFilter.shared] (on by default), plus the debug
|
||||
* settings: tuned limits and the bypass that marks spam instead of hiding it.
|
||||
*/
|
||||
object SpamFilterPreferences {
|
||||
|
||||
private const val PREFS_NAME = "spam_filter_preferences"
|
||||
private const val KEY_ENABLED = "spam_filter_enabled"
|
||||
private const val KEY_DEBUG_BYPASS = "spam_filter_debug_bypass"
|
||||
private const val KEY_MESSAGE_BURST = "spam_filter_message_burst"
|
||||
private const val KEY_SECONDS_PER_MESSAGE = "spam_filter_seconds_per_message"
|
||||
private const val KEY_CHARACTERS_PER_MINUTE = "spam_filter_characters_per_minute"
|
||||
private const val KEY_LINKS_PER_MINUTE = "spam_filter_links_per_minute"
|
||||
private const val KEY_ALLOWED_REPEATS = "spam_filter_allowed_repeats"
|
||||
private const val KEY_CROSS_USER_ALLOWED_REPEATS = "spam_filter_cross_user_allowed_repeats"
|
||||
private const val KEY_CROSS_USER_MIN_LENGTH = "spam_filter_cross_user_min_length"
|
||||
private const val KEY_REPEAT_SIMILARITY = "spam_filter_repeat_similarity"
|
||||
private const val KEY_REPEAT_WINDOW_MINUTES = "spam_filter_repeat_window_minutes"
|
||||
private const val KEY_SELF_MATCH_THRESHOLD = "spam_filter_self_match_threshold"
|
||||
private const val KEY_SELF_MATCH_MIN_LENGTH = "spam_filter_self_match_min_length"
|
||||
private const val DEFAULT_ENABLED = true
|
||||
|
||||
private val _enabled = MutableStateFlow(DEFAULT_ENABLED)
|
||||
val enabled: StateFlow<Boolean> = _enabled.asStateFlow()
|
||||
|
||||
private val _debugBypass = MutableStateFlow(false)
|
||||
val debugBypass: StateFlow<Boolean> = _debugBypass.asStateFlow()
|
||||
|
||||
private val _tuning = MutableStateFlow(SpamTuning.DEFAULT)
|
||||
val tuning: StateFlow<SpamTuning> = _tuning.asStateFlow()
|
||||
|
||||
@Volatile
|
||||
private var sharedPrefs: SharedPreferences? = null
|
||||
|
||||
fun init(context: Context) {
|
||||
if (sharedPrefs != null) return
|
||||
val prefs = context.applicationContext.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
|
||||
sharedPrefs = prefs
|
||||
applyEnabled(prefs.getBoolean(KEY_ENABLED, DEFAULT_ENABLED))
|
||||
applyDebugBypass(prefs.getBoolean(KEY_DEBUG_BYPASS, false))
|
||||
applyTuning(readTuning(prefs))
|
||||
}
|
||||
|
||||
fun setEnabled(enabled: Boolean) {
|
||||
if (_enabled.value == enabled) return
|
||||
sharedPrefs?.edit()?.putBoolean(KEY_ENABLED, enabled)?.apply()
|
||||
applyEnabled(enabled)
|
||||
// Start from a clean slate so budgets don't carry over from before the toggle.
|
||||
SpamFilter.shared.reset()
|
||||
}
|
||||
|
||||
fun setDebugBypass(enabled: Boolean) {
|
||||
sharedPrefs?.edit()?.putBoolean(KEY_DEBUG_BYPASS, enabled)?.apply()
|
||||
applyDebugBypass(enabled)
|
||||
}
|
||||
|
||||
fun setTuning(tuning: SpamTuning) {
|
||||
if (_tuning.value == tuning) return
|
||||
sharedPrefs?.edit()?.apply {
|
||||
putInt(KEY_MESSAGE_BURST, tuning.messageBurst)
|
||||
putInt(KEY_SECONDS_PER_MESSAGE, tuning.secondsPerMessage)
|
||||
putInt(KEY_CHARACTERS_PER_MINUTE, tuning.charactersPerMinute)
|
||||
putInt(KEY_LINKS_PER_MINUTE, tuning.linksPerMinute)
|
||||
putInt(KEY_ALLOWED_REPEATS, tuning.allowedRepeats)
|
||||
putInt(KEY_CROSS_USER_ALLOWED_REPEATS, tuning.crossUserAllowedRepeats)
|
||||
putInt(KEY_CROSS_USER_MIN_LENGTH, tuning.crossUserMinLength)
|
||||
putInt(KEY_REPEAT_SIMILARITY, tuning.repeatSimilarityPercent)
|
||||
putInt(KEY_REPEAT_WINDOW_MINUTES, tuning.repeatWindowMinutes)
|
||||
putInt(KEY_SELF_MATCH_THRESHOLD, tuning.selfMatchPercent)
|
||||
putInt(KEY_SELF_MATCH_MIN_LENGTH, tuning.selfMatchMinLength)
|
||||
apply()
|
||||
}
|
||||
applyTuning(tuning)
|
||||
}
|
||||
|
||||
private fun readTuning(prefs: SharedPreferences): SpamTuning {
|
||||
val d = SpamTuning.DEFAULT
|
||||
return SpamTuning(
|
||||
messageBurst = prefs.getInt(KEY_MESSAGE_BURST, d.messageBurst),
|
||||
secondsPerMessage = prefs.getInt(KEY_SECONDS_PER_MESSAGE, d.secondsPerMessage),
|
||||
charactersPerMinute = prefs.getInt(KEY_CHARACTERS_PER_MINUTE, d.charactersPerMinute),
|
||||
linksPerMinute = prefs.getInt(KEY_LINKS_PER_MINUTE, d.linksPerMinute),
|
||||
allowedRepeats = prefs.getInt(KEY_ALLOWED_REPEATS, d.allowedRepeats),
|
||||
crossUserAllowedRepeats = prefs.getInt(KEY_CROSS_USER_ALLOWED_REPEATS, d.crossUserAllowedRepeats),
|
||||
crossUserMinLength = prefs.getInt(KEY_CROSS_USER_MIN_LENGTH, d.crossUserMinLength),
|
||||
repeatSimilarityPercent = prefs.getInt(KEY_REPEAT_SIMILARITY, d.repeatSimilarityPercent),
|
||||
repeatWindowMinutes = prefs.getInt(KEY_REPEAT_WINDOW_MINUTES, d.repeatWindowMinutes),
|
||||
selfMatchPercent = prefs.getInt(KEY_SELF_MATCH_THRESHOLD, d.selfMatchPercent),
|
||||
selfMatchMinLength = prefs.getInt(KEY_SELF_MATCH_MIN_LENGTH, d.selfMatchMinLength)
|
||||
)
|
||||
}
|
||||
|
||||
private fun applyEnabled(enabled: Boolean) {
|
||||
_enabled.value = enabled
|
||||
SpamFilter.shared.enabled = enabled
|
||||
}
|
||||
|
||||
private fun applyDebugBypass(enabled: Boolean) {
|
||||
_debugBypass.value = enabled
|
||||
SpamFilter.shared.debugBypass = enabled
|
||||
}
|
||||
|
||||
private fun applyTuning(tuning: SpamTuning) {
|
||||
_tuning.value = tuning
|
||||
SpamFilter.shared.config = tuning.toConfig()
|
||||
}
|
||||
}
|
||||
64
app/src/main/java/com/bitchat/android/spam/SpamTuning.kt
Normal file
64
app/src/main/java/com/bitchat/android/spam/SpamTuning.kt
Normal file
@ -0,0 +1,64 @@
|
||||
package com.bitchat.android.spam
|
||||
|
||||
/**
|
||||
* The [SpamFilterConfig] knobs exposed in debug settings, in whole units that suit sliders.
|
||||
* [DEFAULT] matches the code defaults in [SpamFilterConfig].
|
||||
*/
|
||||
data class SpamTuning(
|
||||
val messageBurst: Int,
|
||||
val secondsPerMessage: Int,
|
||||
val charactersPerMinute: Int,
|
||||
val linksPerMinute: Int,
|
||||
val allowedRepeats: Int,
|
||||
val crossUserAllowedRepeats: Int,
|
||||
val crossUserMinLength: Int,
|
||||
val repeatSimilarityPercent: Int,
|
||||
val repeatWindowMinutes: Int,
|
||||
val selfMatchPercent: Int,
|
||||
val selfMatchMinLength: Int
|
||||
) {
|
||||
fun toConfig(): SpamFilterConfig {
|
||||
val base = SpamFilterConfig()
|
||||
return base.copy(
|
||||
messageBucket = TokenBucketConfig(
|
||||
capacity = messageBurst.toDouble(),
|
||||
refillPerSecond = 1.0 / secondsPerMessage.coerceAtLeast(1)
|
||||
),
|
||||
characterBucket = TokenBucketConfig(
|
||||
capacity = charactersPerMinute.toDouble(),
|
||||
refillPerSecond = charactersPerMinute / 60.0
|
||||
),
|
||||
linkBucket = TokenBucketConfig(
|
||||
capacity = linksPerMinute.toDouble(),
|
||||
refillPerSecond = linksPerMinute / 60.0
|
||||
),
|
||||
repetition = base.repetition.copy(
|
||||
allowedRepeats = allowedRepeats,
|
||||
crossUserAllowedRepeats = crossUserAllowedRepeats,
|
||||
crossUserMinLength = crossUserMinLength,
|
||||
repeatedMessageSimilarity = repeatSimilarityPercent / 100.0,
|
||||
historyWindowMs = repeatWindowMinutes * 60_000L,
|
||||
repeatedCharactersThreshold = selfMatchPercent / 100.0,
|
||||
minRepeatedCharactersLength = selfMatchMinLength
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
companion object {
|
||||
val DEFAULT: SpamTuning = SpamFilterConfig().let { config ->
|
||||
SpamTuning(
|
||||
messageBurst = config.messageBucket.capacity.toInt(),
|
||||
secondsPerMessage = Math.round(1.0 / config.messageBucket.refillPerSecond).toInt(),
|
||||
charactersPerMinute = config.characterBucket.capacity.toInt(),
|
||||
linksPerMinute = config.linkBucket.capacity.toInt(),
|
||||
allowedRepeats = config.repetition.allowedRepeats,
|
||||
crossUserAllowedRepeats = config.repetition.crossUserAllowedRepeats,
|
||||
crossUserMinLength = config.repetition.crossUserMinLength,
|
||||
repeatSimilarityPercent = Math.round(config.repetition.repeatedMessageSimilarity * 100).toInt(),
|
||||
repeatWindowMinutes = (config.repetition.historyWindowMs / 60_000L).toInt(),
|
||||
selfMatchPercent = Math.round(config.repetition.repeatedCharactersThreshold * 100).toInt(),
|
||||
selfMatchMinLength = config.repetition.minRepeatedCharactersLength
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,77 @@
|
||||
package com.bitchat.android.spam
|
||||
|
||||
/**
|
||||
* A set of token buckets for one sender, evaluated on message time instead of arrival order.
|
||||
*
|
||||
* Relay backfill and mesh sync hand over a sender's recent history in one burst, often newest
|
||||
* first, so a plain arrival-time bucket would hide an ordinary conversation. Instead each
|
||||
* admitted message stays on a timeline, and a candidate is admitted only if replaying every
|
||||
* bucket over the timeline with the candidate inserted never runs dry. For live, in-order
|
||||
* traffic this behaves exactly like a classic token bucket. Rejected messages cost nothing.
|
||||
*
|
||||
* Entries older than the backdate window are folded into a checkpoint, and nothing can be
|
||||
* inserted before the checkpoint, so the replay stays short and exact.
|
||||
*/
|
||||
internal class TokenBucketSchedule(private val buckets: List<TokenBucketConfig>) {
|
||||
|
||||
private class Entry(val timeMs: Long, val costs: DoubleArray)
|
||||
|
||||
private val entries = ArrayList<Entry>()
|
||||
private var checkpointMs: Long? = null
|
||||
private val checkpointTokens = DoubleArray(buckets.size) { buckets[it].capacity }
|
||||
|
||||
/**
|
||||
* Admits and records the message when every bucket can pay [costs] at [timeMs].
|
||||
* Returns -1 on success, otherwise the index of the first bucket that ran dry.
|
||||
*/
|
||||
fun tryAdmit(timeMs: Long, costs: DoubleArray): Int {
|
||||
require(costs.size == buckets.size)
|
||||
val time = checkpointMs?.let { maxOf(it, timeMs) } ?: timeMs
|
||||
var insertAt = entries.size
|
||||
while (insertAt > 0 && entries[insertAt - 1].timeMs > time) insertAt--
|
||||
val candidate = Entry(time, costs)
|
||||
|
||||
val tokens = checkpointTokens.copyOf()
|
||||
var lastMs = checkpointMs
|
||||
for (i in 0..entries.size) {
|
||||
val entry = when {
|
||||
i < insertAt -> entries[i]
|
||||
i == insertAt -> candidate
|
||||
else -> entries[i - 1]
|
||||
}
|
||||
refill(tokens, lastMs, entry.timeMs)
|
||||
lastMs = entry.timeMs
|
||||
for (b in buckets.indices) {
|
||||
tokens[b] -= entry.costs[b]
|
||||
if (tokens[b] < -EPSILON) return b
|
||||
}
|
||||
}
|
||||
entries.add(insertAt, candidate)
|
||||
return -1
|
||||
}
|
||||
|
||||
/** Folds entries before [cutoffMs] into the checkpoint. */
|
||||
fun prune(cutoffMs: Long) {
|
||||
var folded = 0
|
||||
while (folded < entries.size && entries[folded].timeMs < cutoffMs) {
|
||||
val entry = entries[folded]
|
||||
refill(checkpointTokens, checkpointMs, entry.timeMs)
|
||||
for (b in buckets.indices) checkpointTokens[b] -= entry.costs[b]
|
||||
checkpointMs = entry.timeMs
|
||||
folded++
|
||||
}
|
||||
if (folded > 0) entries.subList(0, folded).clear()
|
||||
}
|
||||
|
||||
private fun refill(tokens: DoubleArray, fromMs: Long?, toMs: Long) {
|
||||
if (fromMs == null || toMs <= fromMs) return
|
||||
val seconds = (toMs - fromMs) / 1000.0
|
||||
for (b in buckets.indices) {
|
||||
tokens[b] = minOf(buckets[b].capacity, tokens[b] + seconds * buckets[b].refillPerSecond)
|
||||
}
|
||||
}
|
||||
|
||||
private companion object {
|
||||
const val EPSILON = 1e-9
|
||||
}
|
||||
}
|
||||
@ -15,6 +15,7 @@ import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Bluetooth
|
||||
import androidx.compose.material.icons.filled.Security
|
||||
import androidx.compose.material.icons.filled.FilterAlt
|
||||
import androidx.compose.material.icons.filled.Speed
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
@ -77,6 +78,7 @@ import com.bitchat.android.net.TorMode
|
||||
import com.bitchat.android.net.TorPreferenceManager
|
||||
import com.bitchat.android.nostr.NostrProofOfWork
|
||||
import com.bitchat.android.nostr.PoWPreferenceManager
|
||||
import com.bitchat.android.spam.SpamFilterPreferences
|
||||
import com.bitchat.android.ui.theme.BitchatMotion
|
||||
import com.bitchat.android.ui.theme.LocalBitchatPalette
|
||||
import com.bitchat.android.util.ShareableApkVariant
|
||||
@ -508,6 +510,7 @@ fun AboutSheet(
|
||||
LaunchedEffect(Unit) { PoWPreferenceManager.init(context) }
|
||||
val powEnabled by PoWPreferenceManager.powEnabled.collectAsState()
|
||||
val powDifficulty by PoWPreferenceManager.powDifficulty.collectAsState()
|
||||
val spamFilterEnabled by SpamFilterPreferences.enabled.collectAsState()
|
||||
var backgroundEnabled by remember { mutableStateOf(com.bitchat.android.service.MeshServicePreferences.isBackgroundEnabled(true)) }
|
||||
var liveVoiceEnabled by remember {
|
||||
mutableStateOf(com.bitchat.android.features.voice.LiveVoicePreferences.isEnabled(context))
|
||||
@ -582,6 +585,21 @@ fun AboutSheet(
|
||||
color = colorScheme.outlineVariant
|
||||
)
|
||||
|
||||
// Spam Filter Toggle
|
||||
SettingsToggleRow(
|
||||
icon = Icons.Filled.FilterAlt,
|
||||
title = stringResource(R.string.about_spam_filter_title),
|
||||
subtitle = stringResource(R.string.about_spam_filter_desc),
|
||||
checked = spamFilterEnabled,
|
||||
onCheckedChange = { SpamFilterPreferences.setEnabled(it) }
|
||||
)
|
||||
|
||||
HorizontalDivider(
|
||||
modifier = Modifier.padding(start = 54.dp),
|
||||
thickness = 1.dp,
|
||||
color = colorScheme.outlineVariant
|
||||
)
|
||||
|
||||
// Tor Toggle
|
||||
SettingsToggleRow(
|
||||
icon = Icons.Filled.Security,
|
||||
|
||||
@ -930,10 +930,8 @@ private fun ChatDialogs(
|
||||
isPresented = showMeshPeerListSheet,
|
||||
viewModel = viewModel,
|
||||
onDismiss = onMeshPeerListDismiss,
|
||||
onShowVerification = {
|
||||
onMeshPeerListDismiss()
|
||||
viewModel.showVerificationSheet(fromSidebar = true)
|
||||
}
|
||||
// Stacks on top of the network sheet, so dismissing it lands back here.
|
||||
onShowVerification = viewModel::showVerificationSheet
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@ -1300,21 +1300,12 @@ class ChatViewModel(
|
||||
notificationManager.clearMeshMentionNotifications()
|
||||
}
|
||||
|
||||
private var reopenSidebarAfterVerification = false
|
||||
|
||||
fun showVerificationSheet(fromSidebar: Boolean = false) {
|
||||
if (fromSidebar) {
|
||||
reopenSidebarAfterVerification = true
|
||||
}
|
||||
fun showVerificationSheet() {
|
||||
state.setShowVerificationSheet(true)
|
||||
}
|
||||
|
||||
fun hideVerificationSheet() {
|
||||
state.setShowVerificationSheet(false)
|
||||
if (reopenSidebarAfterVerification) {
|
||||
reopenSidebarAfterVerification = false
|
||||
state.setShowMeshPeerList(true)
|
||||
}
|
||||
}
|
||||
|
||||
fun showSecurityVerificationSheet() {
|
||||
@ -1490,6 +1481,9 @@ class ChatViewModel(
|
||||
} catch (_: Exception) {
|
||||
wipeComplete = false
|
||||
}
|
||||
|
||||
// The spam filter keeps recent message text and sender keys for its repeat checks
|
||||
com.bitchat.android.spam.SpamFilter.shared.reset()
|
||||
|
||||
// Clear all cryptographic data
|
||||
if (!clearAllCryptographicData()) wipeComplete = false
|
||||
|
||||
@ -464,8 +464,8 @@ fun MeshPeerListSheet(
|
||||
Icon(
|
||||
imageVector = Icons.Outlined.QrCode,
|
||||
contentDescription = stringResource(R.string.verify_title),
|
||||
tint = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
modifier = Modifier.size(22.dp)
|
||||
tint = MaterialTheme.colorScheme.primary,
|
||||
modifier = Modifier.size(HeaderIconSize)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -87,6 +87,8 @@ import com.bitchat.android.mesh.MeshService
|
||||
import com.bitchat.android.model.BitchatMessage
|
||||
import com.bitchat.android.model.BitchatMessageType
|
||||
import com.bitchat.android.model.DeliveryStatus
|
||||
import com.bitchat.android.spam.SpamFilter
|
||||
import com.bitchat.android.spam.SpamVerdict
|
||||
import com.bitchat.android.ui.media.FileMessageItem
|
||||
import com.bitchat.android.ui.theme.BASE_FONT_SIZE
|
||||
import com.bitchat.android.ui.theme.BitchatMotion
|
||||
@ -253,6 +255,8 @@ fun MessagesList(
|
||||
// Collected once here so individual rows never subscribe to the preference flow; a mode
|
||||
// switch simply recomposes the list against the new layout.
|
||||
val bubbles by ChatUiModeManager.modeFlow.collectAsState()
|
||||
// Spam let through by the debug bypass; empty unless that debug setting is on.
|
||||
val spamFlags by SpamFilter.shared.flagged.collectAsState()
|
||||
|
||||
// A fresh scroll position per conversation. Sharing one state meant a switch inherited the
|
||||
// previous channel's offset and then had to correct itself, which is what the jump was.
|
||||
@ -268,8 +272,11 @@ fun MessagesList(
|
||||
var hasScrolledToInitialPosition by remember(conversationKey) { mutableStateOf(false) }
|
||||
var followIncomingMessages by remember(conversationKey) { mutableStateOf(true) }
|
||||
|
||||
// Smart scroll: auto-scroll to bottom for initial load, then follow unless user scrolls away
|
||||
LaunchedEffect(messages.size) {
|
||||
// Smart scroll: auto-scroll to bottom for initial load, then follow unless user scrolls away.
|
||||
//
|
||||
// Keyed on the newest message as well as the count: a timeline at its retention cap evicts its
|
||||
// oldest message for every arrival, so its size stops changing while messages keep coming in.
|
||||
LaunchedEffect(conversationKey, messages.size, messages.lastOrNull()?.id) {
|
||||
if (messages.isNotEmpty()) {
|
||||
val isFirstLoad = !hasScrolledToInitialPosition
|
||||
if (isFirstLoad || followIncomingMessages) {
|
||||
@ -384,6 +391,7 @@ fun MessagesList(
|
||||
mentionPeerIdentities = resolvedMentionPeerIdentities,
|
||||
showSender = !isGrouped,
|
||||
bubbles = bubbles.isBubbles,
|
||||
spamVerdict = spamFlags[message.id],
|
||||
topSpacing = MessageGrouping.topSpacingFor(
|
||||
isGrouped = isGrouped,
|
||||
isFirstInList = originalIndex == 0
|
||||
@ -418,6 +426,8 @@ fun MessageItem(
|
||||
mentionPeerIdentities: Map<String, PeerIdentity> = emptyMap(),
|
||||
showSender: Boolean = true,
|
||||
bubbles: Boolean = false,
|
||||
/** Set when the spam filter's debug bypass let this message through; tints the row red. */
|
||||
spamVerdict: SpamVerdict? = null,
|
||||
topSpacing: Dp = 0.dp,
|
||||
onNicknameClick: ((String) -> Unit)? = null,
|
||||
onMessageLongPress: ((BitchatMessage) -> Unit)? = null,
|
||||
@ -427,11 +437,19 @@ fun MessageItem(
|
||||
) {
|
||||
val colorScheme = MaterialTheme.colorScheme
|
||||
val timeFormatter = remember { SimpleDateFormat(CHAT_TIMESTAMP_PATTERN, Locale.getDefault()) }
|
||||
val spamModifier = if (spamVerdict != null) {
|
||||
Modifier
|
||||
.background(colorScheme.error.copy(alpha = 0.18f), RoundedCornerShape(8.dp))
|
||||
.padding(horizontal = 6.dp, vertical = 4.dp)
|
||||
} else {
|
||||
Modifier
|
||||
}
|
||||
|
||||
Column(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.padding(top = topSpacing),
|
||||
.padding(top = topSpacing)
|
||||
.then(spamModifier),
|
||||
verticalArrangement = Arrangement.spacedBy(0.dp)
|
||||
) {
|
||||
Box(modifier = Modifier.fillMaxWidth()) {
|
||||
@ -497,9 +515,29 @@ fun MessageItem(
|
||||
}
|
||||
|
||||
// Link previews removed; links are now highlighted inline and clickable within the message text
|
||||
|
||||
if (spamVerdict != null) {
|
||||
Text(
|
||||
text = "spam filter would hide this: ${spamVerdict.debugLabel()}",
|
||||
fontFamily = BitchatFontFamily,
|
||||
fontSize = 11.sp,
|
||||
color = colorScheme.error,
|
||||
modifier = Modifier.padding(top = 2.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun SpamVerdict.debugLabel(): String = when (this) {
|
||||
SpamVerdict.ALLOWED -> "allowed"
|
||||
SpamVerdict.MESSAGE_RATE -> "too many messages"
|
||||
SpamVerdict.CHARACTER_RATE -> "too many characters"
|
||||
SpamVerdict.LINK_RATE -> "too many links"
|
||||
SpamVerdict.REPEATED_MESSAGE -> "repeated message"
|
||||
SpamVerdict.REPEATED_ACROSS_USERS -> "same text from other users"
|
||||
SpamVerdict.REPEATED_CHARACTERS -> "repeated characters"
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
private fun MessageTextWithClickableNicknames(
|
||||
|
||||
@ -512,7 +512,7 @@ private fun bitmapFromMatrix(matrix: BitMatrix): Bitmap {
|
||||
return bitmap
|
||||
}
|
||||
|
||||
private class QRCodeAnalyzer(
|
||||
internal class QRCodeAnalyzer(
|
||||
private val onCode: (String) -> Unit
|
||||
) : ImageAnalysis.Analyzer {
|
||||
private val scanner = BarcodeScanning.getClient(
|
||||
|
||||
@ -355,6 +355,9 @@ fun DebugSettingsSheet(
|
||||
item {
|
||||
DistributionInfoSection(distributionInfo)
|
||||
}
|
||||
item {
|
||||
SpamFilterDebugSection()
|
||||
}
|
||||
// Verbose logging toggle
|
||||
item {
|
||||
Surface(shape = RoundedCornerShape(12.dp), color = colorScheme.surfaceVariant.copy(alpha = 0.2f)) {
|
||||
|
||||
@ -0,0 +1,139 @@
|
||||
package com.bitchat.android.ui.debug
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.FilterAlt
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Slider
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Switch
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.bitchat.android.spam.SpamFilter
|
||||
import com.bitchat.android.spam.SpamFilterPreferences
|
||||
import com.bitchat.android.spam.SpamTuning
|
||||
import com.bitchat.android.ui.theme.BitchatFontFamily
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
/** Debug controls for the spam filter: live limit tuning and the mark-instead-of-hide bypass. */
|
||||
@Composable
|
||||
fun SpamFilterDebugSection() {
|
||||
val colorScheme = MaterialTheme.colorScheme
|
||||
val filterEnabled by SpamFilterPreferences.enabled.collectAsState()
|
||||
val bypass by SpamFilterPreferences.debugBypass.collectAsState()
|
||||
val tuning by SpamFilterPreferences.tuning.collectAsState()
|
||||
val flagged by SpamFilter.shared.flagged.collectAsState()
|
||||
val update: (SpamTuning) -> Unit = SpamFilterPreferences::setTuning
|
||||
|
||||
Surface(shape = RoundedCornerShape(12.dp), color = colorScheme.surfaceVariant.copy(alpha = 0.2f)) {
|
||||
Column(Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(10.dp)) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
Icon(Icons.Filled.FilterAlt, contentDescription = null, tint = colorScheme.error)
|
||||
Text("Spam filter", fontFamily = BitchatFontFamily, fontSize = 14.sp, fontWeight = FontWeight.Medium)
|
||||
Spacer(Modifier.weight(1f))
|
||||
Switch(checked = filterEnabled, onCheckedChange = SpamFilterPreferences::setEnabled)
|
||||
}
|
||||
Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
Column(Modifier.weight(1f)) {
|
||||
Text("Show spam in red instead of hiding it", fontFamily = BitchatFontFamily, fontSize = 12.sp)
|
||||
Text(
|
||||
"Bypass for debugging. ${flagged.size} marked since the last reset or limit change.",
|
||||
fontFamily = BitchatFontFamily,
|
||||
fontSize = 11.sp,
|
||||
color = colorScheme.onSurface.copy(alpha = 0.7f)
|
||||
)
|
||||
}
|
||||
Switch(checked = bypass, onCheckedChange = SpamFilterPreferences::setDebugBypass, enabled = filterEnabled)
|
||||
}
|
||||
|
||||
Text(
|
||||
"Limits per sender. Changing one starts every sender with a full budget.",
|
||||
fontFamily = BitchatFontFamily,
|
||||
fontSize = 11.sp,
|
||||
color = colorScheme.onSurface.copy(alpha = 0.7f)
|
||||
)
|
||||
TuningSlider("Message burst", "${tuning.messageBurst} messages", tuning.messageBurst, 1..30) {
|
||||
update(tuning.copy(messageBurst = it))
|
||||
}
|
||||
TuningSlider("Message refill", "1 every ${tuning.secondsPerMessage} s", tuning.secondsPerMessage, 1..60) {
|
||||
update(tuning.copy(secondsPerMessage = it))
|
||||
}
|
||||
TuningSlider("Characters", "${tuning.charactersPerMinute} per minute", tuning.charactersPerMinute, 100..3000, step = 50) {
|
||||
update(tuning.copy(charactersPerMinute = it))
|
||||
}
|
||||
TuningSlider("Links", "${tuning.linksPerMinute} per minute", tuning.linksPerMinute, 0..10) {
|
||||
update(tuning.copy(linksPerMinute = it))
|
||||
}
|
||||
TuningSlider("Repeats allowed", "${tuning.allowedRepeats} near-copies", tuning.allowedRepeats, 0..10) {
|
||||
update(tuning.copy(allowedRepeats = it))
|
||||
}
|
||||
TuningSlider("Copies from other users allowed", "${tuning.crossUserAllowedRepeats} near-copies", tuning.crossUserAllowedRepeats, 0..10) {
|
||||
update(tuning.copy(crossUserAllowedRepeats = it))
|
||||
}
|
||||
TuningSlider("Compare across users from", "${tuning.crossUserMinLength} chars", tuning.crossUserMinLength, 4..100) {
|
||||
update(tuning.copy(crossUserMinLength = it))
|
||||
}
|
||||
TuningSlider("Near-copy match", "≥ ${tuning.repeatSimilarityPercent}%", tuning.repeatSimilarityPercent, 50..100) {
|
||||
update(tuning.copy(repeatSimilarityPercent = it))
|
||||
}
|
||||
TuningSlider("Repeat window", "${tuning.repeatWindowMinutes} min", tuning.repeatWindowMinutes, 1..60) {
|
||||
update(tuning.copy(repeatWindowMinutes = it))
|
||||
}
|
||||
TuningSlider("Repeated characters", "≥ ${tuning.selfMatchPercent}% self-match", tuning.selfMatchPercent, 50..100) {
|
||||
update(tuning.copy(selfMatchPercent = it))
|
||||
}
|
||||
TuningSlider("Repeated characters from", "${tuning.selfMatchMinLength} chars", tuning.selfMatchMinLength, 8..200, step = 4) {
|
||||
update(tuning.copy(selfMatchMinLength = it))
|
||||
}
|
||||
|
||||
Row {
|
||||
Spacer(Modifier.weight(1f))
|
||||
TextButton(onClick = { update(SpamTuning.DEFAULT) }, enabled = tuning != SpamTuning.DEFAULT) {
|
||||
Text("Reset to defaults", fontFamily = BitchatFontFamily, fontSize = 12.sp)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun TuningSlider(
|
||||
label: String,
|
||||
valueText: String,
|
||||
value: Int,
|
||||
range: IntRange,
|
||||
step: Int = 1,
|
||||
onChange: (Int) -> Unit
|
||||
) {
|
||||
val colorScheme = MaterialTheme.colorScheme
|
||||
Column {
|
||||
Row {
|
||||
Text(label, fontFamily = BitchatFontFamily, fontSize = 11.sp, color = colorScheme.onSurface.copy(alpha = 0.7f))
|
||||
Spacer(Modifier.weight(1f))
|
||||
Text(valueText, fontFamily = BitchatFontFamily, fontSize = 11.sp)
|
||||
}
|
||||
Slider(
|
||||
value = value.toFloat(),
|
||||
onValueChange = { raw ->
|
||||
val snapped = range.first + ((raw - range.first) / step).roundToInt() * step
|
||||
onChange(snapped.coerceIn(range))
|
||||
},
|
||||
valueRange = range.first.toFloat()..range.last.toFloat(),
|
||||
steps = ((range.last - range.first) / step - 1).coerceAtLeast(0)
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -111,7 +111,7 @@ fun VoiceNotePlayer(
|
||||
onSeek = seekTo
|
||||
)
|
||||
val durText = if (durationMs > 0) String.format("%02d:%02d", (durationMs / 1000) / 60, (durationMs / 1000) % 60) else "--:--"
|
||||
Text(text = durText, fontFamily = BitchatFontFamily, fontSize = 12.sp)
|
||||
Text(text = durText, fontFamily = BitchatFontFamily, fontSize = 12.sp, color = MaterialTheme.colorScheme.onSurface)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -2,6 +2,7 @@ package com.bitchat.android.ui.media
|
||||
|
||||
import androidx.compose.foundation.Canvas
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.snapshots.SnapshotStateList
|
||||
@ -50,6 +51,10 @@ fun WaveformPreview(
|
||||
path: String,
|
||||
sendProgress: Float?,
|
||||
playbackProgress: Float?,
|
||||
// Unplayed bars follow the surrounding text colour so they read on any bubble tint or
|
||||
// surface; the played portion uses the theme accent, which is tuned per light/dark scheme.
|
||||
baseColor: Color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.35f),
|
||||
playedColor: Color = MaterialTheme.colorScheme.primary,
|
||||
onLoaded: ((FloatArray) -> Unit)? = null,
|
||||
onSeek: ((Float) -> Unit)? = null
|
||||
) {
|
||||
@ -74,10 +79,10 @@ fun WaveformPreview(
|
||||
modifier = modifier,
|
||||
samples = stateSamples,
|
||||
fillProgress = if (stateSamples.isEmpty()) 0f else progress,
|
||||
baseColor = Color(0x2200FF7F),
|
||||
baseColor = baseColor,
|
||||
fillColor = when {
|
||||
sendProgress != null -> Color(0xFF1E88E5) // blue while sending
|
||||
else -> Color(0xFF00C851) // green during playback
|
||||
else -> playedColor
|
||||
},
|
||||
onSeek = onSeek
|
||||
)
|
||||
|
||||
@ -393,4 +393,6 @@
|
||||
<string name="about_app_language">لغة التطبيق</string>
|
||||
<string name="about_system_default">إعداد النظام الافتراضي</string>
|
||||
<string name="about_select_language">اختر اللغة</string>
|
||||
<string name="about_spam_filter_title">فلتر الرسائل المزعجة</string>
|
||||
<string name="about_spam_filter_desc">إخفاء الرسائل العامة من الأشخاص الذين يرسلون بسرعة مفرطة أو يكررون رسائلهم أو يغرقون الدردشة بالروابط.</string>
|
||||
</resources>
|
||||
|
||||
@ -380,4 +380,6 @@
|
||||
<string name="about_app_language">অ্যাপের ভাষা</string>
|
||||
<string name="about_system_default">সিস্টেম ডিফল্ট</string>
|
||||
<string name="about_select_language">ভাষা নির্বাচন করুন</string>
|
||||
<string name="about_spam_filter_title">স্প্যাম ফিল্টার</string>
|
||||
<string name="about_spam_filter_desc">যারা খুব দ্রুত বার্তা পাঠান, একই কথা বারবার বলেন বা অতিরিক্ত লিঙ্ক পাঠান, তাঁদের প্রকাশ্য বার্তা লুকান।</string>
|
||||
</resources>
|
||||
|
||||
@ -394,4 +394,6 @@
|
||||
<string name="about_app_language">App-Sprache</string>
|
||||
<string name="about_system_default">Systemstandard</string>
|
||||
<string name="about_select_language">Sprache auswählen</string>
|
||||
<string name="about_spam_filter_title">Spamfilter</string>
|
||||
<string name="about_spam_filter_desc">Öffentliche Nachrichten von Personen ausblenden, die zu schnell schreiben, sich wiederholen oder zu viele Links senden.</string>
|
||||
</resources>
|
||||
|
||||
@ -393,4 +393,6 @@
|
||||
<string name="about_app_language">Idioma de la app</string>
|
||||
<string name="about_system_default">Predeterminado del sistema</string>
|
||||
<string name="about_select_language">Seleccionar idioma</string>
|
||||
<string name="about_spam_filter_title">Filtro de spam</string>
|
||||
<string name="about_spam_filter_desc">Oculta los mensajes públicos de quienes publican demasiado rápido, se repiten o envían demasiados enlaces.</string>
|
||||
</resources>
|
||||
|
||||
@ -619,4 +619,10 @@
|
||||
<string name="about_app_language">Aplikazioaren hizkuntza</string>
|
||||
<string name="about_system_default">Sistemaren lehenetsia</string>
|
||||
<string name="about_select_language">Hautatu hizkuntza</string>
|
||||
<string name="app_name">bitchat</string>
|
||||
<string name="file_viewer_name">📄 %1$s</string>
|
||||
<string name="version_prefix">v%1$s</string>
|
||||
<string name="image_star">image/*</string>
|
||||
<string name="about_spam_filter_title">Spam-iragazkia</string>
|
||||
<string name="about_spam_filter_desc">Ezkutatu azkarregi idazten duten, mezuak errepikatzen dituzten edo esteka gehiegi bidaltzen dituzten pertsonen mezu publikoak.</string>
|
||||
</resources>
|
||||
|
||||
@ -380,4 +380,6 @@
|
||||
<string name="about_app_language">زبان برنامه</string>
|
||||
<string name="about_system_default">پیشفرض سیستم</string>
|
||||
<string name="about_select_language">انتخاب زبان</string>
|
||||
<string name="about_spam_filter_title">فیلتر هرزنامه</string>
|
||||
<string name="about_spam_filter_desc">پیامهای عمومی افرادی را که خیلی سریع پیام میفرستند، خود را تکرار میکنند یا پیوندهای زیادی میفرستند پنهان کن.</string>
|
||||
</resources>
|
||||
|
||||
@ -391,4 +391,6 @@
|
||||
<string name="about_app_language">Wika ng app</string>
|
||||
<string name="about_system_default">Default ng system</string>
|
||||
<string name="about_select_language">Pumili ng wika</string>
|
||||
<string name="about_spam_filter_title">Filter ng spam</string>
|
||||
<string name="about_spam_filter_desc">Itago ang mga pampublikong mensahe mula sa mga taong masyadong mabilis mag-post, paulit-ulit ang sinasabi, o nagpapadala ng napakaraming link.</string>
|
||||
</resources>
|
||||
|
||||
@ -405,4 +405,6 @@
|
||||
<string name="about_app_language">Langue de l’appli</string>
|
||||
<string name="about_system_default">Langue du système</string>
|
||||
<string name="about_select_language">Choisir la langue</string>
|
||||
<string name="about_spam_filter_title">Filtre antispam</string>
|
||||
<string name="about_spam_filter_desc">Masquer les messages publics des personnes qui publient trop vite, se répètent ou inondent le chat de liens.</string>
|
||||
</resources>
|
||||
|
||||
@ -392,4 +392,6 @@
|
||||
<string name="about_app_language">שפת האפליקציה</string>
|
||||
<string name="about_system_default">ברירת המחדל של המערכת</string>
|
||||
<string name="about_select_language">בחירת שפה</string>
|
||||
<string name="about_spam_filter_title">מסנן ספאם</string>
|
||||
<string name="about_spam_filter_desc">הסתר הודעות ציבוריות מאנשים ששולחים מהר מדי, חוזרים על עצמם או מציפים בקישורים.</string>
|
||||
</resources>
|
||||
|
||||
@ -393,4 +393,6 @@
|
||||
<string name="about_app_language">ऐप की भाषा</string>
|
||||
<string name="about_system_default">सिस्टम डिफ़ॉल्ट</string>
|
||||
<string name="about_select_language">भाषा चुनें</string>
|
||||
<string name="about_spam_filter_title">स्पैम फ़िल्टर</string>
|
||||
<string name="about_spam_filter_desc">बहुत तेज़ी से संदेश भेजने, एक ही बात दोहराने या बहुत अधिक लिंक भेजने वाले लोगों के सार्वजनिक संदेश छिपाएँ।</string>
|
||||
</resources>
|
||||
|
||||
@ -393,4 +393,6 @@
|
||||
<string name="about_app_language">Bahasa aplikasi</string>
|
||||
<string name="about_system_default">Default sistem</string>
|
||||
<string name="about_select_language">Pilih bahasa</string>
|
||||
<string name="about_spam_filter_title">Filter spam</string>
|
||||
<string name="about_spam_filter_desc">Sembunyikan pesan publik dari orang yang mengirim terlalu cepat, mengulang pesan, atau membanjiri obrolan dengan tautan.</string>
|
||||
</resources>
|
||||
|
||||
@ -420,4 +420,6 @@
|
||||
<string name="about_app_language">Lingua dell’app</string>
|
||||
<string name="about_system_default">Predefinita di sistema</string>
|
||||
<string name="about_select_language">Seleziona lingua</string>
|
||||
<string name="about_spam_filter_title">Filtro antispam</string>
|
||||
<string name="about_spam_filter_desc">Nascondi i messaggi pubblici di chi scrive troppo velocemente, si ripete o invia troppi link.</string>
|
||||
</resources>
|
||||
|
||||
@ -393,4 +393,6 @@
|
||||
<string name="about_app_language">アプリの言語</string>
|
||||
<string name="about_system_default">システムのデフォルト</string>
|
||||
<string name="about_select_language">言語を選択</string>
|
||||
<string name="about_spam_filter_title">スパムフィルター</string>
|
||||
<string name="about_spam_filter_desc">短時間に大量のメッセージを送信したり、同じ内容を繰り返したり、リンクを大量に投稿したりする人の公開メッセージを非表示にします。</string>
|
||||
</resources>
|
||||
|
||||
@ -380,4 +380,6 @@
|
||||
<string name="about_app_language">აპის ენა</string>
|
||||
<string name="about_system_default">სისტემის ნაგულისხმევი</string>
|
||||
<string name="about_select_language">ენის არჩევა</string>
|
||||
<string name="about_spam_filter_title">სპამის ფილტრი</string>
|
||||
<string name="about_spam_filter_desc">დამალე იმ ადამიანების საჯარო შეტყობინებები, რომლებიც ზედმეტად სწრაფად წერენ, იმეორებენ შეტყობინებებს ან აგზავნიან ზედმეტად ბევრ ბმულს.</string>
|
||||
</resources>
|
||||
|
||||
@ -393,4 +393,6 @@
|
||||
<string name="about_app_language">앱 언어</string>
|
||||
<string name="about_system_default">시스템 기본값</string>
|
||||
<string name="about_select_language">언어 선택</string>
|
||||
<string name="about_spam_filter_title">스팸 필터</string>
|
||||
<string name="about_spam_filter_desc">너무 빠르게 메시지를 보내거나, 같은 내용을 반복하거나, 링크를 과도하게 보내는 사람의 공개 메시지를 숨깁니다.</string>
|
||||
</resources>
|
||||
|
||||
@ -400,4 +400,6 @@
|
||||
<string name="about_app_language">Fitenin’ny app</string>
|
||||
<string name="about_system_default">Fitenin’ny rafitra</string>
|
||||
<string name="about_select_language">Safidio ny fiteny</string>
|
||||
<string name="about_spam_filter_title">Sivana spam</string>
|
||||
<string name="about_spam_filter_desc">Afeno ny hafatra ampahibemaso avy amin’ireo olona mandefa haingana loatra, mamerimberina hafatra, na mandefa rohy be loatra.</string>
|
||||
</resources>
|
||||
|
||||
@ -433,4 +433,6 @@
|
||||
<string name="about_app_language">Bahasa aplikasi</string>
|
||||
<string name="about_system_default">Lalai sistem</string>
|
||||
<string name="about_select_language">Pilih bahasa</string>
|
||||
<string name="about_spam_filter_title">Penapis spam</string>
|
||||
<string name="about_spam_filter_desc">Sembunyikan mesej awam daripada orang yang menghantar terlalu pantas, mengulang mesej atau membanjiri perbualan dengan pautan.</string>
|
||||
</resources>
|
||||
|
||||
@ -391,4 +391,6 @@
|
||||
<string name="about_app_language">एपको भाषा</string>
|
||||
<string name="about_system_default">प्रणालीको पूर्वनिर्धारित</string>
|
||||
<string name="about_select_language">भाषा छान्नुहोस्</string>
|
||||
<string name="about_spam_filter_title">स्प्याम फिल्टर</string>
|
||||
<string name="about_spam_filter_desc">धेरै छिटो सन्देश पठाउने, एउटै कुरा दोहोर्याउने वा धेरै लिङ्क पठाउने व्यक्तिका सार्वजनिक सन्देशहरू लुकाउनुहोस्।</string>
|
||||
</resources>
|
||||
|
||||
@ -418,4 +418,6 @@
|
||||
<string name="about_app_language">App-taal</string>
|
||||
<string name="about_system_default">Systeemstandaard</string>
|
||||
<string name="about_select_language">Taal selecteren</string>
|
||||
<string name="about_spam_filter_title">Spamfilter</string>
|
||||
<string name="about_spam_filter_desc">Verberg openbare berichten van mensen die te snel posten, zichzelf herhalen of te veel links sturen.</string>
|
||||
</resources>
|
||||
|
||||
@ -380,4 +380,6 @@
|
||||
<string name="about_app_language">ایپ دی بولی</string>
|
||||
<string name="about_system_default">سسٹم دی ڈیفالٹ</string>
|
||||
<string name="about_select_language">بولی چݨو</string>
|
||||
<string name="about_spam_filter_title">سپیم فلٹر</string>
|
||||
<string name="about_spam_filter_desc">اوہناں لوکاں دے عوامی پیغام لُکاؤ جو بہت تیزی نال پیغام بھیجدے، گلّاں دُہراوندے یا بہت سارے لنک بھیجدے نیں۔</string>
|
||||
</resources>
|
||||
|
||||
@ -398,4 +398,6 @@
|
||||
<string name="about_app_language">Język aplikacji</string>
|
||||
<string name="about_system_default">Domyślny systemu</string>
|
||||
<string name="about_select_language">Wybierz język</string>
|
||||
<string name="about_spam_filter_title">Filtr spamu</string>
|
||||
<string name="about_spam_filter_desc">Ukrywaj publiczne wiadomości od osób, które piszą zbyt szybko, powtarzają się lub wysyłają zbyt wiele linków.</string>
|
||||
</resources>
|
||||
|
||||
@ -392,4 +392,6 @@
|
||||
<string name="about_app_language">Idioma do app</string>
|
||||
<string name="about_system_default">Padrão do sistema</string>
|
||||
<string name="about_select_language">Selecionar idioma</string>
|
||||
<string name="about_spam_filter_title">Filtro de spam</string>
|
||||
<string name="about_spam_filter_desc">Ocultar mensagens públicas de pessoas que enviam mensagens rápido demais, se repetem ou enviam links em excesso.</string>
|
||||
</resources>
|
||||
|
||||
@ -393,4 +393,6 @@
|
||||
<string name="about_app_language">Idioma da aplicação</string>
|
||||
<string name="about_system_default">Predefinição do sistema</string>
|
||||
<string name="about_select_language">Selecionar idioma</string>
|
||||
<string name="about_spam_filter_title">Filtro de spam</string>
|
||||
<string name="about_spam_filter_desc">Ocultar mensagens públicas de pessoas que publicam demasiado depressa, se repetem ou enviam demasiadas ligações.</string>
|
||||
</resources>
|
||||
|
||||
@ -380,4 +380,6 @@
|
||||
<string name="about_app_language">Язык приложения</string>
|
||||
<string name="about_system_default">Системный язык</string>
|
||||
<string name="about_select_language">Выбрать язык</string>
|
||||
<string name="about_spam_filter_title">Спам-фильтр</string>
|
||||
<string name="about_spam_filter_desc">Скрывать публичные сообщения от тех, кто пишет слишком быстро, повторяется или отправляет слишком много ссылок.</string>
|
||||
</resources>
|
||||
|
||||
@ -378,4 +378,6 @@
|
||||
<string name="about_app_language">Appspråk</string>
|
||||
<string name="about_system_default">Systemstandard</string>
|
||||
<string name="about_select_language">Välj språk</string>
|
||||
<string name="about_spam_filter_title">Spamfilter</string>
|
||||
<string name="about_spam_filter_desc">Dölj offentliga meddelanden från personer som skriver för snabbt, upprepar sig eller skickar för många länkar.</string>
|
||||
</resources>
|
||||
|
||||
@ -390,4 +390,6 @@
|
||||
<string name="about_app_language">பயன்பாட்டு மொழி</string>
|
||||
<string name="about_system_default">கணினி இயல்புநிலை</string>
|
||||
<string name="about_select_language">மொழியைத் தேர்ந்தெடுக்கவும்</string>
|
||||
<string name="about_spam_filter_title">ஸ்பேம் வடிகட்டி</string>
|
||||
<string name="about_spam_filter_desc">மிக வேகமாகச் செய்திகளை அனுப்புபவர்கள், ஒரே கருத்தைத் திரும்பத் திரும்பக் கூறுபவர்கள் அல்லது அதிக இணைப்புகளை அனுப்புபவர்களின் பொதுச் செய்திகளை மறைக்கவும்.</string>
|
||||
</resources>
|
||||
|
||||
@ -380,4 +380,6 @@
|
||||
<string name="about_app_language">ภาษาของแอป</string>
|
||||
<string name="about_system_default">ค่าเริ่มต้นของระบบ</string>
|
||||
<string name="about_select_language">เลือกภาษา</string>
|
||||
<string name="about_spam_filter_title">ตัวกรองสแปม</string>
|
||||
<string name="about_spam_filter_desc">ซ่อนข้อความสาธารณะจากผู้ที่ส่งข้อความเร็วเกินไป ส่งข้อความซ้ำ หรือส่งลิงก์จำนวนมากเกินไป</string>
|
||||
</resources>
|
||||
|
||||
@ -378,4 +378,6 @@
|
||||
<string name="about_app_language">Uygulama dili</string>
|
||||
<string name="about_system_default">Sistem varsayılanı</string>
|
||||
<string name="about_select_language">Dil seçin</string>
|
||||
<string name="about_spam_filter_title">Spam filtresi</string>
|
||||
<string name="about_spam_filter_desc">Çok hızlı mesaj gönderen, kendini tekrarlayan veya çok fazla bağlantı paylaşan kişilerin herkese açık mesajlarını gizle.</string>
|
||||
</resources>
|
||||
|
||||
@ -400,4 +400,6 @@
|
||||
<string name="about_app_language">Мова застосунку</string>
|
||||
<string name="about_system_default">Системна мова</string>
|
||||
<string name="about_select_language">Вибрати мову</string>
|
||||
<string name="about_spam_filter_title">Спам-фільтр</string>
|
||||
<string name="about_spam_filter_desc">Приховувати публічні повідомлення від тих, хто пише надто швидко, повторюється або надсилає забагато посилань.</string>
|
||||
</resources>
|
||||
|
||||
@ -393,4 +393,6 @@
|
||||
<string name="about_app_language">ایپ کی زبان</string>
|
||||
<string name="about_system_default">سسٹم ڈیفالٹ</string>
|
||||
<string name="about_select_language">زبان منتخب کریں</string>
|
||||
<string name="about_spam_filter_title">اسپام فلٹر</string>
|
||||
<string name="about_spam_filter_desc">ایسے لوگوں کے عوامی پیغامات چھپائیں جو بہت تیزی سے پیغامات بھیجتے ہیں، اپنی بات دہراتے ہیں یا بہت زیادہ لنکس بھیجتے ہیں۔</string>
|
||||
</resources>
|
||||
|
||||
@ -380,4 +380,6 @@
|
||||
<string name="about_app_language">Ngôn ngữ ứng dụng</string>
|
||||
<string name="about_system_default">Mặc định hệ thống</string>
|
||||
<string name="about_select_language">Chọn ngôn ngữ</string>
|
||||
<string name="about_spam_filter_title">Bộ lọc thư rác</string>
|
||||
<string name="about_spam_filter_desc">Ẩn tin nhắn công khai từ những người gửi quá nhanh, lặp lại nội dung hoặc gửi quá nhiều liên kết.</string>
|
||||
</resources>
|
||||
|
||||
@ -388,4 +388,6 @@
|
||||
<string name="about_app_language">应用语言</string>
|
||||
<string name="about_system_default">跟随系统</string>
|
||||
<string name="about_select_language">选择语言</string>
|
||||
<string name="about_spam_filter_title">垃圾消息过滤器</string>
|
||||
<string name="about_spam_filter_desc">隐藏发送过快、重复发言或发送过多链接的用户的公开消息。</string>
|
||||
</resources>
|
||||
|
||||
@ -389,4 +389,6 @@
|
||||
<string name="about_app_language">應用程式語言</string>
|
||||
<string name="about_system_default">跟隨系統</string>
|
||||
<string name="about_select_language">選擇語言</string>
|
||||
<string name="about_spam_filter_title">垃圾訊息篩選器</string>
|
||||
<string name="about_spam_filter_desc">隱藏傳送過快、重複發言或傳送過多連結的使用者的公開訊息。</string>
|
||||
</resources>
|
||||
|
||||
@ -404,4 +404,6 @@
|
||||
<string name="about_app_language">应用语言</string>
|
||||
<string name="about_system_default">跟随系统</string>
|
||||
<string name="about_select_language">选择语言</string>
|
||||
<string name="about_spam_filter_title">垃圾消息过滤器</string>
|
||||
<string name="about_spam_filter_desc">隐藏发送过快、重复发言或发送过多链接的用户的公开消息。</string>
|
||||
</resources>
|
||||
|
||||
@ -209,6 +209,8 @@
|
||||
<string name="about_pow_off">PoW Off</string>
|
||||
<string name="about_pow_on">PoW On</string>
|
||||
<string name="about_pow_tip">Add proof of work to geohash messages for spam deterrence.</string>
|
||||
<string name="about_spam_filter_title">Spam Filter</string>
|
||||
<string name="about_spam_filter_desc">Hide public messages from people who post too fast, repeat themselves, or flood links.</string>
|
||||
<string name="about_pow_difficulty">Difficulty: %1$d bits (~%2$s)</string>
|
||||
<string name="about_pow_difficulty_attempts">Difficulty %1$d requires ~%2$s hash attempts</string>
|
||||
<string name="about_pow_desc_none">No proof of work required</string>
|
||||
|
||||
432
app/src/test/java/com/bitchat/android/spam/SpamFilterTest.kt
Normal file
432
app/src/test/java/com/bitchat/android/spam/SpamFilterTest.kt
Normal file
@ -0,0 +1,432 @@
|
||||
package com.bitchat.android.spam
|
||||
|
||||
import com.bitchat.android.model.BitchatMessage
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertFalse
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
import kotlin.random.Random
|
||||
|
||||
class SpamFilterTest {
|
||||
|
||||
private var now = 1_000_000_000L
|
||||
private var nextId = 0
|
||||
private val random = Random(7)
|
||||
private val filter = SpamFilter(clock = { now })
|
||||
|
||||
private fun send(
|
||||
content: String = prose(),
|
||||
sender: String = "alice",
|
||||
timestampMs: Long = now,
|
||||
id: String = "m${nextId++}"
|
||||
) = filter.check(sender, id, content, timestampMs)
|
||||
|
||||
/** Unrelated synthetic words, so only the rule under test can fire. */
|
||||
private fun prose(length: Int = 40): String = buildString {
|
||||
while (this.length < length) {
|
||||
if (isNotEmpty()) append(' ')
|
||||
repeat(random.nextInt(2, 8)) { append('a' + random.nextInt(26)) }
|
||||
}
|
||||
}.take(length)
|
||||
|
||||
private fun advanceSeconds(seconds: Int) {
|
||||
now += seconds * 1000L
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `sixth message in a burst is shown and seventh is hidden`() {
|
||||
repeat(6) { assertEquals(SpamVerdict.ALLOWED, send()) }
|
||||
assertEquals(SpamVerdict.MESSAGE_RATE, send())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `message budget refills one token every ten seconds`() {
|
||||
repeat(6) { send() }
|
||||
advanceSeconds(9)
|
||||
assertEquals(SpamVerdict.MESSAGE_RATE, send())
|
||||
advanceSeconds(1)
|
||||
assertEquals(SpamVerdict.ALLOWED, send())
|
||||
assertEquals(SpamVerdict.MESSAGE_RATE, send())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `hidden messages do not drain the budget of a flooding sender`() {
|
||||
var shown = 0
|
||||
repeat(120) {
|
||||
if (send() == SpamVerdict.ALLOWED) shown++
|
||||
advanceSeconds(1)
|
||||
}
|
||||
// A burst of 6, then one every 10 seconds for the remaining two minutes.
|
||||
assertEquals(6 + 11, shown)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `character budget allows twelve hundred characters per minute`() {
|
||||
assertEquals(SpamVerdict.ALLOWED, send(prose(600)))
|
||||
assertEquals(SpamVerdict.ALLOWED, send(prose(600)))
|
||||
assertEquals(SpamVerdict.CHARACTER_RATE, send(prose(20)))
|
||||
advanceSeconds(2)
|
||||
assertEquals(SpamVerdict.ALLOWED, send(prose(20)))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `message longer than the character budget is hidden`() {
|
||||
assertEquals(SpamVerdict.CHARACTER_RATE, send(prose(1201)))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `characters are counted as code points`() {
|
||||
val emoji = "😀"
|
||||
val sentence = (0 until 1200).joinToString("") { if (it % 3 == 0) emoji else "${'a' + random.nextInt(26)}" }
|
||||
assertEquals(SpamVerdict.ALLOWED, send(sentence))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `third link within a minute is hidden`() {
|
||||
assertEquals(SpamVerdict.ALLOWED, send("see https://example.org/a for details"))
|
||||
assertEquals(SpamVerdict.ALLOWED, send("mirror at www.example.net too"))
|
||||
assertEquals(SpamVerdict.LINK_RATE, send("and example.com/promo as well"))
|
||||
assertEquals(SpamVerdict.ALLOWED, send("no link in this one at all"))
|
||||
advanceSeconds(30)
|
||||
assertEquals(SpamVerdict.ALLOWED, send("ok one more https://example.org/b"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `message with three links is hidden even for a fresh sender`() {
|
||||
assertEquals(
|
||||
SpamVerdict.LINK_RATE,
|
||||
send("https://alpha.example/one and bravo.example plus www.charlie.example/three")
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `link counter ignores email addresses and abbreviations`() {
|
||||
assertEquals(0, LinkCounter.count("mail me at someone@example.com e.g. today"))
|
||||
assertEquals(0, LinkCounter.count("pi is 3.14, version v1.2.3"))
|
||||
assertEquals(3, LinkCounter.count("https://a.example/x, www.b.example and c.example."))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `third copy of a message within ten minutes is hidden`() {
|
||||
val text = "anyone near the north gate tonight?"
|
||||
assertEquals(SpamVerdict.ALLOWED, send(text))
|
||||
advanceSeconds(60)
|
||||
assertEquals(SpamVerdict.ALLOWED, send(text))
|
||||
advanceSeconds(60)
|
||||
assertEquals(SpamVerdict.REPEATED_MESSAGE, send(text))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `near copies with a changing suffix count as repeats`() {
|
||||
assertEquals(SpamVerdict.ALLOWED, send("FREE SATS at the link below 101"))
|
||||
advanceSeconds(20)
|
||||
assertEquals(SpamVerdict.ALLOWED, send("free sats at the link below 202"))
|
||||
advanceSeconds(20)
|
||||
assertEquals(SpamVerdict.REPEATED_MESSAGE, send("free sats at the link below 303"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `repeats spread beyond the history window are allowed`() {
|
||||
val text = "good morning everyone, coffee anyone?"
|
||||
send(text)
|
||||
advanceSeconds(11 * 60)
|
||||
send(text)
|
||||
advanceSeconds(11 * 60)
|
||||
assertEquals(SpamVerdict.ALLOWED, send(text))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `short replies may repeat freely`() {
|
||||
repeat(5) {
|
||||
assertEquals(SpamVerdict.ALLOWED, send("lol yes"))
|
||||
advanceSeconds(10)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `message repeating a character is hidden`() {
|
||||
assertEquals(SpamVerdict.REPEATED_CHARACTERS, send("A".repeat(40)))
|
||||
assertEquals(SpamVerdict.REPEATED_CHARACTERS, send("hey " + "!".repeat(60)))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `message repeating a phrase is hidden`() {
|
||||
assertEquals(SpamVerdict.REPEATED_CHARACTERS, send("buy now ".repeat(6)))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `ordinary long sentence is not mistaken for repetition`() {
|
||||
val text = "The market by the old bridge opens at nine, bring cash because the card reader is flaky."
|
||||
assertTrue(RepetitionDetector.selfSimilarity(RepetitionDetector.normalize(text, 1000)) < 0.3)
|
||||
assertEquals(SpamVerdict.ALLOWED, send(text))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `senders have independent budgets`() {
|
||||
repeat(6) { send(sender = "alice") }
|
||||
assertEquals(SpamVerdict.MESSAGE_RATE, send(sender = "alice"))
|
||||
assertEquals(SpamVerdict.ALLOWED, send(sender = "bob"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `same message ID arriving twice is judged once`() {
|
||||
repeat(5) { send() }
|
||||
assertEquals(SpamVerdict.ALLOWED, send(id = "dup"))
|
||||
assertEquals(SpamVerdict.ALLOWED, send(id = "dup"))
|
||||
assertEquals(SpamVerdict.MESSAGE_RATE, send())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `backfilled history delivered newest first is shown`() {
|
||||
// Ten messages, one per minute over the last ten minutes, arriving in one burst.
|
||||
val verdicts = (0 until 10).map { minutesAgo ->
|
||||
send(timestampMs = now - minutesAgo * 60_000L)
|
||||
}
|
||||
assertTrue(verdicts.all { it == SpamVerdict.ALLOWED })
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `backfilled burst is still rate limited on its own timestamps`() {
|
||||
val start = now - 5 * 60_000L
|
||||
val verdicts = (0 until 10).map { send(timestampMs = start + it * 1000L) }
|
||||
assertEquals(6, verdicts.count { it == SpamVerdict.ALLOWED })
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `backfill cannot be squeezed in between already shown messages`() {
|
||||
repeat(6) { send(timestampMs = now - 60_000L) }
|
||||
// A message claiming to be from just before the burst would overdraw the bucket.
|
||||
assertEquals(SpamVerdict.MESSAGE_RATE, send(timestampMs = now - 61_000L))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `timestamps older than the backdate window are clamped`() {
|
||||
// Spreading fake timestamps over days must not buy extra budget.
|
||||
val verdicts = (0 until 10).map { send(timestampMs = now - (10 - it) * 86_400_000L) }
|
||||
assertEquals(6, verdicts.count { it == SpamVerdict.ALLOWED })
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `future timestamps are clamped to now`() {
|
||||
val verdicts = (0 until 10).map { send(timestampMs = now + it * 60_000L) }
|
||||
assertEquals(6, verdicts.count { it == SpamVerdict.ALLOWED })
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `disabled filter allows everything`() {
|
||||
filter.enabled = false
|
||||
repeat(20) { assertEquals(SpamVerdict.ALLOWED, send("A".repeat(40))) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `limits are configurable`() {
|
||||
val strict = SpamFilter(
|
||||
config = SpamFilterConfig(messageBucket = TokenBucketConfig(capacity = 2.0, refillPerSecond = 1.0)),
|
||||
clock = { now }
|
||||
)
|
||||
assertEquals(SpamVerdict.ALLOWED, strict.check("a", "1", prose(), now))
|
||||
assertEquals(SpamVerdict.ALLOWED, strict.check("a", "2", prose(), now))
|
||||
assertEquals(SpamVerdict.MESSAGE_RATE, strict.check("a", "3", prose(), now))
|
||||
advanceSeconds(1)
|
||||
assertEquals(SpamVerdict.ALLOWED, strict.check("a", "4", prose(), now))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `debug bypass shows spam and records why it would be hidden`() {
|
||||
filter.debugBypass = true
|
||||
val messages = (1..7).map {
|
||||
BitchatMessage(id = "b$it", sender = "flooder", content = "hi $it", timestamp = java.util.Date(now))
|
||||
}
|
||||
|
||||
val hidden = messages.map { filter.shouldHide(it, "flooder") }
|
||||
|
||||
assertTrue(hidden.none { it })
|
||||
assertEquals(mapOf("b7" to SpamVerdict.MESSAGE_RATE), filter.flagged.value)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `spam is hidden without the debug bypass`() {
|
||||
val messages = (1..7).map {
|
||||
BitchatMessage(id = "h$it", sender = "flooder", content = "hi $it", timestamp = java.util.Date(now))
|
||||
}
|
||||
|
||||
assertEquals(listOf(false, false, false, false, false, false, true), messages.map { filter.shouldHide(it, "flooder") })
|
||||
assertTrue(filter.flagged.value.isEmpty())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `changing limits starts every sender with a full budget`() {
|
||||
repeat(6) { send() }
|
||||
assertEquals(SpamVerdict.MESSAGE_RATE, send())
|
||||
|
||||
filter.config = SpamTuning.DEFAULT.copy(messageBurst = 10).toConfig()
|
||||
|
||||
repeat(10) { assertEquals(SpamVerdict.ALLOWED, send()) }
|
||||
assertEquals(SpamVerdict.MESSAGE_RATE, send())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `default tuning matches the code defaults`() {
|
||||
assertEquals(SpamFilterConfig(), SpamTuning.DEFAULT.toConfig())
|
||||
assertFalse(SpamTuning.DEFAULT.copy(linksPerMinute = 5).toConfig() == SpamFilterConfig())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `same text from many throwaway identities is hidden after three copies`() {
|
||||
val text = "Free sats for everyone at freesats dot example"
|
||||
val verdicts = (1..6).map { send(text, sender = "npub$it") }
|
||||
|
||||
assertEquals(
|
||||
listOf(SpamVerdict.ALLOWED, SpamVerdict.ALLOWED, SpamVerdict.ALLOWED) +
|
||||
List(3) { SpamVerdict.REPEATED_ACROSS_USERS },
|
||||
verdicts
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `near copies with small changes from different identities are hidden`() {
|
||||
// Each variant is one small edit (a suffix, an insertion, a substitution) off the first.
|
||||
val variants = listOf(
|
||||
"Free sats for everyone, claim yours now",
|
||||
"free sats for everyone, claim yours now 1",
|
||||
"Free sats for everyone!! claim yours now",
|
||||
"free sats 4 everyone, claim yours now",
|
||||
"free sats for everyone, claim yours now 55"
|
||||
)
|
||||
val verdicts = variants.mapIndexed { i, text -> send(text, sender = "npub$i") }
|
||||
|
||||
assertEquals(List(4) { SpamVerdict.ALLOWED } + SpamVerdict.REPEATED_ACROSS_USERS, verdicts)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `short common replies from many users are not compared`() {
|
||||
repeat(8) { assertEquals(SpamVerdict.ALLOWED, send("good morning!", sender = "user$it")) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `different messages on the same topic from many users are allowed`() {
|
||||
val messages = listOf(
|
||||
"is the bakery on the corner still open?",
|
||||
"the bakery closes at eight I think",
|
||||
"bakery was still open when I walked by",
|
||||
"anyone know if the bakery has bread left?",
|
||||
"the corner bakery is open until eight"
|
||||
)
|
||||
messages.forEachIndexed { i, text -> assertEquals(SpamVerdict.ALLOWED, send(text, sender = "user$i")) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `copies from other users outside the window do not count`() {
|
||||
val text = "anyone up for chess at the tables later?"
|
||||
repeat(3) {
|
||||
send(text, sender = "user$it")
|
||||
advanceSeconds(4 * 60)
|
||||
}
|
||||
// Only the copy from 4 minutes ago and the one from 8 minutes ago are in the window.
|
||||
assertEquals(SpamVerdict.ALLOWED, send(text, sender = "late"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `a sender's own copies do not count toward the cross-user limit`() {
|
||||
val text = "anyone near the north gate tonight?"
|
||||
send(text, sender = "alice")
|
||||
advanceSeconds(60)
|
||||
send(text, sender = "alice")
|
||||
assertEquals(SpamVerdict.ALLOWED, send(text, sender = "bob"))
|
||||
assertEquals(SpamVerdict.REPEATED_ACROSS_USERS, send(text, sender = "carol"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `best alignment reports where the texts line up`() {
|
||||
val a = RepetitionDetector.normalize("xx hello world", 1000)
|
||||
val b = RepetitionDetector.normalize("hello world", 1000)
|
||||
val alignment = RepetitionDetector.align(a, b)
|
||||
assertEquals(3, alignment.peaks.first().shift)
|
||||
assertEquals(11, alignment.matches)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `an inserted character does not break the match`() {
|
||||
val a = RepetitionDetector.normalize("free sats for everyone, claim yours now", 1000)
|
||||
val b = RepetitionDetector.normalize("free sats for everyone!!, claim yours now", 1000)
|
||||
assertTrue(RepetitionDetector.align(a, b, maxPeaks = 1).score < 0.8)
|
||||
assertTrue(RepetitionDetector.align(a, b).score > 0.9)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `unrelated sentences stay far below the near-copy threshold`() {
|
||||
val a = RepetitionDetector.normalize("the bakery closes at eight I think", 1000)
|
||||
val b = RepetitionDetector.normalize("the corner bakery is open until eight", 1000)
|
||||
assertTrue(RepetitionDetector.align(a, b).score < 0.6)
|
||||
}
|
||||
|
||||
private val disguiseBase = "Free sats for everyone, claim yours now"
|
||||
|
||||
/** The same text a reader sees, written so that a plain comparison would miss it. */
|
||||
private val disguises = mapOf(
|
||||
"zero-width spaces" to disguiseBase.map { "$it" }.joinToString(""),
|
||||
"mixed invisible characters" to disguiseBase.mapIndexed { i, c ->
|
||||
"$c" + listOf("", "", "", "", "")[i % 5]
|
||||
}.joinToString(""),
|
||||
"an accent on every letter" to disguiseBase.map { if (it.isLetter()) "$it́" else "$it" }.joinToString(""),
|
||||
"math bold letters" to disguiseBase.map { c ->
|
||||
when (c) {
|
||||
in 'a'..'z' -> String(Character.toChars(0x1D41A + (c - 'a')))
|
||||
in 'A'..'Z' -> String(Character.toChars(0x1D400 + (c - 'A')))
|
||||
else -> "$c"
|
||||
}
|
||||
}.joinToString(""),
|
||||
"fullwidth letters" to disguiseBase.map { c -> if (c in '!'..'~') (c.code + 0xFEE0).toChar() else c }.joinToString(""),
|
||||
"non-breaking spaces" to disguiseBase.replace(' ', ' ')
|
||||
)
|
||||
|
||||
@Test
|
||||
fun `normalization folds disguises to the text a reader sees`() {
|
||||
val plain = RepetitionDetector.normalize(disguiseBase, 1000).toList()
|
||||
disguises.forEach { (label, text) ->
|
||||
assertEquals(label, plain, RepetitionDetector.normalize(text, 1000).toList())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `disguised copies from other users are still caught`() {
|
||||
disguises.forEach { (label, text) ->
|
||||
val fresh = SpamFilter(clock = { now })
|
||||
repeat(3) { fresh.check("npub$it", "$label-$it", disguiseBase, now) }
|
||||
assertEquals(label, SpamVerdict.REPEATED_ACROSS_USERS, fresh.check("npub9", "$label-x", text, now))
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `disguised copies from the same user are still caught`() {
|
||||
disguises.forEach { (label, text) ->
|
||||
val fresh = SpamFilter(clock = { now })
|
||||
fresh.check("alice", "$label-1", disguiseBase, now)
|
||||
fresh.check("alice", "$label-2", disguiseBase, now)
|
||||
assertEquals(label, SpamVerdict.REPEATED_MESSAGE, fresh.check("alice", "$label-3", text, now))
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `invisible characters cannot break up a repeated character run`() {
|
||||
assertEquals(SpamVerdict.REPEATED_CHARACTERS, send("A".repeat(40)))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `ordinary text with accents and other scripts is still allowed`() {
|
||||
listOf(
|
||||
"Tiếng Việt có dấu, chúng tôi đang ở quán cà phê gần chợ",
|
||||
"क्या कोई पास में चाय की दुकान जानता है? मैं स्टेशन के पास हूँ",
|
||||
"Café au lait, crème brûlée, s'il vous plaît",
|
||||
"家族と一緒に駅の近くのカフェにいます、誰か来ますか"
|
||||
).forEachIndexed { i, text -> assertEquals(text, SpamVerdict.ALLOWED, send(text, sender = "user$i")) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `different sentences in another script are not mistaken for copies`() {
|
||||
assertEquals(SpamVerdict.ALLOWED, send("क्या कोई पास में चाय की दुकान जानता है?", sender = "a"))
|
||||
assertEquals(SpamVerdict.ALLOWED, send("मैं स्टेशन के पास हूँ, बारिश हो रही है", sender = "b"))
|
||||
assertEquals(SpamVerdict.ALLOWED, send("आज बाज़ार कितने बजे बंद होगा?", sender = "c"))
|
||||
assertEquals(SpamVerdict.ALLOWED, send("क्या किसी के पास छाता है जो मैं ले सकूँ?", sender = "d"))
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,118 @@
|
||||
package com.bitchat.android.ui
|
||||
|
||||
import android.app.Application
|
||||
import android.content.ComponentName
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.test.assertIsDisplayed
|
||||
import androidx.compose.ui.test.hasScrollToIndexAction
|
||||
import androidx.compose.ui.test.junit4.createComposeRule
|
||||
import androidx.compose.ui.test.onNodeWithText
|
||||
import androidx.compose.ui.test.performScrollToIndex
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.bitchat.android.mesh.MeshService
|
||||
import com.bitchat.android.model.BitchatMessage
|
||||
import java.util.Date
|
||||
import org.junit.Assert.assertFalse
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.rules.ExternalResource
|
||||
import org.junit.rules.RuleChain
|
||||
import org.junit.runner.RunWith
|
||||
import org.mockito.kotlin.doReturn
|
||||
import org.mockito.kotlin.mock
|
||||
import org.robolectric.RobolectricTestRunner
|
||||
import org.robolectric.RuntimeEnvironment
|
||||
import org.robolectric.Shadows.shadowOf
|
||||
import org.robolectric.annotation.Config
|
||||
|
||||
/**
|
||||
* The message list follows new arrivals while the reader is at the latest message.
|
||||
*
|
||||
* Public, channel and geohash timelines are capped, so once one is full every arrival evicts the
|
||||
* oldest message and the list size stays constant. Following must not depend on the size changing.
|
||||
*/
|
||||
@RunWith(RobolectricTestRunner::class)
|
||||
@Config(sdk = [35], application = Application::class)
|
||||
class MessagesListFollowTest {
|
||||
|
||||
private val compose = createComposeRule()
|
||||
|
||||
// Unit tests run without merged Android resources, so Robolectric does not know the host
|
||||
// activity the compose rule launches. Register it before the rule starts.
|
||||
@get:Rule
|
||||
val rules: RuleChain = RuleChain
|
||||
.outerRule(object : ExternalResource() {
|
||||
override fun before() {
|
||||
val app = RuntimeEnvironment.getApplication()
|
||||
shadowOf(app.packageManager)
|
||||
.addActivityIfNotPresent(ComponentName(app, ComponentActivity::class.java))
|
||||
}
|
||||
})
|
||||
.around(compose)
|
||||
|
||||
private val meshService = mock<MeshService> { on { myPeerID } doReturn "0000000000000000" }
|
||||
private var messages by mutableStateOf(List(TimelineSize) { msg(it) })
|
||||
private var scrolledUp: Boolean? = null
|
||||
|
||||
private fun setContent() {
|
||||
compose.setContent {
|
||||
MessagesList(
|
||||
messages = messages,
|
||||
currentUserNickname = "me",
|
||||
meshService = meshService,
|
||||
modifier = Modifier.size(360.dp, 640.dp),
|
||||
conversationKey = "geo:test",
|
||||
onScrolledUpChanged = { scrolledUp = it }
|
||||
)
|
||||
}
|
||||
compose.waitForIdle()
|
||||
}
|
||||
|
||||
/** Mirrors the timeline retention cap: the oldest message makes room for the new one. */
|
||||
private fun arriveAtCapacity(count: Int) {
|
||||
repeat(count) {
|
||||
messages = messages.drop(1) + msg(TimelineSize + it)
|
||||
compose.waitForIdle()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `a full timeline keeps following arrivals that evict the oldest message`() {
|
||||
setContent()
|
||||
|
||||
arriveAtCapacity(10)
|
||||
|
||||
assertFalse("following stopped once the size stopped changing", scrolledUp ?: false)
|
||||
compose.onNodeWithText(body(TimelineSize + 9), substring = true).assertIsDisplayed()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `a full timeline does not pull a reader back down after they scroll away`() {
|
||||
setContent()
|
||||
compose.onNode(hasScrollToIndexAction()).performScrollToIndex(TimelineSize - 1)
|
||||
compose.waitForIdle()
|
||||
|
||||
arriveAtCapacity(3)
|
||||
|
||||
assertTrue("reader was yanked back to the latest message", scrolledUp ?: false)
|
||||
}
|
||||
|
||||
private fun msg(index: Int) = BitchatMessage(
|
||||
id = "m$index",
|
||||
sender = "alice",
|
||||
content = body(index),
|
||||
timestamp = Date(index * 1_000L),
|
||||
)
|
||||
|
||||
private fun body(index: Int) = "synthetic message $index"
|
||||
|
||||
private companion object {
|
||||
const val TimelineSize = 40
|
||||
}
|
||||
}
|
||||
@ -10,6 +10,10 @@ import com.bitchat.android.noise.NoiseSessionError
|
||||
import com.bitchat.android.protocol.BitchatPacket
|
||||
import com.bitchat.android.protocol.MessageType
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import java.util.Collections
|
||||
import java.util.concurrent.CountDownLatch
|
||||
import java.util.concurrent.Executors
|
||||
import java.util.concurrent.TimeUnit
|
||||
import org.junit.After
|
||||
import org.junit.Assert.assertFalse
|
||||
import org.junit.Assert.assertTrue
|
||||
@ -99,6 +103,75 @@ class SecurityManagerTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `cleanup serializes timestamp traversal with concurrent packet tracking`() {
|
||||
assertCleanupSerializesWrites("messageTimestamps")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `cleanup serializes timestamp traversal with concurrent handshake tracking`() {
|
||||
assertCleanupSerializesWrites("keyExchangeTimestamps")
|
||||
}
|
||||
|
||||
private fun assertCleanupSerializesWrites(fieldName: String) {
|
||||
val allowWrite = CountDownLatch(1)
|
||||
val writeFinished = CountDownLatch(1)
|
||||
lateinit var timestamps: MutableMap<String, Long>
|
||||
val backing = object : LinkedHashMap<String, Long>() {
|
||||
override val entries: MutableSet<MutableMap.MutableEntry<String, Long>>
|
||||
get() {
|
||||
val original = super.entries
|
||||
return object : AbstractMutableSet<MutableMap.MutableEntry<String, Long>>() {
|
||||
override val size get() = original.size
|
||||
override fun add(element: MutableMap.MutableEntry<String, Long>) =
|
||||
error("Not used")
|
||||
override fun iterator(): MutableIterator<MutableMap.MutableEntry<String, Long>> {
|
||||
val iterator = original.iterator()
|
||||
var first = true
|
||||
return object : MutableIterator<MutableMap.MutableEntry<String, Long>> {
|
||||
override fun hasNext() = iterator.hasNext()
|
||||
override fun remove() = iterator.remove()
|
||||
override fun next(): MutableMap.MutableEntry<String, Long> {
|
||||
if (first) {
|
||||
first = false
|
||||
allowWrite.countDown()
|
||||
// A correct synchronized traversal blocks the writer. An
|
||||
// unguarded traversal deterministically exposes the CME.
|
||||
if (!Thread.holdsLock(timestamps)) {
|
||||
assertTrue(writeFinished.await(5, TimeUnit.SECONDS))
|
||||
}
|
||||
}
|
||||
return iterator.next()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
timestamps = Collections.synchronizedMap(backing)
|
||||
timestamps["expired-a"] = 1L
|
||||
timestamps["expired-b"] = 2L
|
||||
SecurityManager::class.java.getDeclaredField(fieldName).apply {
|
||||
isAccessible = true
|
||||
set(securityManager, timestamps)
|
||||
}
|
||||
val now = 10_000_000L
|
||||
val executor = Executors.newSingleThreadExecutor()
|
||||
val writer = executor.submit {
|
||||
check(allowWrite.await(5, TimeUnit.SECONDS))
|
||||
timestamps["fresh"] = now
|
||||
writeFinished.countDown()
|
||||
}
|
||||
try {
|
||||
securityManager.cleanupOldData(now)
|
||||
writer.get(5, TimeUnit.SECONDS)
|
||||
assertTrue(timestamps.keys == setOf("fresh"))
|
||||
} finally {
|
||||
allowWrite.countDown()
|
||||
executor.shutdownNow()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `validatePacket - rejects packet with missing signature`() {
|
||||
val packet = BitchatPacket(
|
||||
|
||||
@ -0,0 +1,79 @@
|
||||
package com.bitchat.android.nostr
|
||||
|
||||
import android.app.Application
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import com.bitchat.android.spam.SpamFilter
|
||||
import com.bitchat.android.ui.ChatState
|
||||
import com.bitchat.android.ui.DataManager
|
||||
import com.bitchat.android.ui.MessageManager
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.test.TestScope
|
||||
import kotlinx.coroutines.test.UnconfinedTestDispatcher
|
||||
import org.junit.After
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.robolectric.RobolectricTestRunner
|
||||
|
||||
@RunWith(RobolectricTestRunner::class)
|
||||
class GeohashMessageHandlerSpamTest {
|
||||
|
||||
private val application: Application = ApplicationProvider.getApplicationContext()
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
private val testDispatcher = UnconfinedTestDispatcher()
|
||||
private val testScope = TestScope(testDispatcher)
|
||||
private val geohash = "zzzzzz"
|
||||
|
||||
private lateinit var chatState: ChatState
|
||||
private lateinit var handler: GeohashMessageHandler
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
com.bitchat.android.services.AppStateStore.clear()
|
||||
SpamFilter.shared.reset()
|
||||
chatState = ChatState(scope = testScope)
|
||||
val dataManager = DataManager(context = application)
|
||||
handler = GeohashMessageHandler(
|
||||
application = application,
|
||||
repo = GeohashRepository(application, chatState, dataManager, testScope),
|
||||
scope = testScope,
|
||||
dataManager = dataManager,
|
||||
addChannelMessage = MessageManager(state = chatState)::addChannelMessage,
|
||||
signatureVerificationDispatcher = testDispatcher,
|
||||
spamFilterDispatcher = testDispatcher
|
||||
)
|
||||
}
|
||||
|
||||
@After
|
||||
fun tearDown() {
|
||||
SpamFilter.shared.reset()
|
||||
com.bitchat.android.services.AppStateStore.clear()
|
||||
}
|
||||
|
||||
private fun event(identity: NostrIdentity, content: String) = NostrEvent(
|
||||
pubkey = identity.publicKeyHex,
|
||||
createdAt = (System.currentTimeMillis() / 1000).toInt(),
|
||||
kind = NostrKind.EPHEMERAL_EVENT,
|
||||
tags = listOf(listOf("g", geohash)),
|
||||
content = content
|
||||
).sign(identity.privateKeyHex)
|
||||
|
||||
@Test
|
||||
fun `flood from one pubkey is cut off after the burst`() {
|
||||
val flooder = NostrIdentity.generate()
|
||||
(1..8).forEach { handler.onEvent(event(flooder, "hi $it"), geohash) }
|
||||
|
||||
assertEquals(6, chatState.getChannelMessagesValue()["geo:$geohash"]?.size)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `same text from many pubkeys is cut off after three copies`() {
|
||||
repeat(5) {
|
||||
handler.onEvent(event(NostrIdentity.generate(), "Free sats for everyone, claim yours now"), geohash)
|
||||
}
|
||||
|
||||
assertEquals(3, chatState.getChannelMessagesValue()["geo:$geohash"]?.size)
|
||||
}
|
||||
}
|
||||
@ -3,6 +3,7 @@ package com.bitchat.android.nostr
|
||||
import android.app.Application
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import com.bitchat.android.services.AppStateStore
|
||||
import com.bitchat.android.spam.SpamFilter
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
@ -23,12 +24,17 @@ class NostrBackgroundEventProcessorTest {
|
||||
@Before
|
||||
fun setUp() {
|
||||
AppStateStore.clear()
|
||||
// This test floods one sender to measure throughput; that is exactly what the spam
|
||||
// filter hides, so it is covered by SpamFilterTest instead.
|
||||
SpamFilter.shared.enabled = false
|
||||
scope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
|
||||
}
|
||||
|
||||
@After
|
||||
fun tearDown() {
|
||||
scope.cancel()
|
||||
SpamFilter.shared.enabled = true
|
||||
SpamFilter.shared.reset()
|
||||
AppStateStore.clear()
|
||||
}
|
||||
|
||||
|
||||
@ -0,0 +1,55 @@
|
||||
package com.bitchat.android.nostr
|
||||
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import kotlinx.coroutines.CoroutineExceptionHandler
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.cancel
|
||||
import okhttp3.WebSocket
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.mockito.kotlin.mock
|
||||
import org.robolectric.RobolectricTestRunner
|
||||
|
||||
@RunWith(RobolectricTestRunner::class)
|
||||
class NostrRelaySnapshotTest {
|
||||
@Test
|
||||
fun `subscription tolerates last relay disconnecting between size and iteration`() {
|
||||
val manager = NostrRelayManager::class.java.getDeclaredConstructor().apply {
|
||||
isAccessible = true
|
||||
}.newInstance()
|
||||
val errors = mutableListOf<Throwable>()
|
||||
val scope = CoroutineScope(Dispatchers.Unconfined + SupervisorJob() +
|
||||
CoroutineExceptionHandler { _, error -> errors.add(error) })
|
||||
val connections = object : ConcurrentHashMap<String, WebSocket>() {
|
||||
override val size: Int
|
||||
get() {
|
||||
val previous = super.size
|
||||
// The real concurrent key view delegates size() to its backing map.
|
||||
// Disconnect exactly after observing the last connection.
|
||||
clear()
|
||||
return previous
|
||||
}
|
||||
}
|
||||
NostrRelayManager::class.java.getDeclaredField("scope").apply {
|
||||
isAccessible = true
|
||||
(get(manager) as CoroutineScope).cancel()
|
||||
set(manager, scope)
|
||||
}
|
||||
NostrRelayManager::class.java.getDeclaredField("connections").apply {
|
||||
isAccessible = true
|
||||
set(manager, connections)
|
||||
}
|
||||
connections["wss://relay.example"] = mock()
|
||||
try {
|
||||
manager.subscribe(NostrFilter(kinds = listOf(NostrKind.TEXT_NOTE)),
|
||||
id = "synthetic-subscription", handler = {})
|
||||
assertTrue("Subscription coroutine failed: $errors", errors.isEmpty())
|
||||
} finally {
|
||||
scope.cancel()
|
||||
connections.clear()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -276,6 +276,65 @@ class IncomingMessageAdmissionTest {
|
||||
assertTrue(state.getPrivateChatsValue().isEmpty())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `public flood from one peer is hidden while private messages still arrive`() {
|
||||
val peer = "flood-${UUID.randomUUID()}"
|
||||
val admitted = (1..7).map { index ->
|
||||
IncomingMessageAdmission.admitToAppState(
|
||||
BitchatMessage(
|
||||
id = "$peer-$index",
|
||||
sender = "flooder",
|
||||
content = "hi $index",
|
||||
timestamp = Date(),
|
||||
senderPeerID = peer
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
assertEquals(listOf(true, true, true, true, true, true, false), admitted)
|
||||
assertFalse(AppStateStore.publicMessages.value.any { it.id == "$peer-7" })
|
||||
assertTrue(
|
||||
IncomingMessageAdmission.admitToAppState(
|
||||
privateMessage(id = "$peer-dm").copy(senderPeerID = peer)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `peer calling itself system is still spam filtered`() {
|
||||
val peer = "system-nick-${UUID.randomUUID()}"
|
||||
val admitted = (1..7).map { index ->
|
||||
IncomingMessageAdmission.admitToAppState(
|
||||
BitchatMessage(
|
||||
id = "$peer-$index",
|
||||
sender = "system",
|
||||
content = "hi $index",
|
||||
timestamp = Date(),
|
||||
senderPeerID = peer
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
assertFalse(admitted.last())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `local system notices are never spam filtered`() {
|
||||
val run = UUID.randomUUID().toString()
|
||||
val admitted = (1..10).map { index ->
|
||||
IncomingMessageAdmission.admitToAppState(
|
||||
BitchatMessage(
|
||||
id = "notice-$run-$index",
|
||||
sender = "system",
|
||||
content = "peer$index favorited you — favorite back to continue DMs later.",
|
||||
timestamp = Date()
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
assertTrue(admitted.all { it })
|
||||
}
|
||||
|
||||
private fun privateMessage(id: String) = BitchatMessage(
|
||||
id = id,
|
||||
sender = "alice",
|
||||
|
||||
@ -298,6 +298,7 @@ python3 tools/release_gate/mesh_lab.py scenario all \
|
||||
| `dm` | Noise handshake both ways, encrypted DM round trips with content match |
|
||||
| `favorite_verification` | favorite signal, orange-outline/filled mutual state, and peer fingerprint verification |
|
||||
| `broadcast` | public mesh message A→B |
|
||||
| `broadcast_spam` | spam filter on B: two near-copies from A shown, the third hidden, A's next ordinary message shown (B needs the filter on, default limits, debug bypass off) |
|
||||
| `ptt_dm` | Noise-encrypted 440 Hz PTT in both directions; asserts real-time capture, zero sequence gaps, decoded PCM duration/energy/continuity, and finalized-note absorption |
|
||||
| `ptt_broadcast` | signed public 440 Hz PTT with the same bidirectional packet and decoded-audio quality assertions |
|
||||
| `file` | 1 KB broadcast file, receiver SHA-256 matches fixture |
|
||||
|
||||
@ -39,6 +39,11 @@
|
||||
<sha256 value="5b2d4387f384e5913a4aec979a20ece13bb5ce992bc90a962dea1feea7fa89e9" origin="Generated by Gradle"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="androidx.annotation" name="annotation" version="1.2.0">
|
||||
<artifact name="annotation-1.2.0.module">
|
||||
<sha256 value="2efcab81ef91b211bacd206eaacd995a51f633a2e96b57a8fc00144c5f9c56b3" origin="Generated by Gradle"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="androidx.annotation" name="annotation" version="1.3.0">
|
||||
<artifact name="annotation-1.3.0.module">
|
||||
<sha256 value="9516c2ae44284ea0bd3d0eade0ee638879b708cbe31e3af92ba96c300604ebc3" origin="Generated by Gradle"/>
|
||||
@ -64,6 +69,11 @@
|
||||
<sha256 value="f204b05b728a97561718bc716242e47c629c0085a80ee74fca53d4d638bcbe3f" origin="Generated by Gradle"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="androidx.annotation" name="annotation-experimental" version="1.1.0">
|
||||
<artifact name="annotation-experimental-1.1.0.module">
|
||||
<sha256 value="0361d1526a4d7501255e19779e09e93cdbd07fee0e2f5c50b7a137432d510119" origin="Generated by Gradle"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="androidx.annotation" name="annotation-experimental" version="1.4.1">
|
||||
<artifact name="annotation-experimental-1.4.1.aar">
|
||||
<sha256 value="6bd4c7c7476f8260cd3bdbb81183583e93fc9f790c27dea7dc314181cbf87aa0" origin="Generated by Gradle"/>
|
||||
@ -1760,6 +1770,11 @@
|
||||
<sha256 value="b97dd112cd684ad3686f5ccf581b92b106ca76565cf02ccfece060af3bc50743" origin="Generated by Gradle"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="androidx.test" name="monitor" version="1.6.0">
|
||||
<artifact name="monitor-1.6.0.pom">
|
||||
<sha256 value="e6e32c3c34a33e81a64fa79ddd06cd47484594e40aea400bbb370ba102bea238" origin="Generated by Gradle"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="androidx.test" name="monitor" version="1.6.1">
|
||||
<artifact name="monitor-1.6.1.aar">
|
||||
<sha256 value="2985ce8556989baf7c84342e7f687713c037a39a922e614d1a3ddf1ca3777079" origin="Generated by Gradle"/>
|
||||
@ -4784,6 +4799,11 @@
|
||||
<sha256 value="f65860bce58a2bfadf05fdc516d38333421f0e387fef87e24449c0394c80d254" origin="Generated by Gradle"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="org.jetbrains.kotlinx" name="kotlinx-coroutines-bom" version="1.6.4">
|
||||
<artifact name="kotlinx-coroutines-bom-1.6.4.pom">
|
||||
<sha256 value="ab2614855fba66aa8a42514dbe3d5a884315ffe1ed63f5932e710a8006245ce1" origin="Generated by Gradle"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="org.jetbrains.kotlinx" name="kotlinx-coroutines-bom" version="1.7.3">
|
||||
<artifact name="kotlinx-coroutines-bom-1.7.3.pom">
|
||||
<sha256 value="4e5d1900e6379ef3f5970d04a8f30529adc82f859e8cc107c21ce8149ef666c4" origin="Generated by Gradle"/>
|
||||
@ -4794,6 +4814,11 @@
|
||||
<sha256 value="1239e9dbe1397cd5971342956b2511bc3ace7b641842e4372a088dcfa8b9ad55" origin="Generated by Gradle"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="org.jetbrains.kotlinx" name="kotlinx-coroutines-bom" version="1.8.1">
|
||||
<artifact name="kotlinx-coroutines-bom-1.8.1.pom">
|
||||
<sha256 value="563e4aa29fa8fe09a6e1746d0a5b51308f7c7dedc468dc5a0ca810a485877030" origin="Generated by Gradle"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="org.jetbrains.kotlinx" name="kotlinx-coroutines-bom" version="1.9.0">
|
||||
<artifact name="kotlinx-coroutines-bom-1.9.0.pom">
|
||||
<sha256 value="bea5511e9001f2c593ab5080df131b219ab5e2085cc5979ce583eacd4946fd78" origin="Generated by Gradle"/>
|
||||
|
||||
@ -532,6 +532,30 @@ def scenario_broadcast(a: Device, b: Device) -> dict:
|
||||
return {"send": send_result, "recv": recv_result}
|
||||
|
||||
|
||||
def scenario_broadcast_spam(a: Device, b: Device) -> dict:
|
||||
"""Spam filter on B: two near-copies from A are shown, the third is hidden, and A's next
|
||||
ordinary message is still shown. Needs B's spam filter on, default limits, bypass off."""
|
||||
id_a = whoami(a)["peer_id"]
|
||||
# A fresh 32-character body per run keeps earlier runs out of B's ten-minute repeat window.
|
||||
token = uuid.uuid4().hex[:16]
|
||||
copies = [f"{token}{token} copy {mark}" for mark in "ABC"]
|
||||
control = f"control {token} with different words, still delivered"
|
||||
expected = [True, True, False, True]
|
||||
with concurrent.futures.ThreadPoolExecutor(max_workers=4) as pool:
|
||||
recvs = [pool.submit(b.cmd, "msg_recv", 45_000, contains=text) for text in [*copies, control]]
|
||||
time.sleep(2)
|
||||
for text in [*copies, control]:
|
||||
a.cmd_ok("broadcast_msg", 30_000, content=text)
|
||||
results = [recv.result() for recv in recvs]
|
||||
delivered = [result.get("status") == "ok" for result in results]
|
||||
if delivered != expected:
|
||||
raise MeshLabError(f"expected delivery {expected} for copies A-C and control, got {delivered}")
|
||||
for result in results:
|
||||
if result.get("status") == "ok":
|
||||
assert result["from"] == id_a, result
|
||||
return {"delivered": delivered}
|
||||
|
||||
|
||||
def _ptt_one_way(
|
||||
sender: Device,
|
||||
receiver: Device,
|
||||
@ -869,6 +893,7 @@ SCENARIOS = {
|
||||
"dm": scenario_dm,
|
||||
"favorite_verification": scenario_favorite_verification,
|
||||
"broadcast": scenario_broadcast,
|
||||
"broadcast_spam": scenario_broadcast_spam,
|
||||
"ptt_dm": scenario_ptt_dm,
|
||||
"ptt_broadcast": scenario_ptt_broadcast,
|
||||
# Broadcast transfers are receiver-capped at 256 fragments (~120 KB); only
|
||||
@ -899,6 +924,7 @@ WATCH_SCENARIOS = [
|
||||
"dm",
|
||||
"favorite_verification",
|
||||
"broadcast",
|
||||
"broadcast_spam",
|
||||
"ptt_dm",
|
||||
"ptt_broadcast",
|
||||
"raw",
|
||||
|
||||
42
tools/release_gate/scanner_release_regression.sh
Normal file
42
tools/release_gate/scanner_release_regression.sh
Normal file
@ -0,0 +1,42 @@
|
||||
#!/usr/bin/env bash
|
||||
# Reproduce/verify the real scanner entry point after production R8 shrinking.
|
||||
# Usage: bash tools/release_gate/scanner_release_regression.sh <disposable-emulator>
|
||||
# Requires JDK 21, the pinned Android SDK, and adb on PATH. Never use publishable
|
||||
# signing keys here: -PreleaseUiTests=true uses the debug key for test APKs only.
|
||||
set -euo pipefail
|
||||
|
||||
if [[ $# != 1 || "$1" != emulator-* ]]; then
|
||||
echo "Usage: $0 <disposable-emulator>" >&2
|
||||
exit 2
|
||||
fi
|
||||
scanner_emulator="$1"
|
||||
if [[ "$(adb -s "$scanner_emulator" shell getprop ro.kernel.qemu | tr -d '\r')" != 1 ]]; then
|
||||
echo "Refusing to install: target is not an emulator." >&2
|
||||
exit 2
|
||||
fi
|
||||
scanner_abi="$(adb -s "$scanner_emulator" shell getprop ro.product.cpu.abi | tr -d '\r')"
|
||||
case "$scanner_abi" in
|
||||
arm64-v8a|x86_64|armeabi-v7a|x86) ;;
|
||||
*) echo "Unsupported emulator ABI." >&2; exit 2 ;;
|
||||
esac
|
||||
|
||||
cd "$(git rev-parse --show-toplevel)"
|
||||
# This must be a disposable emulator. Keep the fixture offline and location off;
|
||||
# no real camera, identities, messages, or transport startup are involved.
|
||||
adb -s "$scanner_emulator" shell svc wifi disable
|
||||
adb -s "$scanner_emulator" shell svc data disable
|
||||
adb -s "$scanner_emulator" shell settings put secure location_mode 0
|
||||
./gradlew -PreleaseUiTests=true :app:assembleRelease :app:assembleReleaseAndroidTest
|
||||
adb -s "$scanner_emulator" install -r "app/build/outputs/apk/release/app-${scanner_abi}-release.apk"
|
||||
adb -s "$scanner_emulator" install -r app/build/outputs/apk/androidTest/release/app-release-androidTest.apk
|
||||
|
||||
scanner_evidence="$(mktemp -d "${TMPDIR:-/tmp}/scanner-release.XXXXXX")"
|
||||
chmod 700 "$scanner_evidence"
|
||||
adb -s "$scanner_emulator" shell am instrument -w -r \
|
||||
com.bitchat.droid.test/com.bitchat.android.ui.ReleaseScannerTestRunner \
|
||||
> "$scanner_evidence/result.txt"
|
||||
cat "$scanner_evidence/result.txt"
|
||||
# adb itself exits successfully even when instrumentation tests fail.
|
||||
grep -q '^INSTRUMENTATION_RESULT: stream=Tests run: 2, failures: 0' "$scanner_evidence/result.txt"
|
||||
grep -q '^INSTRUMENTATION_CODE: -1' "$scanner_evidence/result.txt"
|
||||
echo "Release scanner creation, synthetic QR decoding, and frame cleanup passed."
|
||||
@ -105,6 +105,7 @@ val sharedSourceIncludes = listOf(
|
||||
"com/bitchat/android/services/VerificationService.kt",
|
||||
"com/bitchat/android/services/meshgraph/**",
|
||||
"com/bitchat/android/service/TransportBridgeService.kt",
|
||||
"com/bitchat/android/spam/**",
|
||||
"com/bitchat/android/nostr/Bech32.kt",
|
||||
"com/bitchat/android/nostr/GeohashAliasRegistry.kt",
|
||||
"com/bitchat/android/features/file/FileUtils.kt",
|
||||
@ -119,6 +120,8 @@ val sharedSourceIncludes = listOf(
|
||||
)
|
||||
val sharedSourceExcludes = listOf(
|
||||
"com/bitchat/android/model/FileSharingManager.kt",
|
||||
// The watch has no spam filter toggle, so it keeps the filter's default (on).
|
||||
"com/bitchat/android/spam/SpamFilterPreferences.kt",
|
||||
// Legacy phone monolith and Wi-Fi Aware multiplexer; the watch composes its own service
|
||||
// (MeshCore-style) in M2 instead of reusing these.
|
||||
"com/bitchat/android/mesh/BluetoothMeshService.kt",
|
||||
|
||||
@ -24,6 +24,7 @@ import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateListOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.runtime.saveable.listSaver
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
@ -39,6 +40,8 @@ import androidx.wear.compose.material3.OutlinedButton
|
||||
import com.bitchat.watch.mesh.WearMeshService
|
||||
import com.bitchat.watch.notification.WearNotificationCoordinator
|
||||
import com.bitchat.watch.service.WearMeshForegroundService
|
||||
import com.bitchat.watch.ui.ChatHomePager
|
||||
import com.bitchat.watch.ui.ChatMenuScreen
|
||||
import com.bitchat.watch.ui.ChatScreen
|
||||
import com.bitchat.watch.ui.DmScreen
|
||||
import com.bitchat.watch.ui.NicknameSetupScreen
|
||||
@ -110,6 +113,7 @@ class MainActivity : ComponentActivity() {
|
||||
)
|
||||
else -> WearNavHost(
|
||||
launchRequest = pendingLaunchRequest,
|
||||
onQuit = ::quit,
|
||||
onLaunchRequestHandled = { requestID ->
|
||||
if (pendingLaunchRequest?.id == requestID) {
|
||||
pendingLaunchRequest = null
|
||||
@ -177,6 +181,15 @@ class MainActivity : ComponentActivity() {
|
||||
startForegroundService(Intent(this, WearMeshForegroundService::class.java))
|
||||
}
|
||||
|
||||
/**
|
||||
* Quit from the in-app menu: stopping the foreground service tears the mesh down (see
|
||||
* [WearMeshForegroundService.onDestroy]); the next launch starts it again.
|
||||
*/
|
||||
private fun quit() {
|
||||
stopService(Intent(this, WearMeshForegroundService::class.java))
|
||||
finishAndRemoveTask()
|
||||
}
|
||||
|
||||
private fun consumePrivateMessagePeer(intent: Intent?): String? {
|
||||
val peerID = privateMessagePeerFromIntent(intent)
|
||||
intent?.removeExtra(WearNotificationCoordinator.EXTRA_OPEN_DM)
|
||||
@ -370,6 +383,7 @@ internal class WearNavigationState(
|
||||
@Composable
|
||||
internal fun WearNavHost(
|
||||
launchRequest: WearLaunchRequest?,
|
||||
onQuit: () -> Unit,
|
||||
onLaunchRequestHandled: (Long) -> Unit
|
||||
) {
|
||||
val navigation = rememberSaveable(saver = WearNavigationState.Saver) {
|
||||
@ -397,10 +411,20 @@ internal fun WearNavHost(
|
||||
label = "screenTransition"
|
||||
) { current ->
|
||||
when (current) {
|
||||
is WearScreen.Chat -> ChatScreen(
|
||||
onOpenPeople = { navigation.navigate(WearScreen.People) },
|
||||
onOpenTextInput = { navigation.navigate(WearScreen.TextInput(null)) }
|
||||
)
|
||||
is WearScreen.Chat -> {
|
||||
var chatOwnsGesture by remember { mutableStateOf(false) }
|
||||
ChatHomePager(
|
||||
swipeEnabled = !chatOwnsGesture,
|
||||
menu = { ChatMenuScreen(onQuit = onQuit) },
|
||||
chat = {
|
||||
ChatScreen(
|
||||
onOpenPeople = { navigation.navigate(WearScreen.People) },
|
||||
onOpenTextInput = { navigation.navigate(WearScreen.TextInput(null)) },
|
||||
onHorizontalGestureActive = { chatOwnsGesture = it }
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
is WearScreen.People -> PeopleScreen(
|
||||
onOpenDm = { navigation.navigate(WearScreen.Dm(it)) },
|
||||
onEditNickname = { navigation.navigate(WearScreen.Nickname) }
|
||||
|
||||
@ -13,6 +13,7 @@ import com.bitchat.android.mesh.MeshTransport
|
||||
import com.bitchat.android.model.RoutedPacket
|
||||
import com.bitchat.android.protocol.BitchatPacket
|
||||
import com.bitchat.android.services.AppStateStore
|
||||
import com.bitchat.android.spam.SpamFilter
|
||||
import com.bitchat.android.sync.GossipSyncManager
|
||||
import com.bitchat.android.util.AppConstants
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
@ -238,6 +239,8 @@ class WearMeshService private constructor(private val context: Context) {
|
||||
try { onPrivateMessage?.invoke(message) } catch (_: Exception) { }
|
||||
true
|
||||
}
|
||||
!SpamFilter.isLocalNotice(message) &&
|
||||
SpamFilter.shared.shouldHide(message, SpamFilter.meshSenderKey(message)) -> false
|
||||
message.channel != null -> {
|
||||
AppStateStore.addChannelMessage(message.channel, message)
|
||||
true
|
||||
|
||||
153
wear/src/main/java/com/bitchat/watch/ui/ChatMenuScreen.kt
Normal file
153
wear/src/main/java/com/bitchat/watch/ui/ChatMenuScreen.kt
Normal file
@ -0,0 +1,153 @@
|
||||
package com.bitchat.watch.ui
|
||||
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.PowerSettingsNew
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.wear.compose.foundation.lazy.ScalingLazyColumn
|
||||
import androidx.wear.compose.foundation.lazy.rememberScalingLazyListState
|
||||
import androidx.wear.compose.foundation.pager.HorizontalPager
|
||||
import androidx.wear.compose.foundation.pager.rememberPagerState
|
||||
import androidx.wear.compose.material3.AlertDialog
|
||||
import androidx.wear.compose.material3.AlertDialogDefaults
|
||||
import androidx.wear.compose.material3.FilledTonalButton
|
||||
import androidx.wear.compose.material3.HorizontalPagerScaffold
|
||||
import androidx.wear.compose.material3.Icon
|
||||
import androidx.wear.compose.material3.ListHeader
|
||||
import androidx.wear.compose.material3.MaterialTheme
|
||||
import androidx.wear.compose.material3.ScreenScaffold
|
||||
import androidx.wear.compose.material3.Text
|
||||
import com.bitchat.android.services.AppStateStore
|
||||
import com.bitchat.watch.ui.theme.ChatVisualTokens
|
||||
import com.bitchat.watch.ui.theme.LocalBitchatPalette
|
||||
|
||||
private const val MENU_PAGE = 0
|
||||
private const val CHAT_PAGE = 1
|
||||
|
||||
/**
|
||||
* Home of the app: the public mesh chat, with an app menu one page to its left. Swiping the
|
||||
* chat to the right reveals the menu (the Wear pattern used by Fitbit, Health and similar
|
||||
* apps); swiping right again from the menu's left edge falls through to the system
|
||||
* swipe-to-dismiss, so leaving the app still works as everywhere else on the watch.
|
||||
*
|
||||
* [swipeEnabled] is false while the chat owns a horizontal gesture (push-to-talk drag,
|
||||
* full-screen image), so the pager never steals the pointer mid-gesture.
|
||||
*/
|
||||
@Composable
|
||||
fun ChatHomePager(
|
||||
swipeEnabled: Boolean,
|
||||
menu: @Composable () -> Unit,
|
||||
chat: @Composable () -> Unit,
|
||||
) {
|
||||
val pagerState = rememberPagerState(initialPage = CHAT_PAGE) { 2 }
|
||||
// No page indicator: the menu is a peek-behind page, not a peer of the chat, and the
|
||||
// dots would crowd the chat's edge action bar.
|
||||
HorizontalPagerScaffold(pagerState = pagerState, pageIndicator = null) {
|
||||
HorizontalPager(
|
||||
state = pagerState,
|
||||
userScrollEnabled = swipeEnabled,
|
||||
// The crown scrolls chat history, never pages.
|
||||
rotaryScrollableBehavior = null,
|
||||
) { page ->
|
||||
when (page) {
|
||||
MENU_PAGE -> menu()
|
||||
CHAT_PAGE -> chat()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ChatMenuScreen(onQuit: () -> Unit) {
|
||||
val palette = LocalBitchatPalette.current
|
||||
val peers by AppStateStore.peers.collectAsState()
|
||||
val listState = rememberScalingLazyListState()
|
||||
var confirmQuit by rememberSaveable { mutableStateOf(false) }
|
||||
|
||||
ScreenScaffold(scrollState = listState) { scaffoldPadding ->
|
||||
ScalingLazyColumn(
|
||||
state = listState,
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
contentPadding = scaffoldPadding,
|
||||
) {
|
||||
item {
|
||||
ListHeader {
|
||||
Text(
|
||||
text = "bitchat",
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
fontWeight = FontWeight.Bold,
|
||||
)
|
||||
}
|
||||
}
|
||||
item {
|
||||
Text(
|
||||
text = if (peers.isEmpty()) "Mesh running" else meshPeersText(peers.size),
|
||||
style = ChatVisualTokens.SystemActionStyle,
|
||||
color = palette.textTertiary,
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = Modifier.fillMaxWidth().padding(bottom = 6.dp),
|
||||
)
|
||||
}
|
||||
item {
|
||||
FilledTonalButton(
|
||||
onClick = { confirmQuit = true },
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
icon = {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.PowerSettingsNew,
|
||||
contentDescription = null,
|
||||
tint = MaterialTheme.colorScheme.error,
|
||||
)
|
||||
},
|
||||
label = { Text("Quit bitchat") },
|
||||
secondaryLabel = {
|
||||
Text(
|
||||
text = "Stop the mesh",
|
||||
color = palette.textTertiary,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AlertDialog(
|
||||
visible = confirmQuit,
|
||||
onDismissRequest = { confirmQuit = false },
|
||||
icon = {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.PowerSettingsNew,
|
||||
contentDescription = null,
|
||||
tint = MaterialTheme.colorScheme.error,
|
||||
modifier = Modifier.size(32.dp),
|
||||
)
|
||||
},
|
||||
title = { Text("Quit bitchat?") },
|
||||
confirmButton = {
|
||||
AlertDialogDefaults.ConfirmButton(
|
||||
onClick = {
|
||||
confirmQuit = false
|
||||
onQuit()
|
||||
},
|
||||
)
|
||||
},
|
||||
dismissButton = {
|
||||
AlertDialogDefaults.DismissButton(onClick = { confirmQuit = false })
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
private fun meshPeersText(count: Int): String =
|
||||
if (count == 1) "Mesh running · 1 peer" else "Mesh running · $count peers"
|
||||
@ -57,7 +57,11 @@ import java.util.Date
|
||||
import java.util.Locale
|
||||
|
||||
@Composable
|
||||
fun ChatScreen(onOpenPeople: () -> Unit, onOpenTextInput: () -> Unit) {
|
||||
fun ChatScreen(
|
||||
onOpenPeople: () -> Unit,
|
||||
onOpenTextInput: () -> Unit,
|
||||
onHorizontalGestureActive: (Boolean) -> Unit = {},
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val messages by AppStateStore.publicMessages.collectAsState()
|
||||
val peers by AppStateStore.peers.collectAsState()
|
||||
@ -82,6 +86,11 @@ fun ChatScreen(onOpenPeople: () -> Unit, onOpenTextInput: () -> Unit) {
|
||||
}
|
||||
) { path -> mesh?.let { sendVoiceNote(it, null, path) } }
|
||||
|
||||
// Push-to-talk drags and the image viewer own the whole screen; the home pager must not
|
||||
// turn them into a page swipe.
|
||||
val horizontalGestureActive = voice.recording || viewerPath != null
|
||||
LaunchedEffect(horizontalGestureActive) { onHorizontalGestureActive(horizontalGestureActive) }
|
||||
|
||||
androidx.compose.runtime.DisposableEffect(liveVoiceManager) {
|
||||
liveVoiceManager.showPublicMesh()
|
||||
onDispose { liveVoiceManager.clearVisibleConversation() }
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user