refactor(analytics): consolidate consent logic, move to Settings (#2885)

Signed-off-by: James Rich <2199651+jamesarich@users.noreply.github.com>
This commit is contained in:
James Rich 2025-08-27 21:21:06 -05:00 committed by GitHub
parent 86ce659bc6
commit ad736116a7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 172 additions and 214 deletions

View file

@ -17,28 +17,23 @@
package com.geeksville.mesh.ui.settings.components
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.wrapContentWidth
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.rounded.KeyboardArrowRight
import androidx.compose.material.icons.rounded.Android
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.Icon
import androidx.compose.material3.ListItem
import androidx.compose.material3.ListItemDefaults
import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Switch
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
@ -67,6 +62,7 @@ fun SettingsItem(
}
/** A toggleable settings switch item. */
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
fun SettingsItemSwitch(
checked: Boolean,
@ -120,17 +116,14 @@ private fun ClickableWrapper(enabled: Boolean, onClick: () -> Unit, content: @Co
/** The row content to display for a settings item. */
@Composable
private fun Content(leading: @Composable () -> Unit, text: String, trailing: @Composable RowScope.() -> Unit) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(16.dp),
modifier = Modifier.fillMaxWidth().padding(vertical = 12.dp, horizontal = 16.dp),
) {
leading()
Text(text = text, style = MaterialTheme.typography.bodyLarge, modifier = Modifier.wrapContentWidth())
Spacer(modifier = Modifier.weight(1f))
trailing()
}
private fun Content(leading: @Composable () -> Unit, text: String, trailing: @Composable () -> Unit) {
ListItem(
modifier = Modifier.padding(horizontal = 8.dp),
colors = ListItemDefaults.colors(containerColor = Color.Transparent),
headlineContent = { Text(text) },
leadingContent = { leading() },
trailingContent = { trailing() },
)
}
@Composable

View file

@ -39,6 +39,7 @@ import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.twotone.KeyboardArrowRight
import androidx.compose.material.icons.filled.BugReport
import androidx.compose.material.icons.filled.Download
import androidx.compose.material.icons.filled.Upload
import androidx.compose.material.icons.twotone.Warning
@ -79,6 +80,7 @@ import com.geeksville.mesh.ui.common.components.TitledCard
import com.geeksville.mesh.ui.common.theme.AppTheme
import com.geeksville.mesh.ui.common.theme.StatusColors.StatusRed
import com.geeksville.mesh.ui.settings.components.SettingsItem
import com.geeksville.mesh.ui.settings.components.SettingsItemSwitch
import com.geeksville.mesh.ui.settings.radio.components.EditDeviceProfileDialog
import com.geeksville.mesh.ui.settings.radio.components.PacketResponseStateDialog
import kotlinx.coroutines.delay
@ -201,6 +203,7 @@ fun RadioConfigScreen(
deviceProfile = null
showEditDeviceProfileDialog = true
},
onToggleAnalytics = { viewModel.toggleAnalytics() },
onNavigate = onNavigate,
)
}
@ -286,6 +289,7 @@ private fun RadioConfigItemList(
onRouteClick: (Enum<*>) -> Unit = {},
onImport: () -> Unit = {},
onExport: () -> Unit = {},
onToggleAnalytics: () -> Unit = {},
onNavigate: (Route) -> Unit,
) {
val enabled = state.connected && !state.responseState.isWaiting() && !isManaged
@ -364,6 +368,18 @@ private fun RadioConfigItemList(
)
}
}
item {
if (state.analyticsAvailable) {
TitledCard(title = stringResource(R.string.phone_settings), modifier = Modifier.padding(top = 16.dp)) {
SettingsItemSwitch(
text = stringResource(R.string.analytics_okay),
checked = state.analyticsEnabled,
leadingIcon = Icons.Default.BugReport,
onClick = onToggleAnalytics,
)
}
}
}
}
}

View file

@ -43,7 +43,10 @@ import com.geeksville.mesh.ModuleConfigProtos
import com.geeksville.mesh.Portnums
import com.geeksville.mesh.Position
import com.geeksville.mesh.R
import com.geeksville.mesh.android.GeeksvilleApplication
import com.geeksville.mesh.android.Logging
import com.geeksville.mesh.android.isAnalyticsAvailable
import com.geeksville.mesh.android.prefs.AnalyticsPrefs
import com.geeksville.mesh.android.prefs.MapConsentPrefs
import com.geeksville.mesh.config
import com.geeksville.mesh.database.entity.MyNodeEntity
@ -92,6 +95,8 @@ data class RadioConfigState(
val ringtone: String = "",
val cannedMessageMessages: String = "",
val responseState: ResponseState<Boolean> = ResponseState.Empty,
val analyticsAvailable: Boolean = true,
val analyticsEnabled: Boolean = false,
)
@HiltViewModel
@ -103,6 +108,7 @@ constructor(
private val radioConfigRepository: RadioConfigRepository,
private val locationRepository: LocationRepository,
private val mapConsentPrefs: MapConsentPrefs,
private val analyticsPrefs: AnalyticsPrefs,
) : ViewModel(),
Logging {
private val meshService: IMeshService?
@ -159,6 +165,8 @@ constructor(
}
.launchIn(viewModelScope)
_radioConfigState.update { it.copy(analyticsAvailable = (app as GeeksvilleApplication).isAnalyticsAvailable) }
debug("RadioConfigViewModel created")
}
@ -695,4 +703,9 @@ constructor(
requestIds.update { it.apply { remove(data.requestId) } }
}
}
fun toggleAnalytics() {
analyticsPrefs.analyticsAllowed = !analyticsPrefs.analyticsAllowed
_radioConfigState.update { it.copy(analyticsEnabled = analyticsPrefs.analyticsAllowed) }
}
}