refactor(auth): token store, Apollo auth, and email UI

Update ApiTokenStore, AuthServiceImpl, and interceptor; adjust login/register screens, dashboard, chart, and Firebase auth tests.

Made-with: Cursor
This commit is contained in:
2026-04-06 22:20:13 +02:00
parent d34b3bf70e
commit 9165d26b72
9 changed files with 140 additions and 26 deletions

View File

@@ -162,13 +162,14 @@ fun AirMQChart(
Canvas(modifier = Modifier.fillMaxSize()) {
val w = size.width
val h = size.height
val left = with(density) { ChartPaddingLeftDp.toPx() }
val right = w - with(density) { ChartPaddingRightDp.toPx() }
val innerLeft = with(density) { ChartPaddingLeftDp.toPx() }
val innerRight = w - with(density) { ChartPaddingRightDp.toPx() }
val innerWidth = innerRight - innerLeft
val rad = with(density) { 8.dp.toPx() }
drawRoundRect(
color = config.backgroundColor,
topLeft = Offset(0f, 0f),
size = Size(w, h),
topLeft = Offset(innerLeft, 0f),
size = Size(innerWidth, h),
cornerRadius = CornerRadius(rad, rad)
)
}

View File

@@ -13,12 +13,14 @@ import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import org.db3.airmq.R
import org.db3.airmq.features.common.chart.ChartConfig
import org.db3.airmq.features.common.chart.ChartDataset
import org.db3.airmq.features.common.metric.SensorType
import org.db3.airmq.sdk.auth.ApiTokenStore
import org.db3.airmq.sdk.city.CityService
import org.db3.airmq.sdk.city.DashboardCityContext
import org.db3.airmq.sdk.dashboard.DashboardMetricsRepository
@@ -34,6 +36,7 @@ import androidx.compose.ui.graphics.Color
class DashboardViewModel @Inject constructor(
@ApplicationContext private val context: Context,
private val cityService: CityService,
private val apiTokenStore: ApiTokenStore,
private val dashboardMetricsRepository: DashboardMetricsRepository,
) : ViewModel() {
@@ -47,10 +50,14 @@ class DashboardViewModel @Inject constructor(
init {
viewModelScope.launch {
cityService.observeDashboardCityContext().collectLatest { _ ->
val ctx = cityService.getResolvedDashboardCityContext()
loadDashboardData(ctx)
}
combine(
cityService.observeDashboardCityContext(),
apiTokenStore.observeToken(),
) { _, _ -> }
.collectLatest {
val ctx = cityService.getResolvedDashboardCityContext()
loadDashboardData(ctx)
}
}
}
@@ -88,6 +95,21 @@ class DashboardViewModel @Inject constructor(
}
private suspend fun loadDashboardData(ctx: DashboardCityContext) {
if (apiTokenStore.getToken().isNullOrBlank()) {
cachedAverageRows = emptyList()
val sensor = _uiState.value.selectedSensor
_uiState.update { state ->
state.copy(
city = ctx.displayName,
gaugeValues = SensorType.entries.associateWith { null },
chartData = DashboardChartMapper.chartDataset(emptyList(), sensor),
chartConfig = chartConfigFor(sensor),
chartSensorLabel = chartLabelFor(sensor),
)
}
return
}
val result = dashboardMetricsRepository.fetchCityDashboard(ctx)
val data = result.getOrNull()
cachedAverageRows = data?.averageRows.orEmpty()

View File

@@ -23,6 +23,7 @@ import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.autofill.ContentType
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
@@ -32,6 +33,8 @@ import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.input.PasswordVisualTransformation
import androidx.compose.ui.semantics.contentType
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
@@ -150,7 +153,11 @@ private fun EmailLoginScreenContent(
value = uiState.email,
onValueChange = { onEvent(Event.EmailChanged(it)) },
singleLine = true,
modifier = Modifier.fillMaxWidth(),
modifier = Modifier
.fillMaxWidth()
.semantics {
contentType = ContentType.Username + ContentType.EmailAddress
},
placeholder = {
Text(
stringResource(id = R.string.hint_email),
@@ -176,7 +183,9 @@ private fun EmailLoginScreenContent(
value = uiState.password,
onValueChange = { onEvent(Event.PasswordChanged(it)) },
singleLine = true,
modifier = Modifier.fillMaxWidth(),
modifier = Modifier
.fillMaxWidth()
.semantics { contentType = ContentType.Password },
placeholder = {
Text(
stringResource(id = R.string.hint_password),

View File

@@ -25,9 +25,11 @@ import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.autofill.ContentType
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalAutofillManager
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
@@ -35,6 +37,8 @@ import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.input.PasswordVisualTransformation
import androidx.compose.ui.semantics.contentType
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
@@ -60,11 +64,15 @@ fun EmailRegisterScreen(
) {
val uiState by viewModel.uiState.collectAsState()
val context = LocalContext.current
val autofillManager = LocalAutofillManager.current
LaunchedEffect(viewModel) {
viewModel.actions.collectLatest { action ->
when (action) {
Action.OpenManage -> onRegisterSuccess()
Action.OpenManage -> {
autofillManager?.commit()
onRegisterSuccess()
}
is Action.ShowMessage -> {
Toast.makeText(context, action.message, Toast.LENGTH_SHORT).show()
}
@@ -151,7 +159,9 @@ private fun EmailRegisterScreenContent(
onValueChange = { onEvent(Event.NameChanged(it)) },
singleLine = true,
enabled = fieldEnabled,
modifier = Modifier.fillMaxWidth(),
modifier = Modifier
.fillMaxWidth()
.semantics { contentType = ContentType.PersonFullName },
isError = uiState.nameError != null,
supportingText = uiState.nameError?.let { err ->
{ Text(err, color = Color.White.copy(alpha = 0.9f)) }
@@ -174,7 +184,11 @@ private fun EmailRegisterScreenContent(
onValueChange = { onEvent(Event.EmailChanged(it)) },
singleLine = true,
enabled = fieldEnabled,
modifier = Modifier.fillMaxWidth(),
modifier = Modifier
.fillMaxWidth()
.semantics {
contentType = ContentType.NewUsername + ContentType.EmailAddress
},
isError = uiState.emailError != null,
supportingText = uiState.emailError?.let { err ->
{ Text(err, color = Color.White.copy(alpha = 0.9f)) }
@@ -197,7 +211,9 @@ private fun EmailRegisterScreenContent(
onValueChange = { onEvent(Event.PasswordChanged(it)) },
singleLine = true,
enabled = fieldEnabled,
modifier = Modifier.fillMaxWidth(),
modifier = Modifier
.fillMaxWidth()
.semantics { contentType = ContentType.NewPassword },
isError = uiState.passwordError != null,
supportingText = uiState.passwordError?.let { err ->
{ Text(err, color = Color.White.copy(alpha = 0.9f)) }
@@ -221,7 +237,9 @@ private fun EmailRegisterScreenContent(
onValueChange = { onEvent(Event.PasswordConfirmChanged(it)) },
singleLine = true,
enabled = fieldEnabled,
modifier = Modifier.fillMaxWidth(),
modifier = Modifier
.fillMaxWidth()
.semantics { contentType = ContentType.NewPassword },
isError = uiState.passwordConfirmError != null,
supportingText = uiState.passwordConfirmError?.let { err ->
{ Text(err, color = Color.White.copy(alpha = 0.9f)) }