Add option to opt out of Analytics and Crashlytics (mihonapp/mihon#1237)
(cherry picked from commit 7c7af72f8cb12decc06b76c36852dcc54696236d)
This commit is contained in:
parent
1f748d6ec6
commit
8eb2582a88
10 changed files with 283 additions and 135 deletions
|
|
@ -290,9 +290,9 @@ dependencies {
|
|||
implementation(libs.logcat)
|
||||
|
||||
// Crash reports/analytics
|
||||
implementation(platform(libs.firebase.bom))
|
||||
implementation(libs.firebase.analytics)
|
||||
implementation(libs.firebase.crashlytics)
|
||||
"standardImplementation"(platform(libs.firebase.bom))
|
||||
"standardImplementation"(libs.firebase.analytics)
|
||||
"standardImplementation"(libs.firebase.crashlytics)
|
||||
|
||||
// Shizuku
|
||||
implementation(libs.bundles.shizuku)
|
||||
|
|
|
|||
9
app/src/dev/java/mihon/core/firebase/Firebase.kt
Normal file
9
app/src/dev/java/mihon/core/firebase/Firebase.kt
Normal 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
|
||||
}
|
||||
|
|
@ -14,11 +14,13 @@ import androidx.compose.foundation.layout.Column
|
|||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Check
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.ListItem
|
||||
import androidx.compose.material3.ListItemDefaults
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.OutlinedButton
|
||||
import androidx.compose.material3.Switch
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.DisposableEffect
|
||||
|
|
@ -34,14 +36,19 @@ import androidx.lifecycle.DefaultLifecycleObserver
|
|||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.lifecycle.compose.LocalLifecycleOwner
|
||||
import eu.kanade.presentation.util.rememberRequestPackageInstallsPermissionState
|
||||
import eu.kanade.tachiyomi.core.security.PrivacyPreferences
|
||||
import eu.kanade.tachiyomi.util.system.launchRequestPackageInstallsPermission
|
||||
import tachiyomi.i18n.MR
|
||||
import tachiyomi.i18n.kmk.KMR
|
||||
import tachiyomi.presentation.core.i18n.stringResource
|
||||
import tachiyomi.presentation.core.util.collectAsState
|
||||
import tachiyomi.presentation.core.util.secondaryItemAlpha
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
|
||||
internal class PermissionStep : OnboardingStep {
|
||||
|
||||
private val privacyPreferences: PrivacyPreferences by injectLazy()
|
||||
|
||||
private var notificationGranted by mutableStateOf(false)
|
||||
private var batteryGranted by mutableStateOf(false)
|
||||
|
||||
|
|
@ -90,7 +97,7 @@ internal class PermissionStep : OnboardingStep {
|
|||
}
|
||||
|
||||
Column {
|
||||
PermissionItem(
|
||||
PermissionCheckbox(
|
||||
title = stringResource(MR.strings.onboarding_permission_install_apps),
|
||||
subtitle = stringResource(MR.strings.onboarding_permission_install_apps_description),
|
||||
granted = installGranted,
|
||||
|
|
@ -106,7 +113,7 @@ internal class PermissionStep : OnboardingStep {
|
|||
// no-op. resulting checks is being done on resume
|
||||
},
|
||||
)
|
||||
PermissionItem(
|
||||
PermissionCheckbox(
|
||||
title = stringResource(MR.strings.onboarding_permission_notifications),
|
||||
subtitle = stringResource(MR.strings.onboarding_permission_notifications_description),
|
||||
granted = notificationGranted,
|
||||
|
|
@ -114,7 +121,7 @@ internal class PermissionStep : OnboardingStep {
|
|||
)
|
||||
}
|
||||
|
||||
PermissionItem(
|
||||
PermissionCheckbox(
|
||||
title = stringResource(MR.strings.onboarding_permission_ignore_battery_opts),
|
||||
subtitle = stringResource(MR.strings.onboarding_permission_ignore_battery_opts_description),
|
||||
granted = batteryGranted,
|
||||
|
|
@ -135,7 +142,7 @@ internal class PermissionStep : OnboardingStep {
|
|||
// no-op. resulting checks is being done on resume
|
||||
},
|
||||
)
|
||||
PermissionItem(
|
||||
PermissionCheckbox(
|
||||
title = stringResource(KMR.strings.onboarding_permission_external_storage),
|
||||
subtitle = stringResource(KMR.strings.onboarding_permission_external_storage_description),
|
||||
granted = externalStoragePermissionGranted,
|
||||
|
|
@ -148,7 +155,7 @@ internal class PermissionStep : OnboardingStep {
|
|||
// no-op. resulting checks is being done on resume
|
||||
},
|
||||
)
|
||||
PermissionItem(
|
||||
PermissionCheckbox(
|
||||
title = stringResource(KMR.strings.onboarding_permission_external_storage),
|
||||
subtitle = stringResource(KMR.strings.onboarding_permission_external_storage_description),
|
||||
granted = externalStoragePermissionGranted,
|
||||
|
|
@ -161,7 +168,7 @@ internal class PermissionStep : OnboardingStep {
|
|||
// no-op. resulting checks is being done on resume
|
||||
},
|
||||
)
|
||||
PermissionItem(
|
||||
PermissionCheckbox(
|
||||
title = stringResource(KMR.strings.onboarding_permission_external_storage),
|
||||
subtitle = stringResource(KMR.strings.onboarding_permission_writing_external_storage_description),
|
||||
granted = externalStoragePermissionGranted,
|
||||
|
|
@ -169,6 +176,30 @@ internal class PermissionStep : OnboardingStep {
|
|||
)
|
||||
}
|
||||
// KMK <--
|
||||
|
||||
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,
|
||||
)
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -187,7 +218,7 @@ internal class PermissionStep : OnboardingStep {
|
|||
}
|
||||
|
||||
@Composable
|
||||
private fun PermissionItem(
|
||||
private fun PermissionCheckbox(
|
||||
title: String,
|
||||
subtitle: String,
|
||||
granted: Boolean,
|
||||
|
|
@ -217,4 +248,26 @@ internal class PermissionStep : OnboardingStep {
|
|||
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),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -71,10 +72,20 @@ object SettingsSecurityScreen : SearchableSettings {
|
|||
|
||||
@Composable
|
||||
override fun getPreferences(): List<Preference> {
|
||||
val context = LocalContext.current
|
||||
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 useAuth by useAuthPref.collectAsState()
|
||||
|
||||
|
|
@ -82,7 +93,9 @@ object SettingsSecurityScreen : SearchableSettings {
|
|||
val isCbzPasswordSet by remember { CbzCrypto.isPasswordSetState(scope) }.collectAsState()
|
||||
val passwordProtectDownloads by securityPreferences.passwordProtectDownloads().collectAsState()
|
||||
|
||||
return listOf(
|
||||
return Preference.PreferenceGroup(
|
||||
title = stringResource(MR.strings.pref_security),
|
||||
preferenceItems = persistentListOf(
|
||||
Preference.PreferenceItem.SwitchPreference(
|
||||
pref = useAuthPref,
|
||||
title = stringResource(MR.strings.lock_with_biometrics),
|
||||
|
|
@ -112,6 +125,7 @@ object SettingsSecurityScreen : SearchableSettings {
|
|||
)
|
||||
},
|
||||
),
|
||||
|
||||
Preference.PreferenceItem.SwitchPreference(
|
||||
pref = securityPreferences.hideNotificationContent(),
|
||||
title = stringResource(MR.strings.hide_notification_content),
|
||||
|
|
@ -205,6 +219,31 @@ object SettingsSecurityScreen : SearchableSettings {
|
|||
},
|
||||
// SY <--
|
||||
Preference.PreferenceItem.InfoPreference(stringResource(MR.strings.secure_screen_summary)),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@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)),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -28,8 +28,6 @@ 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.Firebase
|
||||
import com.google.firebase.crashlytics.crashlytics
|
||||
import dev.mihon.injekt.patchInjekt
|
||||
import eu.kanade.domain.DomainModule
|
||||
import eu.kanade.domain.SYDomainModule
|
||||
|
|
@ -37,6 +35,7 @@ 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
|
||||
|
|
@ -59,9 +58,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.isPreviewBuildType
|
||||
import eu.kanade.tachiyomi.util.system.isReleaseBuildType
|
||||
import eu.kanade.tachiyomi.util.system.notify
|
||||
import exh.log.CrashlyticsPrinter
|
||||
import exh.log.EHLogLevel
|
||||
|
|
@ -73,6 +69,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 +91,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()
|
||||
|
|
@ -107,11 +105,6 @@ class App : Application(), DefaultLifecycleObserver, SingletonImageLoader.Factor
|
|||
if (BuildConfig.DEBUG) Timber.plant(Timber.DebugTree())
|
||||
// KMK <--
|
||||
|
||||
// SY -->
|
||||
if (!isDevFlavor) {
|
||||
Firebase.crashlytics.setCrashlyticsCollectionEnabled(isReleaseBuildType || isPreviewBuildType)
|
||||
}
|
||||
// SY <--
|
||||
GlobalExceptionHandler.initialize(applicationContext, CrashActivity::class.java)
|
||||
|
||||
// TLS 1.3 support for Android < 10
|
||||
|
|
@ -136,6 +129,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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -40,6 +41,9 @@ class PreferenceModule(val app: Application) : InjektModule {
|
|||
addSingletonFactory {
|
||||
SecurityPreferences(get())
|
||||
}
|
||||
addSingletonFactory {
|
||||
PrivacyPreferences(get())
|
||||
}
|
||||
addSingletonFactory {
|
||||
LibraryPreferences(get())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,6 +20,15 @@
|
|||
tools:node="remove" />
|
||||
|
||||
<application>
|
||||
<!-- 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 unnecessary stuff from Firebase -->
|
||||
<meta-data
|
||||
android:name="google_analytics_adid_collection_enabled"
|
||||
|
|
|
|||
20
app/src/standard/java/mihon/core/firebase/Firebase.kt
Normal file
20
app/src/standard/java/mihon/core/firebase/Firebase.kt
Normal 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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -191,6 +191,10 @@
|
|||
<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_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_guides_new_user">New to %s? We recommend checking out the getting started guide.</string>
|
||||
<string name="onboarding_guides_returning_user">Reinstalling %s?</string>
|
||||
|
|
@ -243,6 +247,9 @@
|
|||
<string name="pref_app_language">App language</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_when_idle">Lock when idle</string>
|
||||
<string name="lock_always">Always</string>
|
||||
|
|
@ -250,6 +257,7 @@
|
|||
<string name="hide_notification_content">Hide notification content</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="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_show_nsfw_source">Show in sources and extensions lists</string>
|
||||
|
|
|
|||
Loading…
Reference in a new issue