From b7d6cc8dd00efa38363861f9dc131da9794823b1 Mon Sep 17 00:00:00 2001 From: AntsyLich <59261191+AntsyLich@users.noreply.github.com> Date: Tue, 10 Mar 2026 23:53:11 +0600 Subject: [PATCH] Add installation id for feature flags (#3052) # Conflicts: # app/build.gradle.kts # app/src/main/java/eu/kanade/tachiyomi/util/CrashLogUtil.kt # app/src/main/java/mihon/core/migration/migrations/Migrations.kt --- app/build.gradle.kts | 2 +- .../eu/kanade/domain/base/BasePreferences.kt | 2 + .../presentation/more/settings/Preference.kt | 1 + .../more/settings/PreferenceItem.kt | 1 + .../settings/screen/debug/DebugInfoScreen.kt | 42 +++++++++++++++++++ .../eu/kanade/tachiyomi/util/CrashLogUtil.kt | 3 ++ .../migrations/InstallationIdMigration.kt | 18 ++++++++ .../core/migration/migrations/Migrations.kt | 1 + .../kotlin/mihon/core/common/FeatureFlags.kt | 12 ++++++ 9 files changed, 81 insertions(+), 1 deletion(-) create mode 100644 app/src/main/java/mihon/core/migration/migrations/InstallationIdMigration.kt create mode 100644 core/common/src/main/kotlin/mihon/core/common/FeatureFlags.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 8added9bc..5767d05ae 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -31,7 +31,7 @@ android { defaultConfig { applicationId = "eu.kanade.tachiyomi.sy" - versionCode = 76 + versionCode = 77 versionName = "1.12.0" buildConfigField("String", "COMMIT_COUNT", "\"${getCommitCount()}\"") diff --git a/app/src/main/java/eu/kanade/domain/base/BasePreferences.kt b/app/src/main/java/eu/kanade/domain/base/BasePreferences.kt index 416ff4204..0caee6f88 100644 --- a/app/src/main/java/eu/kanade/domain/base/BasePreferences.kt +++ b/app/src/main/java/eu/kanade/domain/base/BasePreferences.kt @@ -35,4 +35,6 @@ class BasePreferences( fun hardwareBitmapThreshold() = preferenceStore.getInt("pref_hardware_bitmap_threshold", GLUtil.SAFE_TEXTURE_LIMIT) fun alwaysDecodeLongStripWithSSIV() = preferenceStore.getBoolean("pref_always_decode_long_strip_with_ssiv", false) + + fun installationId() = preferenceStore.getString(Preference.appStateKey("installation_id"), "") } diff --git a/app/src/main/java/eu/kanade/presentation/more/settings/Preference.kt b/app/src/main/java/eu/kanade/presentation/more/settings/Preference.kt index 356a7271a..22cb5af34 100644 --- a/app/src/main/java/eu/kanade/presentation/more/settings/Preference.kt +++ b/app/src/main/java/eu/kanade/presentation/more/settings/Preference.kt @@ -30,6 +30,7 @@ sealed class Preference { override val title: String, override val subtitle: CharSequence? = null, override val enabled: Boolean = true, + val widget: @Composable (() -> Unit)? = null, val onClick: (() -> Unit)? = null, ) : PreferenceItem() { override val icon: ImageVector? = null diff --git a/app/src/main/java/eu/kanade/presentation/more/settings/PreferenceItem.kt b/app/src/main/java/eu/kanade/presentation/more/settings/PreferenceItem.kt index cc4d81d9f..9e9ffb342 100644 --- a/app/src/main/java/eu/kanade/presentation/more/settings/PreferenceItem.kt +++ b/app/src/main/java/eu/kanade/presentation/more/settings/PreferenceItem.kt @@ -147,6 +147,7 @@ internal fun PreferenceItem( title = item.title, subtitle = item.subtitle, icon = item.icon, + widget = item.widget, onPreferenceClick = item.onClick, ) } diff --git a/app/src/main/java/eu/kanade/presentation/more/settings/screen/debug/DebugInfoScreen.kt b/app/src/main/java/eu/kanade/presentation/more/settings/screen/debug/DebugInfoScreen.kt index c4886e384..511faefb5 100644 --- a/app/src/main/java/eu/kanade/presentation/more/settings/screen/debug/DebugInfoScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/more/settings/screen/debug/DebugInfoScreen.kt @@ -1,24 +1,38 @@ package eu.kanade.presentation.more.settings.screen.debug import android.os.Build +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.Autorenew +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.ReadOnlyComposable import androidx.compose.runtime.getValue import androidx.compose.runtime.produceState +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.platform.LocalContext import androidx.profileinstaller.ProfileVerifier import cafe.adriel.voyager.navigator.LocalNavigator import cafe.adriel.voyager.navigator.currentOrThrow +import eu.kanade.domain.base.BasePreferences import eu.kanade.presentation.more.settings.Preference import eu.kanade.presentation.more.settings.PreferenceScaffold import eu.kanade.presentation.more.settings.screen.about.AboutScreen import eu.kanade.presentation.util.Screen import eu.kanade.tachiyomi.util.system.DeviceUtil import eu.kanade.tachiyomi.util.system.WebViewUtil +import eu.kanade.tachiyomi.util.system.copyToClipboard import kotlinx.collections.immutable.mutate import kotlinx.collections.immutable.persistentListOf import kotlinx.coroutines.guava.await +import kotlinx.coroutines.launch +import mihon.core.common.FeatureFlags import tachiyomi.i18n.MR +import tachiyomi.presentation.core.util.collectAsState +import uy.kohesive.injekt.Injekt +import uy.kohesive.injekt.api.get class DebugInfoScreen : Screen() { @@ -47,6 +61,12 @@ class DebugInfoScreen : Screen() { @Composable private fun getAppInfoGroup(): Preference.PreferenceGroup { + val context = LocalContext.current + val scope = rememberCoroutineScope() + + val installationIdPref = remember { Injekt.get().installationId() } + val installationId by installationIdPref.collectAsState() + return Preference.PreferenceGroup( title = "App info", preferenceItems = persistentListOf( @@ -58,6 +78,28 @@ class DebugInfoScreen : Screen() { title = "Build time", subtitle = AboutScreen.getFormattedBuildTime(), ), + Preference.PreferenceItem.TextPreference( + title = "Installation ID", + subtitle = installationId, + widget = { + IconButton( + onClick = { + scope.launch { + installationIdPref.set(FeatureFlags.newInstallationId()) + } + }, + ) { + Icon( + imageVector = Icons.Outlined.Autorenew, + tint = MaterialTheme.colorScheme.primary, + contentDescription = null, + ) + } + }, + onClick = { + context.copyToClipboard(installationId, installationId) + }, + ), getProfileVerifierPreference(), Preference.PreferenceItem.TextPreference( title = "WebView version", diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/CrashLogUtil.kt b/app/src/main/java/eu/kanade/tachiyomi/util/CrashLogUtil.kt index ab5489706..a6706087c 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/util/CrashLogUtil.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/CrashLogUtil.kt @@ -2,6 +2,7 @@ package eu.kanade.tachiyomi.util import android.content.Context import android.os.Build +import eu.kanade.domain.base.BasePreferences import eu.kanade.tachiyomi.BuildConfig import eu.kanade.tachiyomi.extension.ExtensionManager import eu.kanade.tachiyomi.util.storage.getUriCompat @@ -20,6 +21,7 @@ import java.time.ZoneId class CrashLogUtil( private val context: Context, private val extensionManager: ExtensionManager = Injekt.get(), + private val preferences: BasePreferences = Injekt.get(), ) { suspend fun dumpLogs(exception: Throwable? = null) = withNonCancellableContext { @@ -44,6 +46,7 @@ class CrashLogUtil( App ID: ${BuildConfig.APPLICATION_ID} App version: ${BuildConfig.VERSION_NAME} (${BuildConfig.FLAVOR}, ${BuildConfig.COMMIT_SHA}, ${BuildConfig.VERSION_CODE}, ${BuildConfig.BUILD_TIME}) Preview build: $syDebugVersion + Installation ID: ${preferences.installationId().get()} Android version: ${Build.VERSION.RELEASE} (SDK ${Build.VERSION.SDK_INT}; build ${Build.DISPLAY}) Device brand: ${Build.BRAND} Device manufacturer: ${Build.MANUFACTURER} diff --git a/app/src/main/java/mihon/core/migration/migrations/InstallationIdMigration.kt b/app/src/main/java/mihon/core/migration/migrations/InstallationIdMigration.kt new file mode 100644 index 000000000..c9c3836ac --- /dev/null +++ b/app/src/main/java/mihon/core/migration/migrations/InstallationIdMigration.kt @@ -0,0 +1,18 @@ +package mihon.core.migration.migrations + +import eu.kanade.domain.base.BasePreferences +import mihon.core.common.FeatureFlags +import mihon.core.migration.Migration +import mihon.core.migration.MigrationContext +import kotlin.uuid.ExperimentalUuidApi + +class InstallationIdMigration : Migration { + override val version: Float = Migration.ALWAYS + + @OptIn(ExperimentalUuidApi::class) + override suspend fun invoke(migrationContext: MigrationContext): Boolean { + val installationId = migrationContext.get()?.installationId() ?: return false + if (!installationId.isSet()) installationId.set(FeatureFlags.newInstallationId()) + return true + } +} diff --git a/app/src/main/java/mihon/core/migration/migrations/Migrations.kt b/app/src/main/java/mihon/core/migration/migrations/Migrations.kt index fdb04bfbc..5ecf71715 100644 --- a/app/src/main/java/mihon/core/migration/migrations/Migrations.kt +++ b/app/src/main/java/mihon/core/migration/migrations/Migrations.kt @@ -47,4 +47,5 @@ val migrations: List TrustExtensionRepositoryMigration(), CategoryPreferencesCleanupMigration(), RemoveDuplicateReaderPreferenceMigration(), + InstallationIdMigration(), ) diff --git a/core/common/src/main/kotlin/mihon/core/common/FeatureFlags.kt b/core/common/src/main/kotlin/mihon/core/common/FeatureFlags.kt new file mode 100644 index 000000000..924968094 --- /dev/null +++ b/core/common/src/main/kotlin/mihon/core/common/FeatureFlags.kt @@ -0,0 +1,12 @@ +package mihon.core.common + +import kotlin.uuid.ExperimentalUuidApi +import kotlin.uuid.Uuid + +object FeatureFlags { + + @OptIn(ExperimentalUuidApi::class) + fun newInstallationId(): String { + return Uuid.random().toHexDashString() + } +}