feat(manage): rework Manage screen layout and device list

- Match device item design to reference (ic_chip, status chips, trailing icon)
- Add header (Your devices) and footer to device list
- CTA at bottom, centered 46% width, same style for Sign in/Add device
- Window background #FAFAFA, LegacyBackground on list area
- No spacing between device items, no horizontal margin on list
- Add DeviceItem.extra, ic_go_to_location drawable
- Replace personal data with User/user@example.com
- Strengthen app-recreation-core rule: path check before edits
- Restore real auth logic in ManageViewModel

Made-with: Cursor
This commit is contained in:
2026-03-02 21:51:50 +01:00
parent 9a80ce5dff
commit b607d0198b
15 changed files with 276 additions and 118 deletions

View File

@@ -5,13 +5,22 @@ alwaysApply: true
# AIRMQ Recreation Core Rules # AIRMQ Recreation Core Rules
## CRITICAL: Path check before any edit
**BEFORE editing any file, verify its path.**
- Path contains `airmq-android` but **NOT** `airmq-android-2026` → **NEVER EDIT.** Read-only reference.
- Path contains `airmq-android-2026` → OK to edit.
Example: `C:\Users\sysop\Desktop\airmq-android\app\...` → **DO NOT MODIFY.**
## Hard repository constraint ## Hard repository constraint
ALL CHANGES ONLY IN `airmq-android-2026` REPO; NO CHANGES EVER IN `airmq-android`. ALL CHANGES ONLY IN `airmq-android-2026` REPO; NO CHANGES EVER IN `airmq-android`.
## Repository boundaries ## Repository boundaries
1. Do not modify anything under `C:\Users\sysop\Desktop\airmq-android`. 1. **NEVER** modify anything under `C:\Users\sysop\Desktop\airmq-android`.
2. Treat `airmq-android` as read-only reference only. 2. Treat `airmq-android` as read-only reference only.
3. Create and apply all code/config/build changes only under `C:\Users\sysop\Desktop\airmq-android-2026`. 3. Create and apply all code/config/build changes only under `C:\Users\sysop\Desktop\airmq-android-2026`.

View File

@@ -2,6 +2,7 @@ package org.db3.airmq.features.manage
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
@@ -17,9 +18,6 @@ import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.CircleShape 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.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
@@ -38,6 +36,7 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
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.hilt.navigation.compose.hiltViewModel import androidx.hilt.navigation.compose.hiltViewModel
import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.collectLatest
import org.db3.airmq.R import org.db3.airmq.R
@@ -48,6 +47,7 @@ import org.db3.airmq.features.manage.ManageScreenContract.DeviceItem
import org.db3.airmq.features.manage.ManageScreenContract.Event import org.db3.airmq.features.manage.ManageScreenContract.Event
import org.db3.airmq.features.manage.ManageScreenContract.State import org.db3.airmq.features.manage.ManageScreenContract.State
import org.db3.airmq.ui.theme.AirMQTheme import org.db3.airmq.ui.theme.AirMQTheme
import org.db3.airmq.ui.theme.LegacyBackground
import org.db3.airmq.ui.theme.LegacyNavGradientEnd import org.db3.airmq.ui.theme.LegacyNavGradientEnd
import org.db3.airmq.ui.theme.LegacyNavGradientStart import org.db3.airmq.ui.theme.LegacyNavGradientStart
@@ -58,8 +58,7 @@ fun ManageScreen(
onOpenSettings: () -> Unit, onOpenSettings: () -> Unit,
onOpenLogin: () -> Unit, onOpenLogin: () -> Unit,
onOpenLocation: () -> Unit, onOpenLocation: () -> Unit,
onOpenWidgetConstructor: () -> Unit, onOpenAddLocation: () -> Unit,
onBackToDashboard: () -> Unit,
viewModel: ManageViewModel = hiltViewModel() viewModel: ManageViewModel = hiltViewModel()
) { ) {
val uiState by viewModel.uiState.collectAsState() val uiState by viewModel.uiState.collectAsState()
@@ -71,23 +70,20 @@ fun ManageScreen(
Action.OpenSetup -> onOpenSetup() Action.OpenSetup -> onOpenSetup()
is Action.OpenDevice -> onOpenDevice() is Action.OpenDevice -> onOpenDevice()
is Action.OpenLocation -> onOpenLocation() is Action.OpenLocation -> onOpenLocation()
is Action.OpenAddLocation -> onOpenAddLocation()
} }
} }
} }
ManageScreenContent( ManageScreenContent(
uiState = uiState, uiState = uiState,
onEvent = viewModel::onEvent, onEvent = viewModel::onEvent
onOpenWidgetConstructor = onOpenWidgetConstructor,
onBackToDashboard = onBackToDashboard
) )
} }
@Composable @Composable
private fun ManageScreenContent( private fun ManageScreenContent(
uiState: State, uiState: State,
onEvent: (Event) -> Unit, onEvent: (Event) -> Unit
onOpenWidgetConstructor: () -> Unit,
onBackToDashboard: () -> Unit
) { ) {
Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding -> Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
Column( Column(
@@ -104,33 +100,31 @@ private fun ManageScreenContent(
when (uiState.isAuthorized) { when (uiState.isAuthorized) {
false -> AnonymousContent( false -> AnonymousContent(
modifier = Modifier.weight(1f), modifier = Modifier.weight(1f),
devicesLabel = uiState.devicesLabel, devicesLabel = uiState.devicesLabel
onSignIn = { onEvent(Event.SignInClicked) }
) )
true -> AuthorizedContent( true -> AuthorizedContent(
devicesLabel = uiState.devicesLabel, modifier = Modifier.weight(1f),
devices = uiState.devices, devices = uiState.devices,
onOpenSetup = { onEvent(Event.SetupClicked) },
onOpenDevice = { onEvent(Event.DeviceClicked(it)) }, onOpenDevice = { onEvent(Event.DeviceClicked(it)) },
onOpenLocation = { onEvent(Event.DeviceLocationClicked(it)) } onOpenLocation = { onEvent(Event.DeviceLocationClicked(it)) },
onAddLocation = { onEvent.invoke(Event.AddDeviceLocationClicked(it))}
) )
} }
if (uiState.isAuthorized) {
Spacer(modifier = Modifier.height(8.dp)) Spacer(modifier = Modifier.height(8.dp))
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.Center
) {
AirMQButton( AirMQButton(
text = stringResource(id = R.string.manage_open_widget_constructor), text = if (uiState.isAuthorized) stringResource(id = R.string.button_setup)
onClick = onOpenWidgetConstructor, else stringResource(id = R.string.button_sign_in),
onClick = if (uiState.isAuthorized) { { onEvent(Event.SetupClicked) } }
else { { onEvent(Event.SignInClicked) } },
style = AirMQButtonStyle.Gradient,
leadingIconRes = if (uiState.isAuthorized) null else R.drawable.ic_account,
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth(0.46f)
.padding(horizontal = 16.dp) .padding(bottom = 16.dp)
)
AirMQButton(
text = stringResource(id = R.string.back_to_dashboard),
onClick = onBackToDashboard,
style = AirMQButtonStyle.Outlined,
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp, vertical = 8.dp)
) )
} }
} }
@@ -222,8 +216,7 @@ private fun ProfileHeader(
@Composable @Composable
private fun AnonymousContent( private fun AnonymousContent(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
devicesLabel: String, devicesLabel: String
onSignIn: () -> Unit
) { ) {
Box( Box(
modifier = modifier modifier = modifier
@@ -236,94 +229,143 @@ private fun AnonymousContent(
color = Color(0xFFBDBDBD), color = Color(0xFFBDBDBD),
modifier = Modifier.align(Alignment.Center) modifier = Modifier.align(Alignment.Center)
) )
AirMQButton(
text = stringResource(id = R.string.button_sign_in),
onClick = onSignIn,
style = AirMQButtonStyle.Gradient,
leadingIconRes = R.drawable.ic_account,
modifier = Modifier
.align(Alignment.BottomCenter)
.fillMaxWidth(0.46f)
.padding(bottom = 16.dp)
)
} }
} }
@Composable @Composable
private fun AuthorizedContent( private fun AuthorizedContent(
devicesLabel: String, modifier: Modifier = Modifier,
devices: List<DeviceItem>, devices: List<DeviceItem>,
onOpenSetup: () -> Unit,
onOpenDevice: (String) -> Unit, onOpenDevice: (String) -> Unit,
onOpenLocation: (String) -> Unit onOpenLocation: (String) -> Unit,
onAddLocation: (String) -> Unit
) { ) {
Column( Column(
modifier = modifier
.fillMaxWidth()
.background(LegacyBackground)
) {
if (devices.isEmpty()) {
Box(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.padding(16.dp) .weight(1f)
.padding(horizontal = 16.dp, vertical = 8.dp)
) { ) {
Text(text = devicesLabel, style = MaterialTheme.typography.titleMedium) Text(
Spacer(modifier = Modifier.height(12.dp)) text = stringResource(id = R.string.text_nothing_to_show),
AirMQButton( style = MaterialTheme.typography.headlineSmall.copy(fontWeight = FontWeight.Light),
text = stringResource(id = R.string.button_setup), color = Color(0xFF757575),
onClick = onOpenSetup, modifier = Modifier.align(Alignment.Center)
style = AirMQButtonStyle.Gradient,
modifier = Modifier.fillMaxWidth()
) )
Spacer(modifier = Modifier.height(12.dp)) }
LazyColumn(verticalArrangement = Arrangement.spacedBy(8.dp)) { } else {
LazyColumn(
modifier = Modifier
.fillMaxWidth()
.weight(1f)
.padding(top = 8.dp),
verticalArrangement = Arrangement.Top
) {
item(key = "header") {
DeviceListHeader()
}
items(devices, key = { it.id }) { device -> items(devices, key = { it.id }) { device ->
DeviceRow( DeviceRow(
item = device, item = device,
onOpenDevice = { onOpenDevice(device.id) }, onOpenDevice = { onOpenDevice(device.id) },
onOpenLocation = { onOpenLocation(device.id) } onOpenLocation = { onOpenLocation(device.id) },
onAddLocation = { onAddLocation(device.id) }
) )
} }
item(key = "footer") {
DeviceListFooter()
} }
} }
}
}
}
private val Black38 = Color(0x61000000)
@Composable
private fun DeviceListHeader() {
Text(
text = stringResource(id = R.string.text_your_devices),
modifier = Modifier
.fillMaxWidth()
.padding(start = 16.dp, top = 22.dp),
fontSize = 14.sp,
color = Black38,
fontWeight = FontWeight.Medium
)
}
@Composable
private fun DeviceListFooter() {
Spacer(modifier = Modifier.height(72.dp))
} }
@Composable @Composable
private fun DeviceRow( private fun DeviceRow(
item: DeviceItem, item: DeviceItem,
onOpenDevice: () -> Unit, onOpenDevice: () -> Unit,
onOpenLocation: () -> Unit onOpenLocation: () -> Unit,
onAddLocation: () -> Unit
) { ) {
Card( val isOnline = item.status.equals(stringResource(id = R.string.map_status_online), ignoreCase = true)
shape = RoundedCornerShape(12.dp), Row(
colors = CardDefaults.cardColors(containerColor = Color.White),
elevation = CardDefaults.cardElevation(defaultElevation = 2.dp)
) {
Column(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.padding(12.dp) .height(72.dp)
.clickable(onClick = onOpenDevice)
.padding(horizontal = 16.dp),
verticalAlignment = Alignment.CenterVertically
) { ) {
Text(text = item.name, style = MaterialTheme.typography.titleMedium) Image(
painter = painterResource(id = R.drawable.ic_chip),
contentDescription = stringResource(id = R.string.content_device_icon),
modifier = Modifier.size(40.dp)
)
Spacer(modifier = Modifier.width(16.dp))
Column(modifier = Modifier.weight(1f, fill = true)) {
Text( Text(
text = item.status, text = item.name,
style = MaterialTheme.typography.bodyMedium, fontSize = 16.sp,
color = Color.Gray color = Color.Black
) )
Spacer(modifier = Modifier.height(8.dp)) Text(
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { text = item.extra,
AirMQButton( fontSize = 14.sp,
text = stringResource(id = R.string.button_open), color = Black38
onClick = onOpenDevice,
style = AirMQButtonStyle.Contained,
modifier = Modifier.weight(1f)
)
AirMQButton(
text = if (item.hasLocation) {
stringResource(id = R.string.button_view_on_map)
} else {
stringResource(id = R.string.manage_set_location)
},
onClick = onOpenLocation,
style = AirMQButtonStyle.Outlined,
modifier = Modifier.weight(1f)
) )
} }
Spacer(modifier = Modifier.width(16.dp))
Image(
painter = painterResource(
id = if (isOnline) R.drawable.device_chip_online else R.drawable.device_chip_offline
),
contentDescription = item.status,
modifier = Modifier
.align(Alignment.Top)
.padding(top = 18.dp)
)
val trailingIcon = if (item.hasLocation) R.drawable.ic_go_to_location else R.drawable.ic_warning
IconButton(
onClick = {
if (item.hasLocation) {
onOpenLocation.invoke()
} else {
onAddLocation.invoke()
}
},
modifier = Modifier.size(48.dp)
) {
Icon(
painter = painterResource(id = trailingIcon),
contentDescription = null,
tint = Black38
)
} }
} }
} }
@@ -339,9 +381,7 @@ private fun ManageScreenAnonymousPreview() {
userEmail = "Your preferences are not being synced, please sign in", userEmail = "Your preferences are not being synced, please sign in",
devicesLabel = "Sign in to add devices" devicesLabel = "Sign in to add devices"
), ),
onEvent = {}, onEvent = {}
onOpenWidgetConstructor = {},
onBackToDashboard = {}
) )
} }
} }
@@ -353,17 +393,14 @@ private fun ManageScreenAuthorizedPreview() {
ManageScreenContent( ManageScreenContent(
uiState = State( uiState = State(
isAuthorized = true, isAuthorized = true,
userName = "Anton Betsun", userName = "User",
userEmail = "messbees@gmail.com", userEmail = "user@example.com",
devicesLabel = "My devices",
devices = listOf( devices = listOf(
DeviceItem("1", "AirMQ #1", "Online", true), DeviceItem("1", "AirMQ #1", "mobile", "Online", true),
DeviceItem("2", "AirMQ #2", "Offline", false) DeviceItem("2", "AirMQ #2", "mobile", "Offline", false)
) )
), ),
onEvent = {}, onEvent = {}
onOpenWidgetConstructor = {},
onBackToDashboard = {}
) )
} }
} }

View File

@@ -4,6 +4,7 @@ object ManageScreenContract {
data class DeviceItem( data class DeviceItem(
val id: String, val id: String,
val name: String, val name: String,
val extra: String,
val status: String, val status: String,
val hasLocation: Boolean val hasLocation: Boolean
) )
@@ -22,6 +23,7 @@ object ManageScreenContract {
data object OpenLogin : Action data object OpenLogin : Action
data class OpenDevice(val deviceId: String) : Action data class OpenDevice(val deviceId: String) : Action
data class OpenLocation(val deviceId: String) : Action data class OpenLocation(val deviceId: String) : Action
data class OpenAddLocation(val deviceId: String) : Action
} }
sealed interface Event { sealed interface Event {
@@ -30,5 +32,6 @@ object ManageScreenContract {
data object SignInClicked : Event data object SignInClicked : Event
data class DeviceClicked(val deviceId: String) : Event data class DeviceClicked(val deviceId: String) : Event
data class DeviceLocationClicked(val deviceId: String) : Event data class DeviceLocationClicked(val deviceId: String) : Event
data class AddDeviceLocationClicked(val deviceId: String) : Event
} }
} }

View File

@@ -44,6 +44,7 @@ class ManageViewModel @Inject constructor(
Event.SignInClicked -> _actions.tryEmit(Action.OpenLogin) Event.SignInClicked -> _actions.tryEmit(Action.OpenLogin)
is Event.DeviceClicked -> _actions.tryEmit(Action.OpenDevice(event.deviceId)) is Event.DeviceClicked -> _actions.tryEmit(Action.OpenDevice(event.deviceId))
is Event.DeviceLocationClicked -> _actions.tryEmit(Action.OpenLocation(event.deviceId)) is Event.DeviceLocationClicked -> _actions.tryEmit(Action.OpenLocation(event.deviceId))
is Event.AddDeviceLocationClicked -> _actions.tryEmit(Action.OpenAddLocation(event.deviceId))
} }
} }
@@ -71,17 +72,19 @@ class ManageViewModel @Inject constructor(
isAuthorized = true, isAuthorized = true,
userName = user.displayName ?: appContext.getString(R.string.text_anonymous_user), userName = user.displayName ?: appContext.getString(R.string.text_anonymous_user),
userEmail = user.email ?: "", userEmail = user.email ?: "",
devicesLabel = appContext.getString(R.string.text_your_devices), devicesLabel = "",
devices = listOf( devices = listOf(
DeviceItem( DeviceItem(
id = "device-1", id = "device-1",
name = appContext.getString(R.string.mock_device_name_42), name = appContext.getString(R.string.mock_device_name_42),
extra = "mobile",
status = appContext.getString(R.string.map_status_online), status = appContext.getString(R.string.map_status_online),
hasLocation = true hasLocation = true
), ),
DeviceItem( DeviceItem(
id = "device-2", id = "device-2",
name = appContext.getString(R.string.mock_device_name_17), name = appContext.getString(R.string.mock_device_name_17),
extra = "mobile",
status = appContext.getString(R.string.map_status_offline), status = appContext.getString(R.string.map_status_offline),
hasLocation = false hasLocation = false
) )

View File

@@ -152,8 +152,7 @@ fun AirMQNavGraph(modifier: Modifier = Modifier) {
onOpenSettings = { navController.navigate(AirMqRoutes.SETTINGS) }, onOpenSettings = { navController.navigate(AirMqRoutes.SETTINGS) },
onOpenLogin = { navController.navigate(AirMqRoutes.LOGIN) }, onOpenLogin = { navController.navigate(AirMqRoutes.LOGIN) },
onOpenLocation = { navController.navigate(AirMqRoutes.LOCATION) }, onOpenLocation = { navController.navigate(AirMqRoutes.LOCATION) },
onOpenWidgetConstructor = { navController.navigate(AirMqRoutes.WIDGET_CONSTRUCTOR) }, onOpenAddLocation = { /* TODO */ }
onBackToDashboard = { navController.navigate(AirMqRoutes.DASHBOARD) }
) )
} }
composable( composable(

View File

@@ -0,0 +1,30 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="52dp"
android:height="18dp"
android:viewportWidth="52"
android:viewportHeight="18">
<path
android:pathData="M43,18H9c-4.97,0 -9,-4.03 -9,-9v0c0,-4.97 4.03,-9 9,-9l34,0c4.95,0 9,4.05 9,9v0C52,13.97 47.97,18 43,18z"
android:fillColor="#F1AF00"/>
<path
android:pathData="M10.4,9.51c0,-0.62 0.12,-1.18 0.37,-1.68s0.58,-0.88 1.02,-1.15s0.93,-0.4 1.49,-0.4c0.86,0 1.56,0.3 2.09,0.9s0.8,1.39 0.8,2.38v0.08c0,0.62 -0.12,1.17 -0.35,1.66s-0.57,0.87 -1.01,1.15s-0.94,0.41 -1.51,0.41c-0.86,0 -1.56,-0.3 -2.09,-0.9s-0.8,-1.39 -0.8,-2.37V9.51zM11.49,9.64c0,0.7 0.16,1.27 0.49,1.69s0.76,0.64 1.31,0.64c0.55,0 0.99,-0.22 1.31,-0.65s0.49,-1.04 0.49,-1.81c0,-0.7 -0.17,-1.26 -0.5,-1.69s-0.77,-0.65 -1.32,-0.65c-0.54,0 -0.97,0.21 -1.29,0.64S11.49,8.85 11.49,9.64z"
android:fillColor="#FFFFFF"/>
<path
android:pathData="M18.06,12.74v-5.5h-1V6.4h1V5.75c0,-0.68 0.18,-1.21 0.54,-1.58s0.88,-0.56 1.54,-0.56c0.25,0 0.5,0.03 0.74,0.1l-0.06,0.87c-0.18,-0.04 -0.38,-0.05 -0.59,-0.05c-0.35,0 -0.62,0.1 -0.81,0.31s-0.29,0.5 -0.29,0.88V6.4h1.35v0.84h-1.35v5.5H18.06z"
android:fillColor="#FFFFFF"/>
<path
android:pathData="M22.23,12.74v-5.5h-1V6.4h1V5.75c0,-0.68 0.18,-1.21 0.54,-1.58s0.88,-0.56 1.54,-0.56c0.25,0 0.5,0.03 0.74,0.1L25,4.59c-0.18,-0.04 -0.38,-0.05 -0.59,-0.05c-0.35,0 -0.62,0.1 -0.81,0.31s-0.29,0.5 -0.29,0.88V6.4h1.35v0.84h-1.35v5.5H22.23z"
android:fillColor="#FFFFFF"/>
<path
android:pathData="M27.04,12.74h-1.08v-9h1.08V12.74z"
android:fillColor="#FFFFFF"/>
<path
android:pathData="M28.78,4.72c0,-0.18 0.05,-0.32 0.16,-0.45s0.27,-0.18 0.48,-0.18s0.37,0.06 0.48,0.18s0.16,0.27 0.16,0.45s-0.05,0.32 -0.16,0.44s-0.27,0.18 -0.48,0.18s-0.37,-0.06 -0.48,-0.18S28.78,4.89 28.78,4.72zM29.95,12.74h-1.08V6.4h1.08V12.74z"
android:fillColor="#FFFFFF"/>
<path
android:pathData="M32.71,6.4l0.04,0.8c0.48,-0.61 1.12,-0.91 1.9,-0.91c1.34,0 2.02,0.76 2.03,2.27v4.19h-1.08v-4.2c0,-0.46 -0.11,-0.79 -0.31,-1.01S34.75,7.2 34.32,7.2c-0.35,0 -0.66,0.09 -0.93,0.28s-0.47,0.43 -0.62,0.74v4.52h-1.08V6.4H32.71z"
android:fillColor="#FFFFFF"/>
<path
android:pathData="M40.94,12.85c-0.86,0 -1.56,-0.28 -2.1,-0.85s-0.81,-1.32 -0.81,-2.26v-0.2c0,-0.63 0.12,-1.19 0.36,-1.68s0.58,-0.88 1.01,-1.16s0.9,-0.42 1.4,-0.42c0.82,0 1.46,0.27 1.92,0.81s0.69,1.32 0.69,2.33v0.45h-4.29c0.02,0.63 0.2,1.13 0.55,1.51s0.79,0.58 1.33,0.58c0.38,0 0.71,-0.08 0.97,-0.23s0.5,-0.36 0.7,-0.62l0.66,0.52C42.8,12.45 42,12.85 40.94,12.85zM40.8,7.17c-0.44,0 -0.8,0.16 -1.1,0.48s-0.48,0.76 -0.55,1.34h3.18V8.91c-0.03,-0.55 -0.18,-0.98 -0.45,-1.28S41.26,7.17 40.8,7.17z"
android:fillColor="#FFFFFF"/>
</vector>

View File

@@ -0,0 +1,41 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="52dp"
android:height="18dp"
android:viewportWidth="52"
android:viewportHeight="18">
<path
android:pathData="M43.05,18H8.95C4.01,18 0,13.99 0,9.05l0,-0.09C0,4.01 4.01,0 8.95,0l34.09,0C47.97,0 52,4.03 52,8.95v0.09C52,13.99 47.99,18 43.05,18z"
android:strokeAlpha="0"
android:fillColor="#F1AF00"
android:fillAlpha="0"/>
<path
android:fillColor="#FF000000"
android:pathData="M10.4,9.51c0,-0.62 0.12,-1.18 0.37,-1.68s0.58,-0.88 1.02,-1.15s0.93,-0.4 1.49,-0.4c0.86,0 1.56,0.3 2.09,0.9s0.8,1.39 0.8,2.38v0.08c0,0.62 -0.12,1.17 -0.35,1.66s-0.57,0.87 -1.01,1.15s-0.94,0.41 -1.51,0.41c-0.86,0 -1.56,-0.3 -2.09,-0.9s-0.8,-1.39 -0.8,-2.37V9.51zM11.49,9.64c0,0.7 0.16,1.27 0.49,1.69s0.76,0.64 1.31,0.64c0.55,0 0.99,-0.22 1.31,-0.65s0.49,-1.04 0.49,-1.81c0,-0.7 -0.17,-1.26 -0.5,-1.69s-0.77,-0.65 -1.32,-0.65c-0.54,0 -0.97,0.21 -1.29,0.64S11.49,8.85 11.49,9.64z"
android:strokeAlpha="0.38"
android:fillAlpha="0.38"/>
<path
android:fillColor="#FF000000"
android:pathData="M18.56,6.4l0.04,0.8c0.48,-0.61 1.12,-0.91 1.9,-0.91c1.34,0 2.02,0.76 2.03,2.27v4.19h-1.08v-4.2c0,-0.46 -0.11,-0.79 -0.31,-1.01S20.59,7.2 20.16,7.2c-0.35,0 -0.66,0.09 -0.93,0.28s-0.47,0.43 -0.62,0.74v4.52h-1.08V6.4H18.56z"
android:strokeAlpha="0.38"
android:fillAlpha="0.38"/>
<path
android:fillColor="#FF000000"
android:pathData="M25.33,12.74h-1.08v-9h1.08V12.74z"
android:strokeAlpha="0.38"
android:fillAlpha="0.38"/>
<path
android:fillColor="#FF000000"
android:pathData="M27.07,4.72c0,-0.18 0.05,-0.32 0.16,-0.45s0.27,-0.18 0.48,-0.18s0.37,0.06 0.48,0.18s0.16,0.27 0.16,0.45s-0.05,0.32 -0.16,0.44s-0.27,0.18 -0.48,0.18s-0.37,-0.06 -0.48,-0.18S27.07,4.89 27.07,4.72zM28.24,12.74h-1.08V6.4h1.08V12.74z"
android:strokeAlpha="0.38"
android:fillAlpha="0.38"/>
<path
android:fillColor="#FF000000"
android:pathData="M31,6.4l0.04,0.8c0.48,-0.61 1.12,-0.91 1.9,-0.91c1.34,0 2.02,0.76 2.03,2.27v4.19h-1.08v-4.2c0,-0.46 -0.11,-0.79 -0.31,-1.01S33.04,7.2 32.61,7.2c-0.35,0 -0.66,0.09 -0.93,0.28s-0.47,0.43 -0.62,0.74v4.52h-1.08V6.4H31z"
android:strokeAlpha="0.38"
android:fillAlpha="0.38"/>
<path
android:fillColor="#FF000000"
android:pathData="M39.23,12.86c-0.86,0 -1.56,-0.28 -2.1,-0.85s-0.81,-1.32 -0.81,-2.26v-0.2c0,-0.63 0.12,-1.19 0.36,-1.68s0.58,-0.88 1.01,-1.16s0.9,-0.42 1.4,-0.42c0.82,0 1.46,0.27 1.92,0.81s0.69,1.32 0.69,2.33v0.45h-4.29c0.02,0.63 0.2,1.13 0.55,1.51s0.79,0.58 1.33,0.58c0.38,0 0.71,-0.08 0.97,-0.23s0.5,-0.36 0.7,-0.62l0.66,0.52C41.09,12.45 40.29,12.86 39.23,12.86zM39.09,7.17c-0.44,0 -0.8,0.16 -1.1,0.48s-0.48,0.76 -0.55,1.34h3.18V8.91c-0.03,-0.55 -0.18,-0.98 -0.45,-1.28S39.55,7.17 39.09,7.17z"
android:strokeAlpha="0.38"
android:fillAlpha="0.38"/>
</vector>

View 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="M14,9h-4c-0.55,0 -1,0.45 -1,1v4c0,0.55 0.45,1 1,1h4c0.55,0 1,-0.45 1,-1v-4c0,-0.55 -0.45,-1 -1,-1zM13,13h-2v-2h2v2zM21,10c0,-0.55 -0.45,-1 -1,-1h-1L19,7c0,-1.1 -0.9,-2 -2,-2h-2L15,4c0,-0.55 -0.45,-1 -1,-1s-1,0.45 -1,1v1h-2L11,4c0,-0.55 -0.45,-1 -1,-1s-1,0.45 -1,1v1L7,5c-1.1,0 -2,0.9 -2,2v2L4,9c-0.55,0 -1,0.45 -1,1s0.45,1 1,1h1v2L4,13c-0.55,0 -1,0.45 -1,1s0.45,1 1,1h1v2c0,1.1 0.9,2 2,2h2v1c0,0.55 0.45,1 1,1s1,-0.45 1,-1v-1h2v1c0,0.55 0.45,1 1,1s1,-0.45 1,-1v-1h2c1.1,0 2,-0.9 2,-2v-2h1c0.55,0 1,-0.45 1,-1s-0.45,-1 -1,-1h-1v-2h1c0.55,0 1,-0.45 1,-1zM16,17L8,17c-0.55,0 -1,-0.45 -1,-1L7,8c0,-0.55 0.45,-1 1,-1h8c0.55,0 1,0.45 1,1v8c0,0.55 -0.45,1 -1,1z"/>
</vector>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF000000"
android:pathData="M12.01,0C7.24,0 3.38,3.86 3.38,8.63c0,5.14 5.45,12.23 7.69,14.93c0.49,0.59 1.39,0.59 1.89,0c2.23,-2.7 7.67,-9.79 7.67,-14.93C20.62,3.86 16.77,0 12.01,0zM15.45,9.43l-2.74,2.74c-0.28,0.28 -0.75,0.32 -1.06,0.07c-0.37,-0.32 -0.37,-0.84 -0.05,-1.15l1.44,-1.44h-4.2c-0.45,0 -0.82,-0.37 -0.82,-0.82C8.03,8.37 8.39,8 8.85,8h4.19l-1.4,-1.43c-0.3,-0.29 -0.29,-0.8 0,-1.1c0.3,-0.31 0.79,-0.3 1.09,0.01l2.74,2.8C15.75,8.58 15.75,9.11 15.45,9.43zM18.57,8.68c0,3.63 -2.94,6.57 -6.57,6.57S5.43,12.3 5.43,8.68S8.37,2.11 12,2.11S18.57,5.05 18.57,8.68z"/>
</vector>

View 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">
<path
android:fillColor="#FF000000"
android:pathData="M13.69,2.14C6.85,1.01 1.01,6.85 2.14,13.69c0.68,4.14 4.02,7.48 8.17,8.17c6.84,1.12 12.68,-4.71 11.55,-11.55C21.18,6.16 17.84,2.82 13.69,2.14zM12,13.21c-0.73,0 -1.33,-0.52 -1.33,-1.16V7.39c0,-0.64 0.6,-1.16 1.33,-1.16s1.33,0.52 1.33,1.16v4.66C13.33,12.69 12.73,13.21 12,13.21zM12,17.49L12,17.49c-0.73,0 -1.33,-0.59 -1.33,-1.33v0c0,-0.73 0.59,-1.33 1.33,-1.33h0c0.73,0 1.33,0.59 1.33,1.33v0C13.33,16.9 12.73,17.49 12,17.49z"
android:strokeAlpha="0.54"
android:fillAlpha="0.54"/>
</vector>

View File

@@ -270,8 +270,8 @@
<string name="widget_chart_constructor_title">Канструктар графіка</string> <string name="widget_chart_constructor_title">Канструктар графіка</string>
<string name="widget_news_constructor_title">Канструктар навін</string> <string name="widget_news_constructor_title">Канструктар навін</string>
<string name="back_to_news">Назад да навін</string> <string name="back_to_news">Назад да навін</string>
<string name="mock_user_name">Anton Betsun</string> <string name="mock_user_name">User</string>
<string name="mock_user_email">messbees@gmail.com</string> <string name="mock_user_email">user@example.com</string>
<string name="mock_device_name_42">AirMQ #42</string> <string name="mock_device_name_42">AirMQ #42</string>
<string name="mock_device_name_17">AirMQ #17</string> <string name="mock_device_name_17">AirMQ #17</string>
</resources> </resources>

View File

@@ -270,8 +270,8 @@
<string name="widget_chart_constructor_title">Конструктор графика</string> <string name="widget_chart_constructor_title">Конструктор графика</string>
<string name="widget_news_constructor_title">Конструктор новостей</string> <string name="widget_news_constructor_title">Конструктор новостей</string>
<string name="back_to_news">Назад к новостям</string> <string name="back_to_news">Назад к новостям</string>
<string name="mock_user_name">Anton Betsun</string> <string name="mock_user_name">User</string>
<string name="mock_user_email">messbees@gmail.com</string> <string name="mock_user_email">user@example.com</string>
<string name="mock_device_name_42">AirMQ #42</string> <string name="mock_device_name_42">AirMQ #42</string>
<string name="mock_device_name_17">AirMQ #17</string> <string name="mock_device_name_17">AirMQ #17</string>
</resources> </resources>

View File

@@ -14,4 +14,6 @@
<color name="sensorRed">#FFF44336</color> <color name="sensorRed">#FFF44336</color>
<color name="sensorPink">#FFEC407A</color> <color name="sensorPink">#FFEC407A</color>
<color name="sensorPurple">#FF8E24AA</color> <color name="sensorPurple">#FF8E24AA</color>
<color name="black38">#61000000</color>
<color name="windowBackground">#FAFAFA</color>
</resources> </resources>

View File

@@ -18,7 +18,7 @@
<string name="dialog_enable_wifi_title">Enable WiFi</string> <string name="dialog_enable_wifi_title">Enable WiFi</string>
<string name="dialog_are_you_sure">Are you sure?</string> <string name="dialog_are_you_sure">Are you sure?</string>
<string name="dialog_about_title">About</string> <string name="dialog_about_title">About</string>
<string name="dialog_about_text" translatable="false">App by Anton Betsun \nDesign by Alexander Prokhorov</string> <string name="dialog_about_text" translatable="false">App by AirMQ\nDesign by Alexander Prokhorov</string>
<string name="dialog_anonym_title">Continue anonymously?</string> <string name="dialog_anonym_title">Continue anonymously?</string>
<string name="dialog_anonym_mesage">You will not be able to back up your preferences and add new AirMQ devices</string> <string name="dialog_anonym_mesage">You will not be able to back up your preferences and add new AirMQ devices</string>
<string name="dialog_select_device_title">Select device</string> <string name="dialog_select_device_title">Select device</string>
@@ -326,8 +326,8 @@
<string name="widget_chart_constructor_title">Chart Constructor</string> <string name="widget_chart_constructor_title">Chart Constructor</string>
<string name="widget_news_constructor_title">News Constructor</string> <string name="widget_news_constructor_title">News Constructor</string>
<string name="back_to_news">Back to News</string> <string name="back_to_news">Back to News</string>
<string name="mock_user_name">Anton Betsun</string> <string name="mock_user_name">User</string>
<string name="mock_user_email">messbees@gmail.com</string> <string name="mock_user_email">user@example.com</string>
<string name="mock_device_name_42">AirMQ #42</string> <string name="mock_device_name_42">AirMQ #42</string>
<string name="mock_device_name_17">AirMQ #17</string> <string name="mock_device_name_17">AirMQ #17</string>
</resources> </resources>

View File

@@ -1,5 +1,8 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<style name="Theme.AirMQ" parent="android:Theme.Material.Light.NoActionBar" /> <style name="Theme.AirMQ" parent="android:Theme.Material.Light.NoActionBar">
<item name="android:windowBackground">@color/windowBackground</item>
<item name="android:colorBackground">@color/windowBackground</item>
</style>
</resources> </resources>