From 3f2eb75b71f5a737aeabff8755952e35a1bdc190 Mon Sep 17 00:00:00 2001 From: Callum Wong Date: Mon, 12 May 2025 04:15:05 +1000 Subject: [PATCH] Add QR code scan button for sync API key (jobobby04/tachiyomiSY#1430) * Add dependency com.journeyapps:zxing-android-embedded:4.3.0 * Add widget parameter to EditTextPreferenceWidget * Add QR code scanner icon button to sync API key preference which launches a ScanContract * Remove screenOrientation property from CaptureActivity manifest * Allow scanning both normal and inverted codes * store values and make code more concise Co-authored-by: jobobby04 * Import local context --------- Co-authored-by: jobobby04 (cherry picked from commit 84c7da5a7d244ffc0cd7155d15e0a200f548ad35) --- app/build.gradle.kts | 3 ++ app/src/main/AndroidManifest.xml | 4 ++ .../settings/screen/SettingsDataScreen.kt | 51 +++++++++++++++++-- .../widget/EditTextPreferenceWidget.kt | 2 + gradle/sy.versions.toml | 4 +- .../moko-resources/base/strings.xml | 1 + 6 files changed, 60 insertions(+), 5 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 7b722a7dc..13d0a3311 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -327,6 +327,9 @@ dependencies { // Google drive implementation(sylibs.google.api.services.drive) + + // ZXing Android Embedded + implementation(sylibs.zxing.android.embedded) } androidComponents { diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index e4600cbde..a260fadc3 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -406,6 +406,10 @@ android:scheme="tachiyomisy" /> + + diff --git a/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsDataScreen.kt b/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsDataScreen.kt index dafd8cecc..758f005f2 100644 --- a/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsDataScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsDataScreen.kt @@ -16,6 +16,7 @@ import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.outlined.HelpOutline +import androidx.compose.material.icons.filled.QrCodeScanner import androidx.compose.material3.AlertDialog import androidx.compose.material3.Checkbox import androidx.compose.material3.Icon @@ -41,7 +42,10 @@ import androidx.compose.ui.platform.LocalUriHandler import androidx.core.net.toUri import cafe.adriel.voyager.navigator.LocalNavigator import cafe.adriel.voyager.navigator.currentOrThrow +import com.google.zxing.client.android.Intents import com.hippo.unifile.UniFile +import com.journeyapps.barcodescanner.ScanContract +import com.journeyapps.barcodescanner.ScanOptions import eu.kanade.domain.sync.SyncPreferences import eu.kanade.presentation.more.settings.Preference import eu.kanade.presentation.more.settings.screen.data.CreateBackupScreen @@ -50,7 +54,9 @@ import eu.kanade.presentation.more.settings.screen.data.StorageInfo import eu.kanade.presentation.more.settings.screen.data.SyncSettingsSelector import eu.kanade.presentation.more.settings.screen.data.SyncTriggerOptionsScreen import eu.kanade.presentation.more.settings.widget.BasePreferenceWidget +import eu.kanade.presentation.more.settings.widget.EditTextPreferenceWidget import eu.kanade.presentation.more.settings.widget.PrefsHorizontalPadding +import eu.kanade.presentation.more.settings.widget.TrailingWidgetBuffer import eu.kanade.presentation.util.relativeTimeSpanString import eu.kanade.tachiyomi.data.backup.create.BackupCreateJob import eu.kanade.tachiyomi.data.backup.restore.BackupRestoreJob @@ -697,6 +703,22 @@ object SettingsDataScreen : SearchableSettings { @Composable private fun getSelfHostPreferences(syncPreferences: SyncPreferences): List { val scope = rememberCoroutineScope() + + val qrScanLauncher = rememberLauncherForActivityResult(ScanContract()) { + if (it.contents != null && it.contents.isNotEmpty()) { + syncPreferences.clientAPIKey().set(it.contents) + } + } + val context = LocalContext.current + val scanOptions = remember { + ScanOptions().apply { + setDesiredBarcodeFormats(ScanOptions.QR_CODE) + setOrientationLocked(false) + setPrompt(SYMR.strings.scan_qr_code.getString(context)) + addExtra(Intents.Scan.SCAN_TYPE, Intents.Scan.MIXED_SCAN) + } + } + return listOf( Preference.PreferenceItem.EditTextPreference( preference = syncPreferences.clientHost(), @@ -712,11 +734,32 @@ object SettingsDataScreen : SearchableSettings { true }, ), - Preference.PreferenceItem.EditTextPreference( - preference = syncPreferences.clientAPIKey(), + Preference.PreferenceItem.CustomPreference( title = stringResource(SYMR.strings.pref_sync_api_key), - subtitle = stringResource(SYMR.strings.pref_sync_api_key_summ), - ), + ) { + val values by syncPreferences.clientAPIKey().collectAsState() + EditTextPreferenceWidget( + title = stringResource(SYMR.strings.pref_sync_api_key), + subtitle = stringResource(SYMR.strings.pref_sync_api_key_summ), + onConfirm = { + syncPreferences.clientAPIKey().set(it) + true + }, + icon = null, + value = values, + widget = { + IconButton( + onClick = { qrScanLauncher.launch(scanOptions) }, + modifier = Modifier.padding(start = TrailingWidgetBuffer), + ) { + Icon( + Icons.Filled.QrCodeScanner, + contentDescription = stringResource(SYMR.strings.scan_qr_code), + ) + } + }, + ) + }, ) } diff --git a/app/src/main/java/eu/kanade/presentation/more/settings/widget/EditTextPreferenceWidget.kt b/app/src/main/java/eu/kanade/presentation/more/settings/widget/EditTextPreferenceWidget.kt index 00d8f16d4..61c240f7f 100644 --- a/app/src/main/java/eu/kanade/presentation/more/settings/widget/EditTextPreferenceWidget.kt +++ b/app/src/main/java/eu/kanade/presentation/more/settings/widget/EditTextPreferenceWidget.kt @@ -31,6 +31,7 @@ fun EditTextPreferenceWidget( subtitle: String?, icon: ImageVector?, value: String, + widget: @Composable (() -> Unit)? = null, onConfirm: suspend (String) -> Boolean, ) { var isDialogShown by remember { mutableStateOf(false) } @@ -39,6 +40,7 @@ fun EditTextPreferenceWidget( title = title, subtitle = subtitle?.format(value), icon = icon, + widget = widget, onPreferenceClick = { isDialogShown = true }, ) diff --git a/gradle/sy.versions.toml b/gradle/sy.versions.toml index 5adb701b3..8e175fba9 100644 --- a/gradle/sy.versions.toml +++ b/gradle/sy.versions.toml @@ -13,4 +13,6 @@ sqlcipher = "net.zetetic:sqlcipher-android:4.9.0" exifinterface = "androidx.exifinterface:exifinterface:1.4.1" -google-api-services-drive = "com.google.apis:google-api-services-drive:v3-rev20250511-2.0.0" \ No newline at end of file +google-api-services-drive = "com.google.apis:google-api-services-drive:v3-rev20250511-2.0.0" + +zxing-android-embedded = "com.journeyapps:zxing-android-embedded:4.3.0" diff --git a/i18n-sy/src/commonMain/moko-resources/base/strings.xml b/i18n-sy/src/commonMain/moko-resources/base/strings.xml index 6e16a10fb..0dfaa6c28 100644 --- a/i18n-sy/src/commonMain/moko-resources/base/strings.xml +++ b/i18n-sy/src/commonMain/moko-resources/base/strings.xml @@ -229,6 +229,7 @@ Synchronization frequency Choose what to sync SyncYomi + Scan a QR code Last Synchronization: %1$s Google Drive Sign in