Add option to opt out of Analytics and Crashlytics (#1237)

(cherry picked from commit 7c7af72f8cb12decc06b76c36852dcc54696236d)

# Conflicts:
#	app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsSecurityScreen.kt
#	app/src/standard/AndroidManifest.xml
This commit is contained in:
Roshan Varughese 2024-10-12 13:46:28 +13:00 committed by Jobobby04
parent f6d2d0bd48
commit 2bd9a914c1
9 changed files with 276 additions and 132 deletions

View File

@ -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
}

View File

@ -268,6 +268,14 @@
android:name="android.webkit.WebView.MetricsOptOut" android:name="android.webkit.WebView.MetricsOptOut"
android:value="true" /> android:value="true" />
<!-- Disable for manual opt-in -->
<meta-data
android:name="firebase_analytics_collection_enabled"
android:value="false" />
<meta-data
android:name="firebase_crashlytics_collection_enabled"
android:value="false" />
<!-- Disable advertising ID collection for Firebase --> <!-- Disable advertising ID collection for Firebase -->
<meta-data <meta-data
android:name="google_analytics_adid_collection_enabled" android:name="google_analytics_adid_collection_enabled"

View File

@ -14,11 +14,13 @@ import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Check import androidx.compose.material.icons.filled.Check
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.ListItem import androidx.compose.material3.ListItem
import androidx.compose.material3.ListItemDefaults import androidx.compose.material3.ListItemDefaults
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedButton import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.Switch
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.DisposableEffect
@ -34,13 +36,18 @@ import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.compose.LocalLifecycleOwner import androidx.lifecycle.compose.LocalLifecycleOwner
import eu.kanade.presentation.util.rememberRequestPackageInstallsPermissionState import eu.kanade.presentation.util.rememberRequestPackageInstallsPermissionState
import eu.kanade.tachiyomi.core.security.PrivacyPreferences
import eu.kanade.tachiyomi.util.system.launchRequestPackageInstallsPermission import eu.kanade.tachiyomi.util.system.launchRequestPackageInstallsPermission
import tachiyomi.i18n.MR import tachiyomi.i18n.MR
import tachiyomi.presentation.core.i18n.stringResource import tachiyomi.presentation.core.i18n.stringResource
import tachiyomi.presentation.core.util.collectAsState
import tachiyomi.presentation.core.util.secondaryItemAlpha import tachiyomi.presentation.core.util.secondaryItemAlpha
import uy.kohesive.injekt.injectLazy
internal class PermissionStep : OnboardingStep { internal class PermissionStep : OnboardingStep {
private val privacyPreferences: PrivacyPreferences by injectLazy()
private var notificationGranted by mutableStateOf(false) private var notificationGranted by mutableStateOf(false)
private var batteryGranted by mutableStateOf(false) private var batteryGranted by mutableStateOf(false)
@ -73,7 +80,7 @@ internal class PermissionStep : OnboardingStep {
} }
Column { Column {
PermissionItem( PermissionCheckbox(
title = stringResource(MR.strings.onboarding_permission_install_apps), title = stringResource(MR.strings.onboarding_permission_install_apps),
subtitle = stringResource(MR.strings.onboarding_permission_install_apps_description), subtitle = stringResource(MR.strings.onboarding_permission_install_apps_description),
granted = installGranted, granted = installGranted,
@ -89,7 +96,7 @@ internal class PermissionStep : OnboardingStep {
// no-op. resulting checks is being done on resume // no-op. resulting checks is being done on resume
}, },
) )
PermissionItem( PermissionCheckbox(
title = stringResource(MR.strings.onboarding_permission_notifications), title = stringResource(MR.strings.onboarding_permission_notifications),
subtitle = stringResource(MR.strings.onboarding_permission_notifications_description), subtitle = stringResource(MR.strings.onboarding_permission_notifications_description),
granted = notificationGranted, granted = notificationGranted,
@ -97,7 +104,7 @@ internal class PermissionStep : OnboardingStep {
) )
} }
PermissionItem( PermissionCheckbox(
title = stringResource(MR.strings.onboarding_permission_ignore_battery_opts), title = stringResource(MR.strings.onboarding_permission_ignore_battery_opts),
subtitle = stringResource(MR.strings.onboarding_permission_ignore_battery_opts_description), subtitle = stringResource(MR.strings.onboarding_permission_ignore_battery_opts_description),
granted = batteryGranted, granted = batteryGranted,
@ -109,6 +116,29 @@ internal class PermissionStep : OnboardingStep {
context.startActivity(intent) context.startActivity(intent)
}, },
) )
HorizontalDivider(
modifier = Modifier.padding(vertical = 8.dp, horizontal = 16.dp),
color = MaterialTheme.colorScheme.onPrimaryContainer,
)
val crashlyticsPref = privacyPreferences.crashlytics()
val crashlytics by crashlyticsPref.collectAsState()
PermissionSwitch(
title = stringResource(MR.strings.onboarding_permission_crashlytics),
subtitle = stringResource(MR.strings.onboarding_permission_crashlytics_description),
granted = crashlytics,
onToggleChange = crashlyticsPref::set,
)
val analyticsPref = privacyPreferences.analytics()
val analytics by analyticsPref.collectAsState()
PermissionSwitch(
title = stringResource(MR.strings.onboarding_permission_analytics),
subtitle = stringResource(MR.strings.onboarding_permission_analytics_description),
granted = analytics,
onToggleChange = analyticsPref::set,
)
} }
} }
@ -127,7 +157,7 @@ internal class PermissionStep : OnboardingStep {
} }
@Composable @Composable
private fun PermissionItem( private fun PermissionCheckbox(
title: String, title: String,
subtitle: String, subtitle: String,
granted: Boolean, granted: Boolean,
@ -157,4 +187,26 @@ internal class PermissionStep : OnboardingStep {
colors = ListItemDefaults.colors(containerColor = Color.Transparent), colors = ListItemDefaults.colors(containerColor = Color.Transparent),
) )
} }
@Composable
private fun PermissionSwitch(
title: String,
subtitle: String,
granted: Boolean,
modifier: Modifier = Modifier,
onToggleChange: (Boolean) -> Unit,
) {
ListItem(
modifier = modifier,
headlineContent = { Text(text = title) },
supportingContent = { Text(text = subtitle) },
trailingContent = {
Switch(
checked = granted,
onCheckedChange = onToggleChange,
)
},
colors = ListItemDefaults.colors(containerColor = Color.Transparent),
)
}
} }

View File

@ -45,6 +45,7 @@ import cafe.adriel.voyager.navigator.LocalNavigator
import cafe.adriel.voyager.navigator.currentOrThrow import cafe.adriel.voyager.navigator.currentOrThrow
import dev.icerock.moko.resources.StringResource import dev.icerock.moko.resources.StringResource
import eu.kanade.presentation.more.settings.Preference 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.core.security.SecurityPreferences
import eu.kanade.tachiyomi.ui.base.delegate.SecureActivityDelegate import eu.kanade.tachiyomi.ui.base.delegate.SecureActivityDelegate
import eu.kanade.tachiyomi.ui.category.biometric.BiometricTimesScreen import eu.kanade.tachiyomi.ui.category.biometric.BiometricTimesScreen
@ -70,10 +71,20 @@ object SettingsSecurityScreen : SearchableSettings {
@Composable @Composable
override fun getPreferences(): List<Preference> { override fun getPreferences(): List<Preference> {
val context = LocalContext.current
val securityPreferences = remember { Injekt.get<SecurityPreferences>() } val securityPreferences = remember { Injekt.get<SecurityPreferences>() }
val authSupported = remember { context.isAuthenticationSupported() } val privacyPreferences = remember { Injekt.get<PrivacyPreferences>() }
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 useAuthPref = securityPreferences.useAuthenticator()
val useAuth by useAuthPref.collectAsState() val useAuth by useAuthPref.collectAsState()
@ -81,129 +92,132 @@ object SettingsSecurityScreen : SearchableSettings {
val isCbzPasswordSet by remember { CbzCrypto.isPasswordSetState(scope) }.collectAsState() val isCbzPasswordSet by remember { CbzCrypto.isPasswordSetState(scope) }.collectAsState()
val passwordProtectDownloads by securityPreferences.passwordProtectDownloads().collectAsState() val passwordProtectDownloads by securityPreferences.passwordProtectDownloads().collectAsState()
return listOf( return Preference.PreferenceGroup(
Preference.PreferenceItem.SwitchPreference( title = stringResource(MR.strings.pref_security),
pref = useAuthPref, preferenceItems = persistentListOf(
title = stringResource(MR.strings.lock_with_biometrics), Preference.PreferenceItem.SwitchPreference(
enabled = authSupported, pref = useAuthPref,
onValueChanged = { title = stringResource(MR.strings.lock_with_biometrics),
(context as FragmentActivity).authenticate( enabled = authSupported,
title = context.stringResource(MR.strings.lock_with_biometrics), 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), Preference.PreferenceItem.ListPreference(
enabled = authSupported && useAuth, pref = securityPreferences.lockAppAfter(),
entries = LockAfterValues title = stringResource(MR.strings.lock_when_idle),
.associateWith { enabled = authSupported && useAuth,
when (it) { entries = LockAfterValues
-1 -> stringResource(MR.strings.lock_never) .associateWith {
0 -> stringResource(MR.strings.lock_always) when (it) {
else -> pluralStringResource(MR.plurals.lock_after_mins, count = it, it) -1 -> stringResource(MR.strings.lock_never)
0 -> stringResource(MR.strings.lock_always)
else -> pluralStringResource(MR.plurals.lock_after_mins, count = it, it)
}
} }
} .toImmutableMap(),
.toImmutableMap(), onValueChanged = {
onValueChanged = { (context as FragmentActivity).authenticate(
(context as FragmentActivity).authenticate( title = context.stringResource(MR.strings.lock_when_idle),
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
}, },
) ),
}, Preference.PreferenceItem.SwitchPreference(
Preference.PreferenceItem.TextPreference( pref = securityPreferences.hideNotificationContent(),
title = stringResource(SYMR.strings.delete_cbz_archive_password), title = stringResource(MR.strings.hide_notification_content),
onClick = { ),
CbzCrypto.deleteKeyCbz() Preference.PreferenceItem.ListPreference(
securityPreferences.cbzPassword().set("") pref = securityPreferences.secureScreen(),
}, title = stringResource(MR.strings.secure_screen),
enabled = isCbzPasswordSet, entries = SecurityPreferences.SecureScreenMode.entries
), .associateWith { stringResource(it.titleRes) }
kotlin.run { .toImmutableMap(),
val navigator = LocalNavigator.currentOrThrow ),
val count by securityPreferences.authenticatorTimeRanges().collectAsState() // SY -->
Preference.PreferenceItem.TextPreference( Preference.PreferenceItem.SwitchPreference(
title = stringResource(SYMR.strings.action_edit_biometric_lock_times), pref = securityPreferences.passwordProtectDownloads(),
subtitle = pluralStringResource( title = stringResource(SYMR.strings.password_protect_downloads),
SYMR.plurals.num_lock_times, subtitle = stringResource(SYMR.strings.password_protect_downloads_summary),
count.size, enabled = isCbzPasswordSet,
count.size, ),
Preference.PreferenceItem.ListPreference(
pref = securityPreferences.encryptionType(),
title = stringResource(SYMR.strings.encryption_type),
entries = SecurityPreferences.EncryptionType.entries
.associateWith { stringResource(it.titleRes) }
.toImmutableMap(),
enabled = passwordProtectDownloads,
), ),
onClick = { kotlin.run {
navigator.push(BiometricTimesScreen()) var dialogOpen by remember { mutableStateOf(false) }
}, if (dialogOpen) {
enabled = useAuth, PasswordDialog(
) onDismissRequest = { dialogOpen = false },
}, onReturnPassword = { password ->
kotlin.run { dialogOpen = false
val selection by securityPreferences.authenticatorDays().collectAsState()
var dialogOpen by remember { mutableStateOf(false) } CbzCrypto.deleteKeyCbz()
if (dialogOpen) { securityPreferences.cbzPassword().set(CbzCrypto.encryptCbz(password.replace("\n", "")))
SetLockedDaysDialog( },
onDismissRequest = { dialogOpen = false }, )
initialSelection = selection, }
onDaysSelected = { Preference.PreferenceItem.TextPreference(
dialogOpen = false title = stringResource(SYMR.strings.set_cbz_zip_password),
securityPreferences.authenticatorDays().set(it) onClick = {
dialogOpen = true
}, },
) )
} },
Preference.PreferenceItem.TextPreference( Preference.PreferenceItem.TextPreference(
title = stringResource(SYMR.strings.biometric_lock_days), title = stringResource(SYMR.strings.delete_cbz_archive_password),
subtitle = stringResource(SYMR.strings.biometric_lock_days_summary), onClick = {
onClick = { dialogOpen = true }, CbzCrypto.deleteKeyCbz()
enabled = useAuth, securityPreferences.cbzPassword().set("")
) },
}, enabled = isCbzPasswordSet,
// SY <-- ),
Preference.PreferenceItem.InfoPreference(stringResource(MR.strings.secure_screen_summary)), 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 <-- // 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( private val LockAfterValues = persistentListOf(

View File

@ -28,14 +28,13 @@ import com.elvishew.xlog.printer.AndroidPrinter
import com.elvishew.xlog.printer.Printer import com.elvishew.xlog.printer.Printer
import com.elvishew.xlog.printer.file.backup.NeverBackupStrategy import com.elvishew.xlog.printer.file.backup.NeverBackupStrategy
import com.elvishew.xlog.printer.file.naming.DateFileNameGenerator 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.DomainModule
import eu.kanade.domain.SYDomainModule import eu.kanade.domain.SYDomainModule
import eu.kanade.domain.base.BasePreferences import eu.kanade.domain.base.BasePreferences
import eu.kanade.domain.sync.SyncPreferences import eu.kanade.domain.sync.SyncPreferences
import eu.kanade.domain.ui.UiPreferences import eu.kanade.domain.ui.UiPreferences
import eu.kanade.domain.ui.model.setAppCompatDelegateThemeMode 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.CrashActivity
import eu.kanade.tachiyomi.crash.GlobalExceptionHandler import eu.kanade.tachiyomi.crash.GlobalExceptionHandler
import eu.kanade.tachiyomi.data.coil.BufferedSourceFetcher 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.WebViewUtil
import eu.kanade.tachiyomi.util.system.animatorDurationScale import eu.kanade.tachiyomi.util.system.animatorDurationScale
import eu.kanade.tachiyomi.util.system.cancelNotification 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 eu.kanade.tachiyomi.util.system.notify
import exh.log.CrashlyticsPrinter import exh.log.CrashlyticsPrinter
import exh.log.EHLogLevel import exh.log.EHLogLevel
@ -74,6 +71,7 @@ import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import logcat.LogPriority import logcat.LogPriority
import logcat.LogcatLogger import logcat.LogcatLogger
import mihon.core.firebase.Firebase
import mihon.core.migration.Migrator import mihon.core.migration.Migrator
import mihon.core.migration.migrations.migrations import mihon.core.migration.migrations.migrations
import org.conscrypt.Conscrypt import org.conscrypt.Conscrypt
@ -94,6 +92,7 @@ import java.util.Locale
class App : Application(), DefaultLifecycleObserver, SingletonImageLoader.Factory { class App : Application(), DefaultLifecycleObserver, SingletonImageLoader.Factory {
private val basePreferences: BasePreferences by injectLazy() private val basePreferences: BasePreferences by injectLazy()
private val privacyPreferences: PrivacyPreferences by injectLazy()
private val networkPreferences: NetworkPreferences by injectLazy() private val networkPreferences: NetworkPreferences by injectLazy()
private val disableIncognitoReceiver = DisableIncognitoReceiver() private val disableIncognitoReceiver = DisableIncognitoReceiver()
@ -102,11 +101,6 @@ class App : Application(), DefaultLifecycleObserver, SingletonImageLoader.Factor
override fun onCreate() { override fun onCreate() {
super<Application>.onCreate() super<Application>.onCreate()
// SY -->
if (!isDevFlavor) {
Firebase.crashlytics.setCrashlyticsCollectionEnabled(isReleaseBuildType)
}
// SY <--
GlobalExceptionHandler.initialize(applicationContext, CrashActivity::class.java) GlobalExceptionHandler.initialize(applicationContext, CrashActivity::class.java)
// TLS 1.3 support for Android < 10 // TLS 1.3 support for Android < 10
@ -133,6 +127,8 @@ class App : Application(), DefaultLifecycleObserver, SingletonImageLoader.Factor
setupExhLogging() // EXH logging setupExhLogging() // EXH logging
LogcatLogger.install(XLogLogcatLogger()) // SY Redirect Logcat to XLog LogcatLogger.install(XLogLogcatLogger()) // SY Redirect Logcat to XLog
Firebase.setup(applicationContext, privacyPreferences, ProcessLifecycleOwner.get().lifecycleScope)
setupNotificationChannels() setupNotificationChannels()
ProcessLifecycleOwner.get().lifecycle.addObserver(this) ProcessLifecycleOwner.get().lifecycle.addObserver(this)

View File

@ -6,6 +6,7 @@ import eu.kanade.domain.source.service.SourcePreferences
import eu.kanade.domain.sync.SyncPreferences import eu.kanade.domain.sync.SyncPreferences
import eu.kanade.domain.track.service.TrackPreferences import eu.kanade.domain.track.service.TrackPreferences
import eu.kanade.domain.ui.UiPreferences import eu.kanade.domain.ui.UiPreferences
import eu.kanade.tachiyomi.core.security.PrivacyPreferences
import eu.kanade.tachiyomi.core.security.SecurityPreferences import eu.kanade.tachiyomi.core.security.SecurityPreferences
import eu.kanade.tachiyomi.network.NetworkPreferences import eu.kanade.tachiyomi.network.NetworkPreferences
import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences
@ -37,6 +38,9 @@ class PreferenceModule(val app: Application) : InjektModule {
addSingletonFactory { addSingletonFactory {
SecurityPreferences(get()) SecurityPreferences(get())
} }
addSingletonFactory {
PrivacyPreferences(get())
}
addSingletonFactory { addSingletonFactory {
LibraryPreferences(get()) LibraryPreferences(get())
} }

View File

@ -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)
}
}

View File

@ -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)
}

View File

@ -190,6 +190,10 @@
<string name="onboarding_permission_notifications_description">Get notified for library updates and more.</string> <string name="onboarding_permission_notifications_description">Get notified for library updates and more.</string>
<string name="onboarding_permission_ignore_battery_opts">Background battery usage</string> <string name="onboarding_permission_ignore_battery_opts">Background battery usage</string>
<string name="onboarding_permission_ignore_battery_opts_description">Avoid interruptions to long-running library updates, downloads, and backup restores.</string> <string name="onboarding_permission_ignore_battery_opts_description">Avoid interruptions to long-running library updates, downloads, and backup restores.</string>
<string name="onboarding_permission_crashlytics">Send crash logs</string>
<string name="onboarding_permission_crashlytics_description">Send anonymized crash logs to the developers.</string>
<string name="onboarding_permission_analytics">Allow analytics</string>
<string name="onboarding_permission_analytics_description">Send anonymized usage data to improve app features.</string>
<string name="onboarding_permission_action_grant">Grant</string> <string name="onboarding_permission_action_grant">Grant</string>
<string name="onboarding_guides_new_user">New to %s? We recommend checking out the getting started guide.</string> <string name="onboarding_guides_new_user">New to %s? We recommend checking out the getting started guide.</string>
<string name="onboarding_guides_returning_user">Reinstalling %s?</string> <string name="onboarding_guides_returning_user">Reinstalling %s?</string>
@ -242,6 +246,9 @@
<string name="pref_app_language">App language</string> <string name="pref_app_language">App language</string>
<string name="pref_category_security">Security and privacy</string> <string name="pref_category_security">Security and privacy</string>
<string name="pref_security">Security</string>
<string name="pref_firebase">Analytics and Crash logs</string>
<string name="lock_with_biometrics">Require unlock</string> <string name="lock_with_biometrics">Require unlock</string>
<string name="lock_when_idle">Lock when idle</string> <string name="lock_when_idle">Lock when idle</string>
<string name="lock_always">Always</string> <string name="lock_always">Always</string>
@ -249,6 +256,7 @@
<string name="hide_notification_content">Hide notification content</string> <string name="hide_notification_content">Hide notification content</string>
<string name="secure_screen">Secure screen</string> <string name="secure_screen">Secure screen</string>
<string name="secure_screen_summary">Secure screen hides app contents when switching apps and block screenshots</string> <string name="secure_screen_summary">Secure screen hides app contents when switching apps and block screenshots</string>
<string name="firebase_summary">Sending crash logs and analytics will allow us to identify and fix issues, improve performance, and make future updates more relevant to your needs</string>
<string name="pref_category_nsfw_content">NSFW (18+) sources</string> <string name="pref_category_nsfw_content">NSFW (18+) sources</string>
<string name="pref_show_nsfw_source">Show in sources and extensions lists</string> <string name="pref_show_nsfw_source">Show in sources and extensions lists</string>