Skip to content
This repository was archived by the owner on Aug 21, 2025. It is now read-only.
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,16 @@ import com.automattic.android.tracks.crashlogging.ExtraKnownKey
import com.automattic.android.tracks.crashlogging.PerformanceMonitoringConfig
import com.automattic.android.tracks.crashlogging.ReleaseName
import com.gravatar.app.usercomponent.domain.repository.UserRepository
import com.gravatar.app.usercomponent.domain.usecase.GetPrivacySettings
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.runBlocking

internal class GravatarCrashLoggingDataProvider(
localeProvider: LocaleProvider,
userRepository: UserRepository
userRepository: UserRepository,
private val getPrivacySettings: GetPrivacySettings,
) : CrashLoggingDataProvider {

override val applicationContextProvider = emptyFlow<Map<String, String>>()
Expand Down Expand Up @@ -41,7 +45,7 @@ internal class GravatarCrashLoggingDataProvider(
)
}

override fun crashLoggingEnabled() = false
override fun crashLoggingEnabled() = runBlocking { getPrivacySettings().firstOrNull()?.crashReportingEnabled == true }

override fun extraKnownKeys() = emptyList<ExtraKnownKey>()

Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import com.gravatar.app.homeUi.ImageDownloader
import com.gravatar.app.homeUi.presentation.DrawableUtils
import com.gravatar.app.homeUi.presentation.FileUtils
import com.gravatar.app.homeUi.presentation.home.HomeViewModel
import com.gravatar.app.homeUi.presentation.home.components.privacySetting.PrivacySettingsViewModel
import com.gravatar.app.homeUi.presentation.home.components.topbar.TopBarPickerPopupViewModel
import com.gravatar.app.homeUi.presentation.home.components.topbar.components.about.AboutAppDialogViewModel
import com.gravatar.app.homeUi.presentation.home.gravatar.GravatarViewModel
Expand All @@ -25,6 +26,7 @@ val homeUiModule = module {
viewModelOf(::AboutAppDialogViewModel)
viewModelOf(::ShareViewModel)
viewModelOf(::HomeViewModel)
viewModelOf(::PrivacySettingsViewModel)

includes(userComponentModule)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.gravatar.app.homeUi.presentation.home.components.privacySetting

import com.gravatar.app.usercomponent.domain.model.PrivacySettings

internal data class PrivacySettingUiState(
val privacySettings: PrivacySettings = PrivacySettings(
analyticsEnabled = true,
crashReportingEnabled = true
)
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,224 @@
@file:OptIn(ExperimentalMaterial3Api::class)

package com.gravatar.app.homeUi.presentation.home.components.privacySetting

import android.content.Context
import android.content.Intent
import androidx.annotation.DrawableRes
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.WindowInsetsSides
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.only
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.safeContent
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Close
import androidx.compose.material3.CenterAlignedTopAppBar
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.ModalBottomSheet
import androidx.compose.material3.Surface
import androidx.compose.material3.Switch
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.rememberModalBottomSheetState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.core.net.toUri
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.gravatar.app.design.theme.GravatarAppTheme
import com.gravatar.app.homeUi.R
import org.koin.androidx.compose.koinViewModel

private const val PRIVACY_POLICY_URL = "https://automattic.com/privacy/"

@Composable
internal fun PrivacySettingsBottomSheet(
onDismissRequest: () -> Unit
) {
val topPadding = with(LocalDensity.current) {
WindowInsets.safeContent.only(WindowInsetsSides.Top).getTop(LocalDensity.current).toDp()
}
val viewModel: PrivacySettingsViewModel = koinViewModel()
val uiState by viewModel.uiState.collectAsStateWithLifecycle()

ModalBottomSheet(
onDismissRequest = onDismissRequest,
sheetState = rememberModalBottomSheetState(
skipPartiallyExpanded = true
),
containerColor = MaterialTheme.colorScheme.surfaceVariant,
modifier = Modifier.padding(top = topPadding),
dragHandle = {
Surface(
modifier = Modifier.padding(vertical = 8.dp),
color = MaterialTheme.colorScheme.onSurfaceVariant,
shape = MaterialTheme.shapes.extraLarge
) {
Box(Modifier.size(width = 32.dp, height = 4.dp))
}
}
) {
PrivacySettings(
uiState = uiState,
onEvent = viewModel::onEvent,
onDismissRequest = onDismissRequest,
)
}
}

@Composable
internal fun PrivacySettings(
uiState: PrivacySettingUiState,
onEvent: (PrivacySettingsEvent) -> Unit,
onDismissRequest: () -> Unit,
) {
val context = LocalContext.current
Column(
modifier = Modifier
.background(MaterialTheme.colorScheme.surfaceVariant)
.fillMaxWidth(),
) {
CenterAlignedTopAppBar(
title = {
Text(text = stringResource(R.string.privacy_settings_top_bar_title))
},
navigationIcon = {
IconButton(
onClick = onDismissRequest
) {
Icon(
imageVector = Icons.Default.Close,
contentDescription = stringResource(R.string.close_button)
)
}
},
colors = TopAppBarDefaults.topAppBarColors(
containerColor = MaterialTheme.colorScheme.surfaceVariant
),
windowInsets = WindowInsets(0.dp, 0.dp, 0.dp, 0.dp),
)
Column(
verticalArrangement = Arrangement.spacedBy(26.dp),
modifier = Modifier
.verticalScroll(rememberScrollState())
.padding(bottom = 16.dp, top = 8.dp, start = 16.dp, end = 16.dp)
) {
PrivacySettingsCard(
settingTitle = stringResource(R.string.privacy_settings_share_analytics_data_tittle),
settingIcon = R.drawable.ic_analytics_tracking,
settingDescription = stringResource(R.string.privacy_settings_share_analytics_data_description),
checked = uiState.privacySettings.analyticsEnabled,
onCheckedChange = { onEvent(PrivacySettingsEvent.OnAnalyticsEnabledChanged(it)) },
extraContent = {
Text(
text = stringResource(R.string.privacy_settings_privacy_policy),
color = MaterialTheme.colorScheme.primary,
modifier = Modifier
.padding(top = 16.dp)
.clickable {
context.openPrivacyPolicy()
}
)
},
modifier = Modifier.fillMaxWidth()
)
PrivacySettingsCard(
settingTitle = stringResource(R.string.privacy_settings_share_crash_reports_title),
settingIcon = R.drawable.ic_crashlytics,
settingDescription = stringResource(R.string.privacy_settings_share_crash_reports_description),
checked = uiState.privacySettings.crashReportingEnabled,
onCheckedChange = { onEvent(PrivacySettingsEvent.OnCrashReportingEnabledChanged(it)) },
modifier = Modifier.fillMaxWidth()
)
}
}
}

private fun Context.openPrivacyPolicy() {
val intent = Intent(Intent.ACTION_VIEW, PRIVACY_POLICY_URL.toUri())
startActivity(intent)
}

@Composable
private fun PrivacySettingsCard(
settingTitle: String,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⛏️

Suggested change
settingTitle: String,
@StringRes settingTitle: Int,
@DrawableRes settingIcon: Int,
@StringRes settingDescription: Int,

@DrawableRes settingIcon: Int,
settingDescription: String,
checked: Boolean,
onCheckedChange: (Boolean) -> Unit,
modifier: Modifier = Modifier,
extraContent: @Composable ColumnScope.() -> Unit = {},
) {
val shape = RoundedCornerShape(12.dp)
Column(
modifier = modifier
.background(MaterialTheme.colorScheme.surface, shape)
.padding(16.dp)
) {
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.fillMaxWidth()
) {
Icon(
painter = painterResource(settingIcon),
contentDescription = null,
tint = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.6f)
)
Spacer(modifier = Modifier.width(8.dp))
Text(
text = settingTitle,
style = MaterialTheme.typography.titleMedium.copy(fontWeight = FontWeight.SemiBold),
modifier = Modifier.weight(1f)
)
Switch(
checked = checked,
onCheckedChange = onCheckedChange,
)
}
HorizontalDivider(thickness = 1.dp)
Text(
text = settingDescription,
style = MaterialTheme.typography.bodyLarge,
modifier = Modifier.padding(top = 16.dp)
)
extraContent()
}
}

@Preview
@Composable
internal fun PrivacySettingsPreview() {
GravatarAppTheme {
PrivacySettings(
uiState = PrivacySettingUiState(),
onEvent = {},
onDismissRequest = {}
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.gravatar.app.homeUi.presentation.home.components.privacySetting

internal sealed class PrivacySettingsEvent {
data class OnAnalyticsEnabledChanged(val enabled: Boolean) : PrivacySettingsEvent()
data class OnCrashReportingEnabledChanged(val enabled: Boolean) : PrivacySettingsEvent()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package com.gravatar.app.homeUi.presentation.home.components.privacySetting

import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.gravatar.app.usercomponent.domain.model.PrivacySettings
import com.gravatar.app.usercomponent.domain.usecase.GetPrivacySettings
import com.gravatar.app.usercomponent.domain.usecase.UpdatePrivacySettings
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch

internal class PrivacySettingsViewModel(
private val getPrivacySettings: GetPrivacySettings,
private val updatePrivacySettings: UpdatePrivacySettings,
) : ViewModel() {

private val _uiState = MutableStateFlow(PrivacySettingUiState())
val uiState: StateFlow<PrivacySettingUiState> = _uiState.asStateFlow()

init {
collectPrivacySettings()
}

fun onEvent(event: PrivacySettingsEvent) {
when (event) {
is PrivacySettingsEvent.OnAnalyticsEnabledChanged -> {
val newSettings = _uiState.value.privacySettings.copy(analyticsEnabled = event.enabled)
updateSettings(newSettings)
}

is PrivacySettingsEvent.OnCrashReportingEnabledChanged -> {
val newSettings = _uiState.value.privacySettings.copy(crashReportingEnabled = event.enabled)
updateSettings(newSettings)
}
}
}

private fun collectPrivacySettings() {
getPrivacySettings()
.onEach { settings ->
_uiState.update { current ->
current.copy(privacySettings = settings)
}
}
.launchIn(viewModelScope)
}

private fun updateSettings(newSettings: PrivacySettings) {
_uiState.update { it.copy(privacySettings = newSettings) }
viewModelScope.launch {
updatePrivacySettings(newSettings)
}
}
}
Loading