Port manage/settings flows to Compose and wire settings persistence.
Add contract-driven state/action/event view models for manage and settings, migrate settings UI toward legacy preference rows (with anonymous stub behavior), and back SettingsServiceImpl with SharedPreferences for real toggle/city storage. Made-with: Cursor
This commit is contained in:
@@ -1,8 +1,47 @@
|
||||
package org.db3.airmq.features.manage
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.Card
|
||||
import androidx.compose.material3.CardDefaults
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import org.db3.airmq.features.common.MockScreenScaffold
|
||||
import org.db3.airmq.features.common.ScreenAction
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Brush
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import org.db3.airmq.features.common.AirMqButton
|
||||
import org.db3.airmq.features.common.AirMqButtonStyle
|
||||
import org.db3.airmq.features.manage.ManageScreenContract.Action
|
||||
import org.db3.airmq.features.manage.ManageScreenContract.DeviceItem
|
||||
import org.db3.airmq.features.manage.ManageScreenContract.Event
|
||||
import org.db3.airmq.features.manage.ManageScreenContract.State
|
||||
import org.db3.airmq.features.manage.ManageScreenContract.UserMode
|
||||
import org.db3.airmq.ui.theme.AirMQTheme
|
||||
import org.db3.airmq.ui.theme.LegacyNavGradientEnd
|
||||
import org.db3.airmq.ui.theme.LegacyNavGradientStart
|
||||
|
||||
@Composable
|
||||
fun ManageScreen(
|
||||
@@ -12,19 +51,276 @@ fun ManageScreen(
|
||||
onOpenLogin: () -> Unit,
|
||||
onOpenLocation: () -> Unit,
|
||||
onOpenWidgetConstructor: () -> Unit,
|
||||
onBackToDashboard: () -> Unit
|
||||
onBackToDashboard: () -> Unit,
|
||||
viewModel: ManageViewModel = hiltViewModel()
|
||||
) {
|
||||
MockScreenScaffold(
|
||||
title = "Manage",
|
||||
subtitle = "Bottom-tab equivalent: manage",
|
||||
actions = listOf(
|
||||
ScreenAction("Open Device", onOpenDevice),
|
||||
ScreenAction("Start Setup", onOpenSetup),
|
||||
ScreenAction("Open Settings", onOpenSettings),
|
||||
ScreenAction("Open Login", onOpenLogin),
|
||||
ScreenAction("Select Location", onOpenLocation),
|
||||
ScreenAction("Open Widget Constructor", onOpenWidgetConstructor),
|
||||
ScreenAction("Back to Dashboard", onBackToDashboard)
|
||||
)
|
||||
val uiState by viewModel.uiState.collectAsState()
|
||||
LaunchedEffect(viewModel) {
|
||||
viewModel.actions.collectLatest { action ->
|
||||
when (action) {
|
||||
Action.OpenLogin -> onOpenLogin()
|
||||
Action.OpenSettings -> onOpenSettings()
|
||||
Action.OpenSetup -> onOpenSetup()
|
||||
is Action.OpenDevice -> onOpenDevice()
|
||||
is Action.OpenLocation -> onOpenLocation()
|
||||
}
|
||||
}
|
||||
}
|
||||
ManageScreenContent(
|
||||
uiState = uiState,
|
||||
onEvent = viewModel::onEvent,
|
||||
onOpenWidgetConstructor = onOpenWidgetConstructor,
|
||||
onBackToDashboard = onBackToDashboard
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ManageScreenContent(
|
||||
uiState: State,
|
||||
onEvent: (Event) -> Unit,
|
||||
onOpenWidgetConstructor: () -> Unit,
|
||||
onBackToDashboard: () -> Unit
|
||||
) {
|
||||
Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(innerPadding)
|
||||
) {
|
||||
ProfileHeader(
|
||||
name = uiState.userName,
|
||||
email = uiState.userEmail,
|
||||
onSettingsClick = { onEvent(Event.SettingsClicked) }
|
||||
)
|
||||
when (uiState.userMode) {
|
||||
UserMode.ANONYMOUS -> AnonymousContent(
|
||||
devicesLabel = uiState.devicesLabel,
|
||||
onSignIn = { onEvent(Event.SignInClicked) }
|
||||
)
|
||||
UserMode.AUTHORIZED -> AuthorizedContent(
|
||||
devicesLabel = uiState.devicesLabel,
|
||||
devices = uiState.devices,
|
||||
onOpenSetup = { onEvent(Event.SetupClicked) },
|
||||
onOpenDevice = { onEvent(Event.DeviceClicked(it)) },
|
||||
onOpenLocation = { onEvent(Event.DeviceLocationClicked(it)) }
|
||||
)
|
||||
}
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
AirMqButton(
|
||||
text = "Open Widget Constructor",
|
||||
onClick = onOpenWidgetConstructor,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp)
|
||||
)
|
||||
AirMqButton(
|
||||
text = "Back to Dashboard",
|
||||
onClick = onBackToDashboard,
|
||||
style = AirMqButtonStyle.Outlined,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp, vertical = 8.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ProfileHeader(
|
||||
name: String,
|
||||
email: String,
|
||||
onSettingsClick: () -> Unit
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.background(
|
||||
brush = Brush.verticalGradient(
|
||||
colors = listOf(LegacyNavGradientEnd, LegacyNavGradientStart)
|
||||
)
|
||||
)
|
||||
.padding(16.dp)
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Text(
|
||||
text = "Manage",
|
||||
style = MaterialTheme.typography.headlineSmall,
|
||||
color = Color.White
|
||||
)
|
||||
AirMqButton(
|
||||
text = "Settings",
|
||||
onClick = onSettingsClick,
|
||||
style = AirMqButtonStyle.Text
|
||||
)
|
||||
}
|
||||
Spacer(modifier = Modifier.height(12.dp))
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.background(color = Color.White.copy(alpha = 0.25f), shape = CircleShape)
|
||||
.padding(horizontal = 14.dp, vertical = 10.dp)
|
||||
) {
|
||||
Text(
|
||||
text = name.firstOrNull()?.uppercase() ?: "A",
|
||||
color = Color.White,
|
||||
style = MaterialTheme.typography.titleMedium
|
||||
)
|
||||
}
|
||||
Spacer(modifier = Modifier.padding(horizontal = 8.dp))
|
||||
Column {
|
||||
Text(
|
||||
text = name,
|
||||
color = Color.White,
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
fontWeight = FontWeight.SemiBold
|
||||
)
|
||||
Text(
|
||||
text = email,
|
||||
color = Color.White.copy(alpha = 0.8f),
|
||||
style = MaterialTheme.typography.bodyMedium
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun AnonymousContent(
|
||||
devicesLabel: String,
|
||||
onSignIn: () -> Unit
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Text(
|
||||
text = devicesLabel,
|
||||
style = MaterialTheme.typography.titleMedium
|
||||
)
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
AirMqButton(
|
||||
text = "Sign in",
|
||||
onClick = onSignIn,
|
||||
style = AirMqButtonStyle.Gradient,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun AuthorizedContent(
|
||||
devicesLabel: String,
|
||||
devices: List<DeviceItem>,
|
||||
onOpenSetup: () -> Unit,
|
||||
onOpenDevice: (String) -> Unit,
|
||||
onOpenLocation: (String) -> Unit
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp)
|
||||
) {
|
||||
Text(text = devicesLabel, style = MaterialTheme.typography.titleMedium)
|
||||
Spacer(modifier = Modifier.height(12.dp))
|
||||
AirMqButton(
|
||||
text = "Setup",
|
||||
onClick = onOpenSetup,
|
||||
style = AirMqButtonStyle.Gradient,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
Spacer(modifier = Modifier.height(12.dp))
|
||||
LazyColumn(verticalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
items(devices, key = { it.id }) { device ->
|
||||
DeviceRow(
|
||||
item = device,
|
||||
onOpenDevice = { onOpenDevice(device.id) },
|
||||
onOpenLocation = { onOpenLocation(device.id) }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun DeviceRow(
|
||||
item: DeviceItem,
|
||||
onOpenDevice: () -> Unit,
|
||||
onOpenLocation: () -> Unit
|
||||
) {
|
||||
Card(
|
||||
shape = RoundedCornerShape(12.dp),
|
||||
colors = CardDefaults.cardColors(containerColor = Color.White),
|
||||
elevation = CardDefaults.cardElevation(defaultElevation = 2.dp)
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(12.dp)
|
||||
) {
|
||||
Text(text = item.name, style = MaterialTheme.typography.titleMedium)
|
||||
Text(
|
||||
text = item.status,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = Color.Gray
|
||||
)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
AirMqButton(
|
||||
text = "Open",
|
||||
onClick = onOpenDevice,
|
||||
style = AirMqButtonStyle.Contained,
|
||||
modifier = Modifier.weight(1f)
|
||||
)
|
||||
AirMqButton(
|
||||
text = if (item.hasLocation) "Show on map" else "Set location",
|
||||
onClick = onOpenLocation,
|
||||
style = AirMqButtonStyle.Outlined,
|
||||
modifier = Modifier.weight(1f)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun ManageScreenAnonymousPreview() {
|
||||
AirMQTheme {
|
||||
ManageScreenContent(
|
||||
uiState = State(
|
||||
userMode = UserMode.ANONYMOUS
|
||||
),
|
||||
onEvent = {},
|
||||
onOpenWidgetConstructor = {},
|
||||
onBackToDashboard = {}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun ManageScreenAuthorizedPreview() {
|
||||
AirMQTheme {
|
||||
ManageScreenContent(
|
||||
uiState = State(
|
||||
userMode = UserMode.AUTHORIZED,
|
||||
userName = "Anton Betsun",
|
||||
userEmail = "messbees@gmail.com",
|
||||
devicesLabel = "My devices",
|
||||
devices = listOf(
|
||||
DeviceItem("1", "AirMQ #1", "Online", true),
|
||||
DeviceItem("2", "AirMQ #2", "Offline", false)
|
||||
)
|
||||
),
|
||||
onEvent = {},
|
||||
onOpenWidgetConstructor = {},
|
||||
onBackToDashboard = {}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
package org.db3.airmq.features.manage
|
||||
|
||||
object ManageScreenContract {
|
||||
|
||||
enum class UserMode {
|
||||
ANONYMOUS,
|
||||
AUTHORIZED
|
||||
}
|
||||
|
||||
data class DeviceItem(
|
||||
val id: String,
|
||||
val name: String,
|
||||
val status: String,
|
||||
val hasLocation: Boolean
|
||||
)
|
||||
|
||||
data class State(
|
||||
val userMode: UserMode = UserMode.ANONYMOUS,
|
||||
val userName: String = "Anonymous",
|
||||
val userEmail: String = "Please sign in to access your devices.",
|
||||
val devicesLabel: String = "Sign in to see your devices",
|
||||
val devices: List<DeviceItem> = emptyList()
|
||||
)
|
||||
|
||||
sealed interface Action {
|
||||
data object OpenSettings : Action
|
||||
data object OpenSetup : Action
|
||||
data object OpenLogin : Action
|
||||
data class OpenDevice(val deviceId: String) : Action
|
||||
data class OpenLocation(val deviceId: String) : Action
|
||||
}
|
||||
|
||||
sealed interface Event {
|
||||
data object SettingsClicked : Event
|
||||
data object SetupClicked : Event
|
||||
data object SignInClicked : Event
|
||||
data class DeviceClicked(val deviceId: String) : Event
|
||||
data class DeviceLocationClicked(val deviceId: String) : Event
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
package org.db3.airmq.features.manage
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.SharedFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asSharedFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import org.db3.airmq.features.manage.ManageScreenContract.Action
|
||||
import org.db3.airmq.features.manage.ManageScreenContract.DeviceItem
|
||||
import org.db3.airmq.features.manage.ManageScreenContract.Event
|
||||
import org.db3.airmq.features.manage.ManageScreenContract.State
|
||||
import org.db3.airmq.features.manage.ManageScreenContract.UserMode
|
||||
|
||||
@HiltViewModel
|
||||
class ManageViewModel @Inject constructor() : ViewModel() {
|
||||
|
||||
// Temporary migration stub: keep screen in anonymous mode.
|
||||
private val forceAnonymous = true
|
||||
|
||||
private val _uiState = MutableStateFlow(initialState())
|
||||
val uiState: StateFlow<State> = _uiState.asStateFlow()
|
||||
|
||||
private val _actions = MutableSharedFlow<Action>(extraBufferCapacity = 1)
|
||||
val actions: SharedFlow<Action> = _actions.asSharedFlow()
|
||||
|
||||
fun onEvent(event: Event) {
|
||||
when (event) {
|
||||
Event.SettingsClicked -> _actions.tryEmit(Action.OpenSettings)
|
||||
Event.SetupClicked -> _actions.tryEmit(Action.OpenSetup)
|
||||
Event.SignInClicked -> _actions.tryEmit(Action.OpenLogin)
|
||||
is Event.DeviceClicked -> _actions.tryEmit(Action.OpenDevice(event.deviceId))
|
||||
is Event.DeviceLocationClicked -> _actions.tryEmit(Action.OpenLocation(event.deviceId))
|
||||
}
|
||||
}
|
||||
|
||||
private fun initialState(): State {
|
||||
val mode = if (forceAnonymous) {
|
||||
UserMode.ANONYMOUS
|
||||
} else {
|
||||
UserMode.AUTHORIZED
|
||||
}
|
||||
return when (mode) {
|
||||
UserMode.ANONYMOUS -> State(
|
||||
userMode = UserMode.ANONYMOUS
|
||||
)
|
||||
UserMode.AUTHORIZED -> State(
|
||||
userMode = UserMode.AUTHORIZED,
|
||||
userName = "Anton Betsun",
|
||||
userEmail = "messbees@gmail.com",
|
||||
devicesLabel = "My devices",
|
||||
devices = listOf(
|
||||
DeviceItem(
|
||||
id = "device-1",
|
||||
name = "AirMQ #42",
|
||||
status = "Online",
|
||||
hasLocation = true
|
||||
),
|
||||
DeviceItem(
|
||||
id = "device-2",
|
||||
name = "AirMQ #17",
|
||||
status = "Offline",
|
||||
hasLocation = false
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,22 +1,281 @@
|
||||
package org.db3.airmq.features.settings
|
||||
|
||||
import android.widget.Toast
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.heightIn
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material3.Checkbox
|
||||
import androidx.compose.material3.CheckboxDefaults
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import org.db3.airmq.features.common.MockScreenScaffold
|
||||
import org.db3.airmq.features.common.ScreenAction
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import org.db3.airmq.features.settings.SettingsScreenContract.Action
|
||||
import org.db3.airmq.features.settings.SettingsScreenContract.Event
|
||||
import org.db3.airmq.features.settings.SettingsScreenContract.State
|
||||
import org.db3.airmq.features.settings.SettingsScreenContract.UserMode
|
||||
import org.db3.airmq.ui.theme.AirMQTheme
|
||||
|
||||
@Composable
|
||||
fun SettingsScreen(
|
||||
onOpenDebug: () -> Unit,
|
||||
onOpenCity: () -> Unit,
|
||||
onLogOutToManage: () -> Unit
|
||||
onLogOutToManage: () -> Unit,
|
||||
viewModel: SettingsViewModel = hiltViewModel()
|
||||
) {
|
||||
MockScreenScaffold(
|
||||
title = "Settings",
|
||||
subtitle = "Settings and account actions.",
|
||||
actions = listOf(
|
||||
ScreenAction("Open Debug", onOpenDebug),
|
||||
ScreenAction("Open City", onOpenCity),
|
||||
ScreenAction("Log Out to Manage", onLogOutToManage)
|
||||
)
|
||||
val uiState by viewModel.uiState.collectAsState()
|
||||
val context = LocalContext.current
|
||||
|
||||
LaunchedEffect(viewModel) {
|
||||
viewModel.actions.collectLatest { action ->
|
||||
when (action) {
|
||||
Action.OpenDebug -> onOpenDebug()
|
||||
Action.LogOutToManage -> onLogOutToManage()
|
||||
is Action.ShowMessage -> Toast.makeText(context, action.message, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SettingsScreenContent(
|
||||
uiState = uiState,
|
||||
onEvent = viewModel::onEvent
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SettingsScreenContent(
|
||||
uiState: State,
|
||||
onEvent: (Event) -> Unit
|
||||
) {
|
||||
val screenHorizontalPadding = 16.dp
|
||||
val iconSize = 20.dp
|
||||
val iconTextGap = 12.dp
|
||||
val headerTextStart = iconSize + iconTextGap
|
||||
|
||||
Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
|
||||
if (uiState.isLoading) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(innerPadding),
|
||||
verticalArrangement = Arrangement.Center,
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
CircularProgressIndicator()
|
||||
}
|
||||
return@Scaffold
|
||||
}
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(innerPadding)
|
||||
.padding(horizontal = screenHorizontalPadding, vertical = 12.dp)
|
||||
.verticalScroll(rememberScrollState()),
|
||||
verticalArrangement = Arrangement.spacedBy(2.dp)
|
||||
) {
|
||||
Text(
|
||||
text = "Application",
|
||||
style = MaterialTheme.typography.labelLarge,
|
||||
color = Color(0xFF607D8B),
|
||||
modifier = Modifier.padding(start = headerTextStart, top = 4.dp, bottom = 4.dp)
|
||||
)
|
||||
PreferenceRow(
|
||||
iconName = "ic_pref_city",
|
||||
iconFallbackRes = android.R.drawable.ic_menu_myplaces,
|
||||
title = "City in dashboard",
|
||||
summary = uiState.city,
|
||||
iconSize = iconSize,
|
||||
iconTextGap = iconTextGap,
|
||||
onClick = { onEvent(Event.CityClicked) }
|
||||
)
|
||||
PreferenceCheckRow(
|
||||
iconName = "ic_pref_notifications",
|
||||
iconFallbackRes = android.R.drawable.ic_dialog_info,
|
||||
title = "Notify when device becomes offline",
|
||||
checked = uiState.deviceStatusNotificationsEnabled,
|
||||
iconSize = iconSize,
|
||||
iconTextGap = iconTextGap,
|
||||
onToggle = { onEvent(Event.DeviceStatusNotificationsChanged(it)) }
|
||||
)
|
||||
PreferenceCheckRow(
|
||||
iconName = "ic_pref_offline_devices",
|
||||
iconFallbackRes = android.R.drawable.ic_menu_mylocation,
|
||||
title = "Show offline devices",
|
||||
checked = uiState.offlineDevicesVisible,
|
||||
iconSize = iconSize,
|
||||
iconTextGap = iconTextGap,
|
||||
onToggle = { onEvent(Event.OfflineDevicesVisibilityChanged(it)) }
|
||||
)
|
||||
PreferenceRow(
|
||||
iconName = "ic_pref_info",
|
||||
iconFallbackRes = android.R.drawable.ic_dialog_info,
|
||||
title = "About",
|
||||
iconSize = iconSize,
|
||||
iconTextGap = iconTextGap,
|
||||
onClick = { onEvent(Event.AboutClicked) }
|
||||
)
|
||||
if (uiState.userMode == UserMode.AUTHORIZED) {
|
||||
PreferenceRow(
|
||||
iconName = "ic_pref_debug",
|
||||
iconFallbackRes = android.R.drawable.ic_lock_power_off,
|
||||
title = "Log out",
|
||||
iconSize = iconSize,
|
||||
iconTextGap = iconTextGap,
|
||||
onClick = { onEvent(Event.LogOutClicked) }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun PreferenceRow(
|
||||
iconName: String,
|
||||
iconFallbackRes: Int,
|
||||
title: String,
|
||||
summary: String? = null,
|
||||
iconSize: androidx.compose.ui.unit.Dp,
|
||||
iconTextGap: androidx.compose.ui.unit.Dp,
|
||||
onClick: () -> Unit
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val iconRes = remember(iconName, iconFallbackRes) {
|
||||
context.resources.getIdentifier(iconName, "drawable", context.packageName)
|
||||
.takeIf { it != 0 } ?: iconFallbackRes
|
||||
}
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.heightIn(min = 44.dp)
|
||||
.clickable(onClick = onClick)
|
||||
.padding(vertical = 2.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(iconTextGap)
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(id = iconRes),
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(iconSize),
|
||||
tint = Color(0x99333333)
|
||||
)
|
||||
Column {
|
||||
Text(text = title, style = MaterialTheme.typography.bodyLarge, color = Color(0xFF222222))
|
||||
if (summary != null) {
|
||||
Text(
|
||||
text = summary,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = Color(0xFF6B6B6B)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun PreferenceCheckRow(
|
||||
iconName: String,
|
||||
iconFallbackRes: Int,
|
||||
title: String,
|
||||
checked: Boolean,
|
||||
iconSize: androidx.compose.ui.unit.Dp,
|
||||
iconTextGap: androidx.compose.ui.unit.Dp,
|
||||
onToggle: (Boolean) -> Unit
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val iconRes = remember(iconName, iconFallbackRes) {
|
||||
context.resources.getIdentifier(iconName, "drawable", context.packageName)
|
||||
.takeIf { it != 0 } ?: iconFallbackRes
|
||||
}
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.heightIn(min = 44.dp)
|
||||
.clickable { onToggle(!checked) }
|
||||
.padding(vertical = 2.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(id = iconRes),
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(iconSize),
|
||||
tint = Color(0x99333333)
|
||||
)
|
||||
Spacer(modifier = Modifier.size(iconTextGap))
|
||||
Text(
|
||||
text = title,
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
color = Color(0xFF222222),
|
||||
modifier = Modifier.weight(1f)
|
||||
)
|
||||
Checkbox(
|
||||
checked = checked,
|
||||
onCheckedChange = onToggle,
|
||||
colors = CheckboxDefaults.colors(
|
||||
checkedColor = Color(0xFF2F6FA8),
|
||||
uncheckedColor = Color(0xFF8C8C8C),
|
||||
checkmarkColor = Color.White
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(name = "Settings Anonymous", showBackground = true, showSystemUi = true)
|
||||
@Composable
|
||||
private fun SettingsScreenAnonymousPreview() {
|
||||
AirMQTheme {
|
||||
SettingsScreenContent(
|
||||
uiState = State(
|
||||
userMode = UserMode.ANONYMOUS,
|
||||
city = "Minsk",
|
||||
deviceStatusNotificationsEnabled = true,
|
||||
offlineDevicesVisible = false,
|
||||
advancedEnabled = false,
|
||||
isLoading = false
|
||||
),
|
||||
onEvent = {}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(name = "Settings Advanced", showBackground = true, showSystemUi = true)
|
||||
@Composable
|
||||
private fun SettingsScreenAdvancedPreview() {
|
||||
AirMQTheme {
|
||||
SettingsScreenContent(
|
||||
uiState = State(
|
||||
userMode = UserMode.ANONYMOUS,
|
||||
city = "Minsk",
|
||||
deviceStatusNotificationsEnabled = true,
|
||||
offlineDevicesVisible = true,
|
||||
advancedEnabled = true,
|
||||
isLoading = false
|
||||
),
|
||||
onEvent = {}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
package org.db3.airmq.features.settings
|
||||
|
||||
object SettingsScreenContract {
|
||||
|
||||
enum class UserMode {
|
||||
ANONYMOUS,
|
||||
AUTHORIZED
|
||||
}
|
||||
|
||||
data class State(
|
||||
val userMode: UserMode = UserMode.ANONYMOUS,
|
||||
val city: String = "Minsk",
|
||||
val deviceStatusNotificationsEnabled: Boolean = true,
|
||||
val offlineDevicesVisible: Boolean = false,
|
||||
val advancedEnabled: Boolean = false,
|
||||
val isLoading: Boolean = true
|
||||
)
|
||||
|
||||
sealed interface Action {
|
||||
data object OpenDebug : Action
|
||||
data object LogOutToManage : Action
|
||||
data class ShowMessage(val message: String) : Action
|
||||
}
|
||||
|
||||
sealed interface Event {
|
||||
data object CityClicked : Event
|
||||
data object AboutClicked : Event
|
||||
data object DebugClicked : Event
|
||||
data object LogOutClicked : Event
|
||||
data class DeviceStatusNotificationsChanged(val enabled: Boolean) : Event
|
||||
data class OfflineDevicesVisibilityChanged(val enabled: Boolean) : Event
|
||||
data class AdvancedChanged(val enabled: Boolean) : Event
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
package org.db3.airmq.features.settings
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.SharedFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asSharedFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import org.db3.airmq.features.settings.SettingsScreenContract.Action
|
||||
import org.db3.airmq.features.settings.SettingsScreenContract.Event
|
||||
import org.db3.airmq.features.settings.SettingsScreenContract.State
|
||||
import org.db3.airmq.features.settings.SettingsScreenContract.UserMode
|
||||
import org.db3.airmq.sdk.settings.SettingsService
|
||||
|
||||
@HiltViewModel
|
||||
class SettingsViewModel @Inject constructor(
|
||||
private val settingsService: SettingsService
|
||||
) : ViewModel() {
|
||||
|
||||
// Temporary migration stub: keep screen in anonymous mode.
|
||||
private val forceAnonymous = true
|
||||
|
||||
private val _uiState = MutableStateFlow(State())
|
||||
val uiState: StateFlow<State> = _uiState.asStateFlow()
|
||||
|
||||
private val _actions = MutableSharedFlow<Action>(extraBufferCapacity = 1)
|
||||
val actions: SharedFlow<Action> = _actions.asSharedFlow()
|
||||
|
||||
init {
|
||||
loadSettings()
|
||||
}
|
||||
|
||||
fun onEvent(event: Event) {
|
||||
when (event) {
|
||||
Event.CityClicked -> _actions.tryEmit(Action.ShowMessage("Coming Soon"))
|
||||
Event.AboutClicked -> _actions.tryEmit(Action.ShowMessage("Coming Soon"))
|
||||
Event.DebugClicked -> _actions.tryEmit(Action.OpenDebug)
|
||||
Event.LogOutClicked -> _actions.tryEmit(Action.LogOutToManage)
|
||||
is Event.DeviceStatusNotificationsChanged -> {
|
||||
updateToggle(
|
||||
toggle = { settingsService.setDeviceStatusNotificationsEnabled(event.enabled) },
|
||||
updateState = { copy(deviceStatusNotificationsEnabled = event.enabled) }
|
||||
)
|
||||
}
|
||||
is Event.OfflineDevicesVisibilityChanged -> {
|
||||
updateToggle(
|
||||
toggle = { settingsService.setOfflineDevicesVisible(event.enabled) },
|
||||
updateState = { copy(offlineDevicesVisible = event.enabled) }
|
||||
)
|
||||
}
|
||||
is Event.AdvancedChanged -> {
|
||||
updateToggle(
|
||||
toggle = { settingsService.setAdvancedEnabled(event.enabled) },
|
||||
updateState = { copy(advancedEnabled = event.enabled) }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun loadSettings() {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
val city = settingsService.getCity() ?: "Minsk"
|
||||
val deviceStatus = settingsService.getDeviceStatusNotificationsEnabled()
|
||||
val offlineVisible = settingsService.getOfflineDevicesVisible()
|
||||
val advanced = settingsService.getAdvancedEnabled()
|
||||
_uiState.value = _uiState.value.copy(
|
||||
userMode = if (forceAnonymous) UserMode.ANONYMOUS else UserMode.AUTHORIZED,
|
||||
city = city,
|
||||
deviceStatusNotificationsEnabled = deviceStatus,
|
||||
offlineDevicesVisible = offlineVisible,
|
||||
advancedEnabled = advanced,
|
||||
isLoading = false
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateToggle(
|
||||
toggle: suspend () -> Result<Unit>,
|
||||
updateState: State.() -> State
|
||||
) {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
val previous = _uiState.value
|
||||
_uiState.value = previous.updateState()
|
||||
val result = toggle()
|
||||
if (result.isFailure) {
|
||||
_uiState.value = previous
|
||||
_actions.tryEmit(
|
||||
Action.ShowMessage(result.exceptionOrNull()?.message ?: "Failed to save settings")
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
11
app/src/main/res/drawable/ic_pref_advanced.xml
Normal file
11
app/src/main/res/drawable/ic_pref_advanced.xml
Normal file
@@ -0,0 +1,11 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24"
|
||||
android:tint="#333333"
|
||||
android:alpha="0.6">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M12.09,2.91C10.08,0.9 7.07,0.49 4.65,1.67L8.28,5.3c0.39,0.39 0.39,1.02 0,1.41L6.69,8.3c-0.39,0.4 -1.02,0.4 -1.41,0L1.65,4.67C0.48,7.1 0.89,10.09 2.9,12.1c1.86,1.86 4.58,2.35 6.89,1.48l7.96,7.96c1.03,1.03 2.69,1.03 3.71,0 1.03,-1.03 1.03,-2.69 0,-3.71L13.54,9.9c0.92,-2.34 0.44,-5.1 -1.45,-6.99z"/>
|
||||
</vector>
|
||||
11
app/src/main/res/drawable/ic_pref_city.xml
Normal file
11
app/src/main/res/drawable/ic_pref_city.xml
Normal file
@@ -0,0 +1,11 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:tint="#333333"
|
||||
android:alpha="0.6"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M15,11L15,5.83c0,-0.53 -0.21,-1.04 -0.59,-1.41L12.7,2.71c-0.39,-0.39 -1.02,-0.39 -1.41,0l-1.7,1.7C9.21,4.79 9,5.3 9,5.83L9,7L5,7c-1.1,0 -2,0.9 -2,2v10c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2v-6c0,-1.1 -0.9,-2 -2,-2h-4zM7,19L5,19v-2h2v2zM7,15L5,15v-2h2v2zM7,11L5,11L5,9h2v2zM13,19h-2v-2h2v2zM13,15h-2v-2h2v2zM13,11h-2L11,9h2v2zM13,7h-2L11,5h2v2zM19,19h-2v-2h2v2zM19,15h-2v-2h2v2z" />
|
||||
</vector>
|
||||
13
app/src/main/res/drawable/ic_pref_debug.xml
Normal file
13
app/src/main/res/drawable/ic_pref_debug.xml
Normal file
@@ -0,0 +1,13 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24"
|
||||
android:tint="#333333"
|
||||
android:alpha="0.6">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M19,8h-1.81c-0.45,-0.78 -1.07,-1.45 -1.82,-1.96l0.93,-0.93c0.39,-0.39 0.39,-1.02 0,-1.41 -0.39,-0.39 -1.02,-0.39 -1.41,0l-1.47,1.47C12.96,5.06 12.49,5 12,5s-0.96,0.06 -1.41,0.17L9.11,3.7c-0.39,-0.39 -1.02,-0.39 -1.41,0 -0.39,0.39 -0.39,1.02 0,1.41l0.92,0.93C7.88,6.55 7.26,7.22 6.81,8L5,8c-0.55,0 -1,0.45 -1,1s0.45,1 1,1h1.09c-0.05,0.33 -0.09,0.66 -0.09,1v1L5,12c-0.55,0 -1,0.45 -1,1s0.45,1 1,1h1v1c0,0.34 0.04,0.67 0.09,1L5,16c-0.55,0 -1,0.45 -1,1s0.45,1 1,1h1.81c1.04,1.79 2.97,3 5.19,3s4.15,-1.21 5.19,-3L19,18c0.55,0 1,-0.45 1,-1s-0.45,-1 -1,-1h-1.09c0.05,-0.33 0.09,-0.66 0.09,-1v-1h1c0.55,0 1,-0.45 1,-1s-0.45,-1 -1,-1h-1v-1c0,-0.34 -0.04,-0.67 -0.09,-1L19,10c0.55,0 1,-0.45 1,-1s-0.45,-1 -1,-1zM13,16h-2c-0.55,0 -1,-0.45 -1,-1s0.45,-1 1,-1h2c0.55,0 1,0.45 1,1s-0.45,1 -1,1zM13,12h-2c-0.55,0 -1,-0.45 -1,-1s0.45,-1 1,-1h2c0.55,0 1,0.45 1,1s-0.45,1 -1,1z"
|
||||
tools:ignore="VectorPath" />
|
||||
</vector>
|
||||
11
app/src/main/res/drawable/ic_pref_info.xml
Normal file
11
app/src/main/res/drawable/ic_pref_info.xml
Normal file
@@ -0,0 +1,11 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24"
|
||||
android:tint="#333333"
|
||||
android:alpha="0.6">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,17c-0.55,0 -1,-0.45 -1,-1v-4c0,-0.55 0.45,-1 1,-1s1,0.45 1,1v4c0,0.55 -0.45,1 -1,1zM13,9h-2L11,7h2v2z"/>
|
||||
</vector>
|
||||
11
app/src/main/res/drawable/ic_pref_notifications.xml
Normal file
11
app/src/main/res/drawable/ic_pref_notifications.xml
Normal file
@@ -0,0 +1,11 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:tint="#333333"
|
||||
android:alpha="0.6"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M12,22c1.1,0 2,-0.9 2,-2h-4c0,1.1 0.89,2 2,2zM18,16v-5c0,-3.07 -1.64,-5.64 -4.5,-6.32L13.5,4c0,-0.83 -0.67,-1.5 -1.5,-1.5s-1.5,0.67 -1.5,1.5v0.68C7.63,5.36 6,7.92 6,11v5l-1.29,1.29c-0.63,0.63 -0.19,1.71 0.7,1.71h13.17c0.89,0 1.34,-1.08 0.71,-1.71L18,16z" />
|
||||
</vector>
|
||||
14
app/src/main/res/drawable/ic_pref_offline_devices.xml
Normal file
14
app/src/main/res/drawable/ic_pref_offline_devices.xml
Normal file
@@ -0,0 +1,14 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24"
|
||||
android:tint="#333333"
|
||||
android:alpha="0.6">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M14,10V3.26C13.35,3.09 12.68,3 12,3c-4.2,0 -8,3.22 -8,8.2c0,3.18 2.45,6.92 7.34,11.23c0.38,0.33 0.95,0.33 1.33,0C17.55,18.12 20,14.38 20,11.2c0,-0.41 -0.04,-0.81 -0.09,-1.2H14zM12,13c-1.1,0 -2,-0.9 -2,-2c0,-1.1 0.9,-2 2,-2s2,0.9 2,2C14,12.1 13.1,13 12,13z"/>
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M22.54,2.88l-1.42,-1.42l-2.12,2.13l-2.12,-2.13l-1.42,1.42l2.13,2.12l-2.13,2.12l1.42,1.42l2.12,-2.13l2.12,2.13l1.42,-1.42l-2.13,-2.12z"/>
|
||||
</vector>
|
||||
@@ -1,25 +1,55 @@
|
||||
package org.db3.airmq.sdk.settings
|
||||
|
||||
import android.content.Context
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import javax.inject.Inject
|
||||
|
||||
class SettingsServiceImpl @Inject constructor() : SettingsService {
|
||||
override suspend fun getCity(): String? = throw NotImplementedError("getCity is not implemented yet")
|
||||
class SettingsServiceImpl @Inject constructor(
|
||||
@ApplicationContext context: Context
|
||||
) : SettingsService {
|
||||
private val sharedPreferences =
|
||||
context.getSharedPreferences(PREFERENCES_NAME, Context.MODE_PRIVATE)
|
||||
|
||||
override suspend fun setCity(city: String?): Result<Unit> = throw NotImplementedError("setCity is not implemented yet")
|
||||
override suspend fun getCity(): String? = sharedPreferences.getString(KEY_CITY, DEFAULT_CITY)
|
||||
|
||||
override suspend fun getOfflineDevicesVisible(): Boolean = false
|
||||
override suspend fun setCity(city: String?): Result<Unit> = runCatching {
|
||||
sharedPreferences.edit().putString(KEY_CITY, city ?: DEFAULT_CITY).apply()
|
||||
}
|
||||
|
||||
override suspend fun getOfflineDevicesVisible(): Boolean =
|
||||
sharedPreferences.getBoolean(KEY_OFFLINE_DEVICES, DEFAULT_OFFLINE_DEVICES)
|
||||
|
||||
override suspend fun setOfflineDevicesVisible(visible: Boolean): Result<Unit> =
|
||||
throw NotImplementedError("setOfflineDevicesVisible is not implemented yet")
|
||||
runCatching {
|
||||
sharedPreferences.edit().putBoolean(KEY_OFFLINE_DEVICES, visible).apply()
|
||||
}
|
||||
|
||||
override suspend fun getAdvancedEnabled(): Boolean = throw NotImplementedError("getAdvancedEnabled is not implemented yet")
|
||||
override suspend fun getAdvancedEnabled(): Boolean =
|
||||
sharedPreferences.getBoolean(KEY_SHOW_ADVANCED, DEFAULT_SHOW_ADVANCED)
|
||||
|
||||
override suspend fun setAdvancedEnabled(enabled: Boolean): Result<Unit> =
|
||||
throw NotImplementedError("setAdvancedEnabled is not implemented yet")
|
||||
runCatching {
|
||||
sharedPreferences.edit().putBoolean(KEY_SHOW_ADVANCED, enabled).apply()
|
||||
}
|
||||
|
||||
override suspend fun getDeviceStatusNotificationsEnabled(): Boolean =
|
||||
throw NotImplementedError("getDeviceStatusNotificationsEnabled is not implemented yet")
|
||||
sharedPreferences.getBoolean(KEY_DEVICE_STATUS_NOTIFICATIONS, DEFAULT_DEVICE_STATUS_NOTIFICATIONS)
|
||||
|
||||
override suspend fun setDeviceStatusNotificationsEnabled(enabled: Boolean): Result<Unit> =
|
||||
throw NotImplementedError("setDeviceStatusNotificationsEnabled is not implemented yet")
|
||||
runCatching {
|
||||
sharedPreferences.edit().putBoolean(KEY_DEVICE_STATUS_NOTIFICATIONS, enabled).apply()
|
||||
}
|
||||
|
||||
private companion object {
|
||||
private const val PREFERENCES_NAME = "airmq_settings"
|
||||
private const val KEY_CITY = "dashboard_city"
|
||||
private const val KEY_OFFLINE_DEVICES = "offline_devices"
|
||||
private const val KEY_SHOW_ADVANCED = "show_advanced"
|
||||
private const val KEY_DEVICE_STATUS_NOTIFICATIONS = "device_status"
|
||||
|
||||
private const val DEFAULT_CITY = "Minsk"
|
||||
private const val DEFAULT_OFFLINE_DEVICES = false
|
||||
private const val DEFAULT_SHOW_ADVANCED = false
|
||||
private const val DEFAULT_DEVICE_STATUS_NOTIFICATIONS = true
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user