diff --git a/app/src/dev/java/mihon/core/firebase/Firebase.kt b/app/src/dev/java/mihon/core/firebase/Firebase.kt
new file mode 100644
index 000000000..6108a4c76
--- /dev/null
+++ b/app/src/dev/java/mihon/core/firebase/Firebase.kt
@@ -0,0 +1,9 @@
+package mihon.core.firebase
+
+import android.content.Context
+import eu.kanade.tachiyomi.core.security.PrivacyPreferences
+import kotlinx.coroutines.CoroutineScope
+
+object Firebase {
+ fun setup(context: Context, preference: PrivacyPreferences, scope: CoroutineScope) = Unit
+}
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 2587ddff4..0ab666594 100755
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -268,6 +268,14 @@
android:name="android.webkit.WebView.MetricsOptOut"
android:value="true" />
+
+
+
+
Unit,
+ ) {
+ ListItem(
+ modifier = modifier,
+ headlineContent = { Text(text = title) },
+ supportingContent = { Text(text = subtitle) },
+ trailingContent = {
+ Switch(
+ checked = granted,
+ onCheckedChange = onToggleChange,
+ )
+ },
+ colors = ListItemDefaults.colors(containerColor = Color.Transparent),
+ )
+ }
}
diff --git a/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsSecurityScreen.kt b/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsSecurityScreen.kt
index 1508cf8db..a79d37ec0 100644
--- a/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsSecurityScreen.kt
+++ b/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsSecurityScreen.kt
@@ -45,6 +45,7 @@ import cafe.adriel.voyager.navigator.LocalNavigator
import cafe.adriel.voyager.navigator.currentOrThrow
import dev.icerock.moko.resources.StringResource
import eu.kanade.presentation.more.settings.Preference
+import eu.kanade.tachiyomi.core.security.PrivacyPreferences
import eu.kanade.tachiyomi.core.security.SecurityPreferences
import eu.kanade.tachiyomi.ui.base.delegate.SecureActivityDelegate
import eu.kanade.tachiyomi.ui.category.biometric.BiometricTimesScreen
@@ -70,10 +71,20 @@ object SettingsSecurityScreen : SearchableSettings {
@Composable
override fun getPreferences(): List {
- val context = LocalContext.current
val securityPreferences = remember { Injekt.get() }
- val authSupported = remember { context.isAuthenticationSupported() }
+ val privacyPreferences = remember { Injekt.get() }
+ return listOf(
+ getSecurityGroup(securityPreferences),
+ getFirebaseGroup(privacyPreferences),
+ )
+ }
+ @Composable
+ private fun getSecurityGroup(
+ securityPreferences: SecurityPreferences,
+ ): Preference.PreferenceGroup {
+ val context = LocalContext.current
+ val authSupported = remember { context.isAuthenticationSupported() }
val useAuthPref = securityPreferences.useAuthenticator()
val useAuth by useAuthPref.collectAsState()
@@ -81,129 +92,132 @@ object SettingsSecurityScreen : SearchableSettings {
val isCbzPasswordSet by remember { CbzCrypto.isPasswordSetState(scope) }.collectAsState()
val passwordProtectDownloads by securityPreferences.passwordProtectDownloads().collectAsState()
- return listOf(
- Preference.PreferenceItem.SwitchPreference(
- pref = useAuthPref,
- title = stringResource(MR.strings.lock_with_biometrics),
- enabled = authSupported,
- onValueChanged = {
- (context as FragmentActivity).authenticate(
- title = context.stringResource(MR.strings.lock_with_biometrics),
- )
- },
- ),
- Preference.PreferenceItem.ListPreference(
- pref = securityPreferences.lockAppAfter(),
- title = stringResource(MR.strings.lock_when_idle),
- enabled = authSupported && useAuth,
- entries = LockAfterValues
- .associateWith {
- when (it) {
- -1 -> stringResource(MR.strings.lock_never)
- 0 -> stringResource(MR.strings.lock_always)
- else -> pluralStringResource(MR.plurals.lock_after_mins, count = it, it)
+ return Preference.PreferenceGroup(
+ title = stringResource(MR.strings.pref_security),
+ preferenceItems = persistentListOf(
+ Preference.PreferenceItem.SwitchPreference(
+ pref = useAuthPref,
+ title = stringResource(MR.strings.lock_with_biometrics),
+ enabled = authSupported,
+ onValueChanged = {
+ (context as FragmentActivity).authenticate(
+ title = context.stringResource(MR.strings.lock_with_biometrics),
+ )
+ },
+ ),
+ Preference.PreferenceItem.ListPreference(
+ pref = securityPreferences.lockAppAfter(),
+ title = stringResource(MR.strings.lock_when_idle),
+ enabled = authSupported && useAuth,
+ entries = LockAfterValues
+ .associateWith {
+ when (it) {
+ -1 -> stringResource(MR.strings.lock_never)
+ 0 -> stringResource(MR.strings.lock_always)
+ else -> pluralStringResource(MR.plurals.lock_after_mins, count = it, it)
+ }
}
- }
- .toImmutableMap(),
- onValueChanged = {
- (context as FragmentActivity).authenticate(
- title = context.stringResource(MR.strings.lock_when_idle),
- )
- },
- ),
- Preference.PreferenceItem.SwitchPreference(
- pref = securityPreferences.hideNotificationContent(),
- title = stringResource(MR.strings.hide_notification_content),
- ),
- Preference.PreferenceItem.ListPreference(
- pref = securityPreferences.secureScreen(),
- title = stringResource(MR.strings.secure_screen),
- entries = SecurityPreferences.SecureScreenMode.entries
- .associateWith { stringResource(it.titleRes) }
- .toImmutableMap(),
- ),
- // SY -->
- Preference.PreferenceItem.SwitchPreference(
- pref = securityPreferences.passwordProtectDownloads(),
- title = stringResource(SYMR.strings.password_protect_downloads),
- subtitle = stringResource(SYMR.strings.password_protect_downloads_summary),
- enabled = isCbzPasswordSet,
- ),
- Preference.PreferenceItem.ListPreference(
- pref = securityPreferences.encryptionType(),
- title = stringResource(SYMR.strings.encryption_type),
- entries = SecurityPreferences.EncryptionType.entries
- .associateWith { stringResource(it.titleRes) }
- .toImmutableMap(),
- enabled = passwordProtectDownloads,
-
- ),
- kotlin.run {
- var dialogOpen by remember { mutableStateOf(false) }
- if (dialogOpen) {
- PasswordDialog(
- onDismissRequest = { dialogOpen = false },
- onReturnPassword = { password ->
- dialogOpen = false
-
- CbzCrypto.deleteKeyCbz()
- securityPreferences.cbzPassword().set(CbzCrypto.encryptCbz(password.replace("\n", "")))
- },
- )
- }
- Preference.PreferenceItem.TextPreference(
- title = stringResource(SYMR.strings.set_cbz_zip_password),
- onClick = {
- dialogOpen = true
+ .toImmutableMap(),
+ onValueChanged = {
+ (context as FragmentActivity).authenticate(
+ title = context.stringResource(MR.strings.lock_when_idle),
+ )
},
- )
- },
- Preference.PreferenceItem.TextPreference(
- title = stringResource(SYMR.strings.delete_cbz_archive_password),
- onClick = {
- CbzCrypto.deleteKeyCbz()
- securityPreferences.cbzPassword().set("")
- },
- enabled = isCbzPasswordSet,
- ),
- kotlin.run {
- val navigator = LocalNavigator.currentOrThrow
- val count by securityPreferences.authenticatorTimeRanges().collectAsState()
- Preference.PreferenceItem.TextPreference(
- title = stringResource(SYMR.strings.action_edit_biometric_lock_times),
- subtitle = pluralStringResource(
- SYMR.plurals.num_lock_times,
- count.size,
- count.size,
+ ),
+ Preference.PreferenceItem.SwitchPreference(
+ pref = securityPreferences.hideNotificationContent(),
+ title = stringResource(MR.strings.hide_notification_content),
+ ),
+ Preference.PreferenceItem.ListPreference(
+ pref = securityPreferences.secureScreen(),
+ title = stringResource(MR.strings.secure_screen),
+ entries = SecurityPreferences.SecureScreenMode.entries
+ .associateWith { stringResource(it.titleRes) }
+ .toImmutableMap(),
+ ),
+ // SY -->
+ Preference.PreferenceItem.SwitchPreference(
+ pref = securityPreferences.passwordProtectDownloads(),
+ title = stringResource(SYMR.strings.password_protect_downloads),
+ subtitle = stringResource(SYMR.strings.password_protect_downloads_summary),
+ enabled = isCbzPasswordSet,
+ ),
+ Preference.PreferenceItem.ListPreference(
+ pref = securityPreferences.encryptionType(),
+ title = stringResource(SYMR.strings.encryption_type),
+ entries = SecurityPreferences.EncryptionType.entries
+ .associateWith { stringResource(it.titleRes) }
+ .toImmutableMap(),
+ enabled = passwordProtectDownloads,
+
),
- onClick = {
- navigator.push(BiometricTimesScreen())
- },
- enabled = useAuth,
- )
- },
- kotlin.run {
- val selection by securityPreferences.authenticatorDays().collectAsState()
- var dialogOpen by remember { mutableStateOf(false) }
- if (dialogOpen) {
- SetLockedDaysDialog(
- onDismissRequest = { dialogOpen = false },
- initialSelection = selection,
- onDaysSelected = {
- dialogOpen = false
- securityPreferences.authenticatorDays().set(it)
+ kotlin.run {
+ var dialogOpen by remember { mutableStateOf(false) }
+ if (dialogOpen) {
+ PasswordDialog(
+ onDismissRequest = { dialogOpen = false },
+ onReturnPassword = { password ->
+ dialogOpen = false
+
+ CbzCrypto.deleteKeyCbz()
+ securityPreferences.cbzPassword().set(CbzCrypto.encryptCbz(password.replace("\n", "")))
+ },
+ )
+ }
+ Preference.PreferenceItem.TextPreference(
+ title = stringResource(SYMR.strings.set_cbz_zip_password),
+ onClick = {
+ dialogOpen = true
},
)
- }
+ },
Preference.PreferenceItem.TextPreference(
- title = stringResource(SYMR.strings.biometric_lock_days),
- subtitle = stringResource(SYMR.strings.biometric_lock_days_summary),
- onClick = { dialogOpen = true },
- enabled = useAuth,
- )
- },
- // SY <--
- Preference.PreferenceItem.InfoPreference(stringResource(MR.strings.secure_screen_summary)),
+ title = stringResource(SYMR.strings.delete_cbz_archive_password),
+ onClick = {
+ CbzCrypto.deleteKeyCbz()
+ securityPreferences.cbzPassword().set("")
+ },
+ enabled = isCbzPasswordSet,
+ ),
+ kotlin.run {
+ val navigator = LocalNavigator.currentOrThrow
+ val count by securityPreferences.authenticatorTimeRanges().collectAsState()
+ Preference.PreferenceItem.TextPreference(
+ title = stringResource(SYMR.strings.action_edit_biometric_lock_times),
+ subtitle = pluralStringResource(
+ SYMR.plurals.num_lock_times,
+ count.size,
+ count.size,
+ ),
+ onClick = {
+ navigator.push(BiometricTimesScreen())
+ },
+ enabled = useAuth,
+ )
+ },
+ kotlin.run {
+ val selection by securityPreferences.authenticatorDays().collectAsState()
+ var dialogOpen by remember { mutableStateOf(false) }
+ if (dialogOpen) {
+ SetLockedDaysDialog(
+ onDismissRequest = { dialogOpen = false },
+ initialSelection = selection,
+ onDaysSelected = {
+ dialogOpen = false
+ securityPreferences.authenticatorDays().set(it)
+ },
+ )
+ }
+ Preference.PreferenceItem.TextPreference(
+ title = stringResource(SYMR.strings.biometric_lock_days),
+ subtitle = stringResource(SYMR.strings.biometric_lock_days_summary),
+ onClick = { dialogOpen = true },
+ enabled = useAuth,
+ )
+ },
+ // SY <--
+ Preference.PreferenceItem.InfoPreference(stringResource(MR.strings.secure_screen_summary)),
+ )
)
}
@@ -361,6 +375,28 @@ object SettingsSecurityScreen : SearchableSettings {
)
}
// SY <--
+
+ @Composable
+ private fun getFirebaseGroup(
+ privacyPreferences: PrivacyPreferences,
+ ): Preference.PreferenceGroup {
+ return Preference.PreferenceGroup(
+ title = stringResource(MR.strings.pref_firebase),
+ preferenceItems = persistentListOf(
+ Preference.PreferenceItem.SwitchPreference(
+ pref = privacyPreferences.crashlytics(),
+ title = stringResource(MR.strings.onboarding_permission_crashlytics),
+ subtitle = stringResource(MR.strings.onboarding_permission_crashlytics_description),
+ ),
+ Preference.PreferenceItem.SwitchPreference(
+ pref = privacyPreferences.analytics(),
+ title = stringResource(MR.strings.onboarding_permission_analytics),
+ subtitle = stringResource(MR.strings.onboarding_permission_analytics_description),
+ ),
+ Preference.PreferenceItem.InfoPreference(stringResource(MR.strings.firebase_summary)),
+ ),
+ )
+ }
}
private val LockAfterValues = persistentListOf(
diff --git a/app/src/main/java/eu/kanade/tachiyomi/App.kt b/app/src/main/java/eu/kanade/tachiyomi/App.kt
index eecbd877a..842a11e03 100755
--- a/app/src/main/java/eu/kanade/tachiyomi/App.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/App.kt
@@ -28,14 +28,13 @@ import com.elvishew.xlog.printer.AndroidPrinter
import com.elvishew.xlog.printer.Printer
import com.elvishew.xlog.printer.file.backup.NeverBackupStrategy
import com.elvishew.xlog.printer.file.naming.DateFileNameGenerator
-import com.google.firebase.crashlytics.ktx.crashlytics
-import com.google.firebase.ktx.Firebase
import eu.kanade.domain.DomainModule
import eu.kanade.domain.SYDomainModule
import eu.kanade.domain.base.BasePreferences
import eu.kanade.domain.sync.SyncPreferences
import eu.kanade.domain.ui.UiPreferences
import eu.kanade.domain.ui.model.setAppCompatDelegateThemeMode
+import eu.kanade.tachiyomi.core.security.PrivacyPreferences
import eu.kanade.tachiyomi.crash.CrashActivity
import eu.kanade.tachiyomi.crash.GlobalExceptionHandler
import eu.kanade.tachiyomi.data.coil.BufferedSourceFetcher
@@ -60,8 +59,6 @@ import eu.kanade.tachiyomi.util.system.DeviceUtil
import eu.kanade.tachiyomi.util.system.WebViewUtil
import eu.kanade.tachiyomi.util.system.animatorDurationScale
import eu.kanade.tachiyomi.util.system.cancelNotification
-import eu.kanade.tachiyomi.util.system.isDevFlavor
-import eu.kanade.tachiyomi.util.system.isReleaseBuildType
import eu.kanade.tachiyomi.util.system.notify
import exh.log.CrashlyticsPrinter
import exh.log.EHLogLevel
@@ -74,6 +71,7 @@ import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import logcat.LogPriority
import logcat.LogcatLogger
+import mihon.core.firebase.Firebase
import mihon.core.migration.Migrator
import mihon.core.migration.migrations.migrations
import org.conscrypt.Conscrypt
@@ -94,6 +92,7 @@ import java.util.Locale
class App : Application(), DefaultLifecycleObserver, SingletonImageLoader.Factory {
private val basePreferences: BasePreferences by injectLazy()
+ private val privacyPreferences: PrivacyPreferences by injectLazy()
private val networkPreferences: NetworkPreferences by injectLazy()
private val disableIncognitoReceiver = DisableIncognitoReceiver()
@@ -102,11 +101,6 @@ class App : Application(), DefaultLifecycleObserver, SingletonImageLoader.Factor
override fun onCreate() {
super.onCreate()
- // SY -->
- if (!isDevFlavor) {
- Firebase.crashlytics.setCrashlyticsCollectionEnabled(isReleaseBuildType)
- }
- // SY <--
GlobalExceptionHandler.initialize(applicationContext, CrashActivity::class.java)
// TLS 1.3 support for Android < 10
@@ -133,6 +127,8 @@ class App : Application(), DefaultLifecycleObserver, SingletonImageLoader.Factor
setupExhLogging() // EXH logging
LogcatLogger.install(XLogLogcatLogger()) // SY Redirect Logcat to XLog
+ Firebase.setup(applicationContext, privacyPreferences, ProcessLifecycleOwner.get().lifecycleScope)
+
setupNotificationChannels()
ProcessLifecycleOwner.get().lifecycle.addObserver(this)
diff --git a/app/src/main/java/eu/kanade/tachiyomi/di/PreferenceModule.kt b/app/src/main/java/eu/kanade/tachiyomi/di/PreferenceModule.kt
index 5cec2bd39..c482acc47 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/di/PreferenceModule.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/di/PreferenceModule.kt
@@ -6,6 +6,7 @@ import eu.kanade.domain.source.service.SourcePreferences
import eu.kanade.domain.sync.SyncPreferences
import eu.kanade.domain.track.service.TrackPreferences
import eu.kanade.domain.ui.UiPreferences
+import eu.kanade.tachiyomi.core.security.PrivacyPreferences
import eu.kanade.tachiyomi.core.security.SecurityPreferences
import eu.kanade.tachiyomi.network.NetworkPreferences
import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences
@@ -37,6 +38,9 @@ class PreferenceModule(val app: Application) : InjektModule {
addSingletonFactory {
SecurityPreferences(get())
}
+ addSingletonFactory {
+ PrivacyPreferences(get())
+ }
addSingletonFactory {
LibraryPreferences(get())
}
diff --git a/app/src/standard/java/mihon/core/firebase/Firebase.kt b/app/src/standard/java/mihon/core/firebase/Firebase.kt
new file mode 100644
index 000000000..7ec88da27
--- /dev/null
+++ b/app/src/standard/java/mihon/core/firebase/Firebase.kt
@@ -0,0 +1,20 @@
+package mihon.core.firebase
+
+import android.content.Context
+import com.google.firebase.analytics.FirebaseAnalytics
+import com.google.firebase.crashlytics.FirebaseCrashlytics
+import eu.kanade.tachiyomi.core.security.PrivacyPreferences
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+
+object Firebase {
+ fun setup(context: Context, preference: PrivacyPreferences, scope: CoroutineScope) {
+ preference.analytics().changes().onEach { enabled ->
+ FirebaseAnalytics.getInstance(context).setAnalyticsCollectionEnabled(enabled)
+ }.launchIn(scope)
+ preference.crashlytics().changes().onEach { enabled ->
+ FirebaseCrashlytics.getInstance().setCrashlyticsCollectionEnabled(enabled)
+ }.launchIn(scope)
+ }
+}
diff --git a/core/common/src/main/kotlin/eu/kanade/tachiyomi/core/security/PrivacyPreferences.kt b/core/common/src/main/kotlin/eu/kanade/tachiyomi/core/security/PrivacyPreferences.kt
new file mode 100644
index 000000000..ebf5692d8
--- /dev/null
+++ b/core/common/src/main/kotlin/eu/kanade/tachiyomi/core/security/PrivacyPreferences.kt
@@ -0,0 +1,11 @@
+package eu.kanade.tachiyomi.core.security
+
+import tachiyomi.core.common.preference.PreferenceStore
+
+class PrivacyPreferences(
+ private val preferenceStore: PreferenceStore,
+) {
+ fun crashlytics() = preferenceStore.getBoolean("crashlytics", true)
+
+ fun analytics() = preferenceStore.getBoolean("analytics", true)
+}
diff --git a/i18n/src/commonMain/moko-resources/base/strings.xml b/i18n/src/commonMain/moko-resources/base/strings.xml
index 69688c1aa..c298d62f0 100755
--- a/i18n/src/commonMain/moko-resources/base/strings.xml
+++ b/i18n/src/commonMain/moko-resources/base/strings.xml
@@ -190,6 +190,10 @@
Get notified for library updates and more.
Background battery usage
Avoid interruptions to long-running library updates, downloads, and backup restores.
+ Send crash logs
+ Send anonymized crash logs to the developers.
+ Allow analytics
+ Send anonymized usage data to improve app features.
Grant
New to %s? We recommend checking out the getting started guide.
Reinstalling %s?
@@ -242,6 +246,9 @@
App language
Security and privacy
+ Security
+ Analytics and Crash logs
+
Require unlock
Lock when idle
Always
@@ -249,6 +256,7 @@
Hide notification content
Secure screen
Secure screen hides app contents when switching apps and block screenshots
+ Sending crash logs and analytics will allow us to identify and fix issues, improve performance, and make future updates more relevant to your needs
NSFW (18+) sources
Show in sources and extensions lists