feat: integrate city selection into dashboard, nav and settings
This commit is contained in:
@@ -1,17 +1,26 @@
|
|||||||
package org.db3.airmq.features.dashboard
|
package org.db3.airmq.features.dashboard
|
||||||
|
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.fillMaxHeight
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.layout.width
|
||||||
|
import androidx.compose.foundation.Image
|
||||||
import androidx.compose.foundation.layout.statusBarsPadding
|
import androidx.compose.foundation.layout.statusBarsPadding
|
||||||
import androidx.compose.foundation.rememberScrollState
|
import androidx.compose.foundation.rememberScrollState
|
||||||
import androidx.compose.foundation.verticalScroll
|
import androidx.compose.foundation.verticalScroll
|
||||||
|
import androidx.compose.ui.layout.ContentScale
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
@@ -20,6 +29,7 @@ import androidx.compose.ui.graphics.Brush
|
|||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
@@ -34,8 +44,6 @@ import org.db3.airmq.ui.theme.LegacyNavGradientStart
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun DashboardScreen(
|
fun DashboardScreen(
|
||||||
onOpenMap: () -> Unit,
|
|
||||||
onOpenManage: () -> Unit,
|
|
||||||
onOpenCity: () -> Unit,
|
onOpenCity: () -> Unit,
|
||||||
onOpenDevice: () -> Unit,
|
onOpenDevice: () -> Unit,
|
||||||
onOpenNews: () -> Unit,
|
onOpenNews: () -> Unit,
|
||||||
@@ -43,6 +51,17 @@ fun DashboardScreen(
|
|||||||
viewModel: DashboardViewModel = hiltViewModel()
|
viewModel: DashboardViewModel = hiltViewModel()
|
||||||
) {
|
) {
|
||||||
val state by viewModel.uiState.collectAsState()
|
val state by viewModel.uiState.collectAsState()
|
||||||
|
|
||||||
|
LaunchedEffect(viewModel) {
|
||||||
|
viewModel.actions.collect { action ->
|
||||||
|
when (action) {
|
||||||
|
DashboardScreenContract.Action.OpenCity -> onOpenCity()
|
||||||
|
DashboardScreenContract.Action.OpenNews -> { /* handled elsewhere */ }
|
||||||
|
DashboardScreenContract.Action.OpenWidgetConstructor -> { /* handled elsewhere */ }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
DashboardContent(
|
DashboardContent(
|
||||||
state = state,
|
state = state,
|
||||||
onEvent = viewModel::onEvent
|
onEvent = viewModel::onEvent
|
||||||
@@ -69,7 +88,13 @@ internal fun DashboardContent(
|
|||||||
)
|
)
|
||||||
.statusBarsPadding()
|
.statusBarsPadding()
|
||||||
) {
|
) {
|
||||||
CitySelector(city = state.city, modifier = Modifier.padding(top = 12.dp))
|
CitySelector(
|
||||||
|
city = state.city,
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(top = 12.dp)
|
||||||
|
.padding(horizontal = 16.dp),
|
||||||
|
onClick = { onEvent(DashboardScreenContract.Event.CitySelectorClicked) }
|
||||||
|
)
|
||||||
MetricGaugePager(
|
MetricGaugePager(
|
||||||
selectedSensor = state.selectedSensor,
|
selectedSensor = state.selectedSensor,
|
||||||
values = state.gaugeValues,
|
values = state.gaugeValues,
|
||||||
@@ -100,20 +125,60 @@ internal fun DashboardContent(
|
|||||||
@Composable
|
@Composable
|
||||||
private fun CitySelector(
|
private fun CitySelector(
|
||||||
city: String,
|
city: String,
|
||||||
modifier: Modifier = Modifier
|
modifier: Modifier = Modifier,
|
||||||
|
onClick: () -> Unit = {}
|
||||||
) {
|
) {
|
||||||
Box(
|
Box(
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.height(44.dp),
|
.height(44.dp)
|
||||||
|
.padding(horizontal = 16.dp)
|
||||||
|
.clickable(onClick = onClick),
|
||||||
contentAlignment = Alignment.Center
|
contentAlignment = Alignment.Center
|
||||||
) {
|
) {
|
||||||
Text(
|
Row(
|
||||||
text = city,
|
modifier = Modifier
|
||||||
color = Color.White,
|
.height(44.dp)
|
||||||
fontSize = 22.sp,
|
.fillMaxWidth(),
|
||||||
fontWeight = FontWeight.Medium
|
verticalAlignment = Alignment.CenterVertically
|
||||||
)
|
) {
|
||||||
|
Image(
|
||||||
|
painter = painterResource(R.drawable.city_left),
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier
|
||||||
|
.width(22.dp)
|
||||||
|
.fillMaxHeight()
|
||||||
|
)
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.weight(1f)
|
||||||
|
.height(44.dp)
|
||||||
|
.padding(horizontal = 4.dp),
|
||||||
|
contentAlignment = Alignment.Center
|
||||||
|
) {
|
||||||
|
Image(
|
||||||
|
painter = painterResource(R.drawable.city_middle),
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
contentScale = ContentScale.FillBounds
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = city,
|
||||||
|
color = Color.White,
|
||||||
|
fontSize = 22.sp,
|
||||||
|
fontWeight = FontWeight.Medium,
|
||||||
|
maxLines = 1,
|
||||||
|
overflow = TextOverflow.Ellipsis
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Image(
|
||||||
|
painter = painterResource(R.drawable.city_right),
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier
|
||||||
|
.width(22.dp)
|
||||||
|
.fillMaxHeight()
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -80,6 +80,7 @@ object DashboardScreenContract {
|
|||||||
}
|
}
|
||||||
|
|
||||||
sealed interface Event {
|
sealed interface Event {
|
||||||
|
data object CitySelectorClicked : Event
|
||||||
data class GaugeSelected(val sensor: SensorType) : Event
|
data class GaugeSelected(val sensor: SensorType) : Event
|
||||||
data class PageChanged(val page: Int) : Event
|
data class PageChanged(val page: Int) : Event
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package org.db3.airmq.features.dashboard
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
@@ -10,8 +11,12 @@ import kotlinx.coroutines.flow.SharedFlow
|
|||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.flow.asSharedFlow
|
import kotlinx.coroutines.flow.asSharedFlow
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
|
import kotlinx.coroutines.flow.launchIn
|
||||||
|
import kotlinx.coroutines.flow.onEach
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
import org.db3.airmq.sdk.city.CityService
|
||||||
import org.db3.airmq.R
|
import org.db3.airmq.R
|
||||||
import org.db3.airmq.features.common.chart.ChartConfig
|
import org.db3.airmq.features.common.chart.ChartConfig
|
||||||
import org.db3.airmq.features.common.chart.ChartDataset
|
import org.db3.airmq.features.common.chart.ChartDataset
|
||||||
@@ -23,17 +28,27 @@ import androidx.compose.ui.graphics.Color
|
|||||||
|
|
||||||
@HiltViewModel
|
@HiltViewModel
|
||||||
class DashboardViewModel @Inject constructor(
|
class DashboardViewModel @Inject constructor(
|
||||||
@ApplicationContext private val context: Context
|
@ApplicationContext private val context: Context,
|
||||||
|
private val cityService: CityService
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
|
|
||||||
private val _uiState = MutableStateFlow(initialState())
|
private val _uiState = MutableStateFlow(initialState())
|
||||||
val uiState: StateFlow<DashboardScreenContract.State> = _uiState.asStateFlow()
|
val uiState: StateFlow<DashboardScreenContract.State> = _uiState.asStateFlow()
|
||||||
|
|
||||||
|
init {
|
||||||
|
viewModelScope.launch {
|
||||||
|
cityService.observeSelectedCity().collect { city ->
|
||||||
|
_uiState.update { it.copy(city = city) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private val _actions = MutableSharedFlow<DashboardScreenContract.Action>(extraBufferCapacity = 1)
|
private val _actions = MutableSharedFlow<DashboardScreenContract.Action>(extraBufferCapacity = 1)
|
||||||
val actions: SharedFlow<DashboardScreenContract.Action> = _actions.asSharedFlow()
|
val actions: SharedFlow<DashboardScreenContract.Action> = _actions.asSharedFlow()
|
||||||
|
|
||||||
fun onEvent(event: DashboardScreenContract.Event) {
|
fun onEvent(event: DashboardScreenContract.Event) {
|
||||||
when (event) {
|
when (event) {
|
||||||
|
DashboardScreenContract.Event.CitySelectorClicked -> _actions.tryEmit(DashboardScreenContract.Action.OpenCity)
|
||||||
is DashboardScreenContract.Event.GaugeSelected -> {
|
is DashboardScreenContract.Event.GaugeSelected -> {
|
||||||
_uiState.update { state ->
|
_uiState.update { state ->
|
||||||
state.copy(
|
state.copy(
|
||||||
|
|||||||
@@ -146,8 +146,6 @@ fun AirMQNavGraph(modifier: Modifier = Modifier) {
|
|||||||
}
|
}
|
||||||
composable(AirMqRoutes.DASHBOARD) {
|
composable(AirMqRoutes.DASHBOARD) {
|
||||||
DashboardScreen(
|
DashboardScreen(
|
||||||
onOpenMap = { navController.navigate(AirMqRoutes.MAP) },
|
|
||||||
onOpenManage = { navController.navigate(AirMqRoutes.MANAGE) },
|
|
||||||
onOpenCity = { navController.navigate(AirMqRoutes.CITY) },
|
onOpenCity = { navController.navigate(AirMqRoutes.CITY) },
|
||||||
onOpenDevice = { navController.navigate(AirMqRoutes.device()) },
|
onOpenDevice = { navController.navigate(AirMqRoutes.device()) },
|
||||||
onOpenNews = { navController.navigate(AirMqRoutes.NEWS) },
|
onOpenNews = { navController.navigate(AirMqRoutes.NEWS) },
|
||||||
@@ -181,13 +179,19 @@ fun AirMQNavGraph(modifier: Modifier = Modifier) {
|
|||||||
DeviceSettingsScreen(
|
DeviceSettingsScreen(
|
||||||
deviceId = backStackEntry.arguments?.getString("deviceId") ?: "mock-device-id",
|
deviceId = backStackEntry.arguments?.getString("deviceId") ?: "mock-device-id",
|
||||||
onOpenLocation = { navController.navigate(AirMqRoutes.LOCATION) },
|
onOpenLocation = { navController.navigate(AirMqRoutes.LOCATION) },
|
||||||
onShowOnMap = { navController.navigate(AirMqRoutes.MAP) },
|
onShowOnMap = {
|
||||||
|
navController.navigate(AirMqRoutes.MAP) {
|
||||||
|
popUpTo(AirMqRoutes.MANAGE) {
|
||||||
|
inclusive = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
onNavigateBack = { navController.popBackStack() }
|
onNavigateBack = { navController.popBackStack() }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
composable(AirMqRoutes.CITY) {
|
composable(AirMqRoutes.CITY) {
|
||||||
CityScreen(
|
CityScreen(
|
||||||
onBackToDashboard = { navController.navigate(AirMqRoutes.DASHBOARD) }
|
onNavigateBack = { navController.popBackStack() }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
composable(AirMqRoutes.SETTINGS) {
|
composable(AirMqRoutes.SETTINGS) {
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ fun SettingsScreen(
|
|||||||
viewModel.actions.collectLatest { action ->
|
viewModel.actions.collectLatest { action ->
|
||||||
when (action) {
|
when (action) {
|
||||||
Action.OpenDebug -> onOpenDebug()
|
Action.OpenDebug -> onOpenDebug()
|
||||||
|
Action.OpenCity -> onOpenCity()
|
||||||
Action.LogOutToManage -> onLogOutToManage()
|
Action.LogOutToManage -> onLogOutToManage()
|
||||||
is Action.ShowMessage -> Toast.makeText(context, action.message, Toast.LENGTH_SHORT).show()
|
is Action.ShowMessage -> Toast.makeText(context, action.message, Toast.LENGTH_SHORT).show()
|
||||||
}
|
}
|
||||||
@@ -178,7 +179,7 @@ private fun PreferenceRow(
|
|||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.heightIn(min = 44.dp)
|
.heightIn(min = 64.dp)
|
||||||
.clickable(onClick = onClick)
|
.clickable(onClick = onClick)
|
||||||
.padding(vertical = 2.dp),
|
.padding(vertical = 2.dp),
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
@@ -221,7 +222,7 @@ private fun PreferenceCheckRow(
|
|||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.heightIn(min = 44.dp)
|
.heightIn(min = 64.dp)
|
||||||
.clickable { onToggle(!checked) }
|
.clickable { onToggle(!checked) }
|
||||||
.padding(vertical = 2.dp),
|
.padding(vertical = 2.dp),
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ object SettingsScreenContract {
|
|||||||
|
|
||||||
sealed interface Action {
|
sealed interface Action {
|
||||||
data object OpenDebug : Action
|
data object OpenDebug : Action
|
||||||
|
data object OpenCity : Action
|
||||||
data object LogOutToManage : Action
|
data object LogOutToManage : Action
|
||||||
data class ShowMessage(val message: String) : Action
|
data class ShowMessage(val message: String) : Action
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,13 +19,15 @@ import org.db3.airmq.features.settings.SettingsScreenContract.Action
|
|||||||
import org.db3.airmq.features.settings.SettingsScreenContract.Event
|
import org.db3.airmq.features.settings.SettingsScreenContract.Event
|
||||||
import org.db3.airmq.features.settings.SettingsScreenContract.State
|
import org.db3.airmq.features.settings.SettingsScreenContract.State
|
||||||
import org.db3.airmq.sdk.auth.AuthService
|
import org.db3.airmq.sdk.auth.AuthService
|
||||||
|
import org.db3.airmq.sdk.city.CityService
|
||||||
import org.db3.airmq.sdk.settings.SettingsService
|
import org.db3.airmq.sdk.settings.SettingsService
|
||||||
|
|
||||||
@HiltViewModel
|
@HiltViewModel
|
||||||
class SettingsViewModel @Inject constructor(
|
class SettingsViewModel @Inject constructor(
|
||||||
@ApplicationContext private val appContext: Context,
|
@ApplicationContext private val appContext: Context,
|
||||||
private val settingsService: SettingsService,
|
private val settingsService: SettingsService,
|
||||||
private val authService: AuthService
|
private val authService: AuthService,
|
||||||
|
private val cityService: CityService
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
|
|
||||||
private val _uiState = MutableStateFlow(State())
|
private val _uiState = MutableStateFlow(State())
|
||||||
@@ -40,7 +42,7 @@ class SettingsViewModel @Inject constructor(
|
|||||||
|
|
||||||
fun onEvent(event: Event) {
|
fun onEvent(event: Event) {
|
||||||
when (event) {
|
when (event) {
|
||||||
Event.CityClicked -> _actions.tryEmit(Action.ShowMessage(appContext.getString(R.string.coming_soon)))
|
Event.CityClicked -> _actions.tryEmit(Action.OpenCity)
|
||||||
Event.AboutClicked -> _actions.tryEmit(Action.ShowMessage(appContext.getString(R.string.coming_soon)))
|
Event.AboutClicked -> _actions.tryEmit(Action.ShowMessage(appContext.getString(R.string.coming_soon)))
|
||||||
Event.DebugClicked -> _actions.tryEmit(Action.OpenDebug)
|
Event.DebugClicked -> _actions.tryEmit(Action.OpenDebug)
|
||||||
Event.LogOutClicked -> logOut()
|
Event.LogOutClicked -> logOut()
|
||||||
@@ -68,7 +70,7 @@ class SettingsViewModel @Inject constructor(
|
|||||||
private fun loadSettings() {
|
private fun loadSettings() {
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
val session = authService.getUser()
|
val session = authService.getUser()
|
||||||
val city = settingsService.getCity() ?: appContext.getString(R.string.city_minsk)
|
val city = cityService.getSelectedCity()
|
||||||
val deviceStatus = settingsService.getDeviceStatusNotificationsEnabled()
|
val deviceStatus = settingsService.getDeviceStatusNotificationsEnabled()
|
||||||
val offlineVisible = settingsService.getOfflineDevicesVisible()
|
val offlineVisible = settingsService.getOfflineDevicesVisible()
|
||||||
val advanced = settingsService.getAdvancedEnabled()
|
val advanced = settingsService.getAdvancedEnabled()
|
||||||
|
|||||||
@@ -190,6 +190,7 @@
|
|||||||
<string name="text_no_city_hint"><u>Няма вашага горада?</u></string>
|
<string name="text_no_city_hint"><u>Няма вашага горада?</u></string>
|
||||||
<string name="text_wrong_city_hint"><u>Няправільны горад?</u></string>
|
<string name="text_wrong_city_hint"><u>Няправільны горад?</u></string>
|
||||||
<string name="text_city_warning">Гэтая опцыя вызначае, якія дадзеныя будуць адлюстроўвацца ў дашбордзе. Калі горад не выбраны, будзе ўсталяваны стандартны варыянт.</string>
|
<string name="text_city_warning">Гэтая опцыя вызначае, якія дадзеныя будуць адлюстроўвацца ў дашбордзе. Калі горад не выбраны, будзе ўсталяваны стандартны варыянт.</string>
|
||||||
|
<string name="city_list_unavailable">Спіс гарадоў недаступны. Выкарыстоўваецца горад па змаўчанні.</string>
|
||||||
<string name="text_no_city">"Калі вы не бачыце ваш горад, значыць, у ім няма прылад "</string>
|
<string name="text_no_city">"Калі вы не бачыце ваш горад, значыць, у ім няма прылад "</string>
|
||||||
<string name="button_enter_manual">Увесці ўручную</string>
|
<string name="button_enter_manual">Увесці ўручную</string>
|
||||||
<string name="text_welcome">Вітаем вас у AirMQ</string>
|
<string name="text_welcome">Вітаем вас у AirMQ</string>
|
||||||
|
|||||||
@@ -190,6 +190,7 @@
|
|||||||
<string name="text_no_city_hint"><u>Нет вашего города?</u></string>
|
<string name="text_no_city_hint"><u>Нет вашего города?</u></string>
|
||||||
<string name="text_wrong_city_hint"><u>Неправильный город?</u></string>
|
<string name="text_wrong_city_hint"><u>Неправильный город?</u></string>
|
||||||
<string name="text_city_warning">Эта опция определяет, какие данные будут отображаться в дашборде. Если город не выбран, установится стандартный вариант.</string>
|
<string name="text_city_warning">Эта опция определяет, какие данные будут отображаться в дашборде. Если город не выбран, установится стандартный вариант.</string>
|
||||||
|
<string name="city_list_unavailable">Список городов недоступен. Используется город по умолчанию.</string>
|
||||||
<string name="text_no_city">"Если вы не видете ваш город, значит в нем нет устройств "</string>
|
<string name="text_no_city">"Если вы не видете ваш город, значит в нем нет устройств "</string>
|
||||||
<string name="button_enter_manual">Ввести вручную</string>
|
<string name="button_enter_manual">Ввести вручную</string>
|
||||||
<string name="text_welcome">Добро пожаловать в AirMQ</string>
|
<string name="text_welcome">Добро пожаловать в AirMQ</string>
|
||||||
|
|||||||
@@ -250,6 +250,7 @@
|
|||||||
<string name="text_wrong_city_hint"><u>Wrong city?</u></string>
|
<string name="text_wrong_city_hint"><u>Wrong city?</u></string>
|
||||||
<string name="text_no_city">If you don’t see your city, there are not enough sensors there to provide reliable data just yet. Instead, data from the closest available city will be displayed when automatic detection is enabled</string>
|
<string name="text_no_city">If you don’t see your city, there are not enough sensors there to provide reliable data just yet. Instead, data from the closest available city will be displayed when automatic detection is enabled</string>
|
||||||
<string name="text_city_warning">This setting affects what data is displayed in the dashboard. If no city is selected, default value will be set.</string>
|
<string name="text_city_warning">This setting affects what data is displayed in the dashboard. If no city is selected, default value will be set.</string>
|
||||||
|
<string name="city_list_unavailable">City list unavailable. Using default city.</string>
|
||||||
<string name="button_enter_manual">Enter manually</string>
|
<string name="button_enter_manual">Enter manually</string>
|
||||||
<string name="text_welcome">Welcome to AirMQ</string>
|
<string name="text_welcome">Welcome to AirMQ</string>
|
||||||
<string name="text_welcome_second">Learn what your city breathes</string>
|
<string name="text_welcome_second">Learn what your city breathes</string>
|
||||||
|
|||||||
Reference in New Issue
Block a user