Migrate UI from android project to kmp
This commit is contained in:
@@ -1,49 +1,18 @@
|
||||
package com.digitoolsolutions.app.torquevaultkmp
|
||||
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.safeContentPadding
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import org.jetbrains.compose.resources.painterResource
|
||||
|
||||
import torquevaultkmp.composeapp.generated.resources.Res
|
||||
import torquevaultkmp.composeapp.generated.resources.compose_multiplatform
|
||||
import com.digitoolsolutions.android.torquevault.ui.theme.TorqueVaultTheme
|
||||
import com.digitoolsolutions.app.torquevaultkmp.screens.MainScreen
|
||||
|
||||
@Composable
|
||||
@Preview
|
||||
fun App() {
|
||||
MaterialTheme {
|
||||
var showContent by remember { mutableStateOf(false) }
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.background(MaterialTheme.colorScheme.primaryContainer)
|
||||
.safeContentPadding()
|
||||
.fillMaxSize(),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
Button(onClick = { showContent = !showContent }) {
|
||||
Text("Click me!")
|
||||
}
|
||||
AnimatedVisibility(showContent) {
|
||||
val greeting = remember { Greeting().greet() }
|
||||
Column(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
Image(painterResource(Res.drawable.compose_multiplatform), null)
|
||||
Text("Compose: $greeting")
|
||||
}
|
||||
}
|
||||
}
|
||||
TorqueVaultTheme {
|
||||
MainScreen(
|
||||
isLoggedIn = false,
|
||||
isDarkTheme = true,
|
||||
onThemeToggle = {}
|
||||
)
|
||||
}
|
||||
}
|
||||
+113
@@ -0,0 +1,113 @@
|
||||
package com.digitoolsolutions.app.torquevaultkmp.components
|
||||
|
||||
import androidx.compose.foundation.layout.RowScope
|
||||
import androidx.compose.foundation.layout.WindowInsets
|
||||
import androidx.compose.foundation.layout.WindowInsetsSides
|
||||
import androidx.compose.foundation.layout.displayCutout
|
||||
import androidx.compose.foundation.layout.navigationBars
|
||||
import androidx.compose.foundation.layout.only
|
||||
import androidx.compose.foundation.layout.union
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TopAppBar
|
||||
import androidx.compose.material3.TopAppBarDefaults
|
||||
import androidx.compose.material3.TopAppBarScrollBehavior
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.painter.Painter
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import com.digitoolsolutions.app.torquevaultkmp.theme.AppBarColor
|
||||
import org.jetbrains.compose.resources.painterResource
|
||||
import org.jetbrains.compose.resources.stringResource
|
||||
import torquevaultkmp.composeapp.generated.resources.Res
|
||||
import torquevaultkmp.composeapp.generated.resources.baseline_add_24
|
||||
import torquevaultkmp.composeapp.generated.resources.baseline_arrow_back_24
|
||||
import torquevaultkmp.composeapp.generated.resources.baseline_menu_24
|
||||
import torquevaultkmp.composeapp.generated.resources.menu_item_accessibility
|
||||
import torquevaultkmp.composeapp.generated.resources.navigation_item_accessibility
|
||||
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun AppBar(
|
||||
title: @Composable () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
onNavigationButtonClick: (() -> Unit)? = null,
|
||||
onHamburgerButtonClick: (() -> Unit)? = null,
|
||||
showBackButton: Boolean = onNavigationButtonClick != null,
|
||||
backButtonIcon: Painter = painterResource(Res.drawable.baseline_arrow_back_24),
|
||||
showHamburgerButton: Boolean = onHamburgerButtonClick != null,
|
||||
expandedHeight: Dp = TopAppBarDefaults.TopAppBarExpandedHeight,
|
||||
windowInsets: WindowInsets = WindowInsets(0,0,0,0),
|
||||
// windowInsets: WindowInsets = WindowInsets.displayCutout
|
||||
//// .union(WindowInsets.statusBars)
|
||||
// .union(WindowInsets.navigationBars)
|
||||
// .only(WindowInsetsSides.Horizontal + WindowInsetsSides.Top),
|
||||
scrollBehavior: TopAppBarScrollBehavior? = null,
|
||||
actions: @Composable RowScope.() -> Unit = {},
|
||||
) {
|
||||
TopAppBar(
|
||||
title = title,
|
||||
modifier = modifier,
|
||||
colors = TopAppBarDefaults.topAppBarColors(
|
||||
containerColor = AppBarColor,
|
||||
titleContentColor = MaterialTheme.colorScheme.onPrimary,
|
||||
actionIconContentColor = MaterialTheme.colorScheme.onPrimary,
|
||||
navigationIconContentColor = MaterialTheme.colorScheme.onPrimary,
|
||||
),
|
||||
navigationIcon = {
|
||||
onNavigationButtonClick?.takeIf { showBackButton }?.let { action ->
|
||||
IconButton(onClick = action) {
|
||||
Icon(
|
||||
painter = backButtonIcon,
|
||||
contentDescription = stringResource(Res.string.navigation_item_accessibility),
|
||||
tint = MaterialTheme.colorScheme.onPrimary,
|
||||
)
|
||||
}
|
||||
} ?: run {
|
||||
onHamburgerButtonClick?.takeIf { showHamburgerButton }?.let { action ->
|
||||
IconButton(onClick = action) {
|
||||
Icon(
|
||||
painter = painterResource(Res.drawable.baseline_menu_24),
|
||||
contentDescription = stringResource(Res.string.menu_item_accessibility),
|
||||
tint = MaterialTheme.colorScheme.onPrimary,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
actions = actions,
|
||||
scrollBehavior = scrollBehavior,
|
||||
expandedHeight = expandedHeight,
|
||||
windowInsets = windowInsets,
|
||||
)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun AppBarPreview() {
|
||||
MaterialTheme {
|
||||
AppBar(
|
||||
title = { Text(text = "Title") },
|
||||
actions = {
|
||||
IconButton(onClick = {}) {
|
||||
Icon(
|
||||
painter = painterResource(Res.drawable.baseline_add_24),
|
||||
contentDescription = ""
|
||||
)
|
||||
}
|
||||
IconButton(onClick = {}) {
|
||||
Icon(
|
||||
painter = painterResource(Res.drawable.baseline_menu_24),
|
||||
contentDescription = ""
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
package com.digitoolsolutions.app.torquevaultkmp.components
|
||||
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.ColorFilter
|
||||
import androidx.compose.ui.graphics.painter.Painter
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
||||
@Composable
|
||||
internal fun BigIcon(
|
||||
imageVector: ImageVector,
|
||||
modifier: Modifier = Modifier,
|
||||
size: Dp = 80.dp,
|
||||
color: Color = MaterialTheme.colorScheme.onBackground.copy(alpha = 0.6f),
|
||||
) {
|
||||
Image(
|
||||
imageVector = imageVector,
|
||||
contentDescription = null,
|
||||
modifier = modifier.size(size),
|
||||
colorFilter = ColorFilter.tint(color),
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
internal fun BigIcon(
|
||||
painterResource: Painter,
|
||||
modifier: Modifier = Modifier,
|
||||
size: Dp = 80.dp,
|
||||
color: Color = MaterialTheme.colorScheme.onBackground.copy(alpha = 0.6f),
|
||||
) {
|
||||
Image(
|
||||
painter = painterResource,
|
||||
contentDescription = null,
|
||||
modifier = modifier.size(size),
|
||||
colorFilter = ColorFilter.tint(color),
|
||||
)
|
||||
}
|
||||
+105
@@ -0,0 +1,105 @@
|
||||
package com.digitoolsolutions.app.torquevaultkmp.components
|
||||
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.ColorFilter
|
||||
import androidx.compose.ui.graphics.painter.Painter
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.graphics.vector.rememberVectorPainter
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import org.jetbrains.compose.resources.painterResource
|
||||
import torquevaultkmp.composeapp.generated.resources.Res
|
||||
import torquevaultkmp.composeapp.generated.resources.ic_check
|
||||
|
||||
/**
|
||||
* A circular icon with a background.
|
||||
*
|
||||
* @param painter The painter to be used for the icon.
|
||||
* @param modifier The modifier to be applied to the icon.
|
||||
* @param backgroundColor The background color of the circle.
|
||||
* @param enabled If false, the icon will be displayed in a disabled state.
|
||||
* @param size The size of the circular background.
|
||||
* @param iconSize The size of the icon inside the circle.
|
||||
*/
|
||||
@Composable
|
||||
fun CircularIcon(
|
||||
painter: Painter,
|
||||
modifier: Modifier = Modifier,
|
||||
backgroundColor: Color = MaterialTheme.colorScheme.secondary,
|
||||
enabled: Boolean = true,
|
||||
size: Dp = 40.dp,
|
||||
iconSize: Dp = 24.dp,
|
||||
iconTint: Color = MaterialTheme.colorScheme.onSecondary
|
||||
) {
|
||||
val padding = (size - iconSize) / 2
|
||||
Image(
|
||||
painter = painter,
|
||||
contentDescription = null,
|
||||
colorFilter = if (enabled) {
|
||||
ColorFilter.tint(iconTint)
|
||||
} else {
|
||||
ColorFilter.tint(MaterialTheme.colorScheme.onSurface.copy(alpha = 0.38f))
|
||||
},
|
||||
modifier = Modifier
|
||||
.size(size)
|
||||
.then(modifier)
|
||||
.background(
|
||||
color = if (enabled) {
|
||||
backgroundColor
|
||||
} else {
|
||||
MaterialTheme.colorScheme.onSurface.copy(alpha = 0.12f)
|
||||
},
|
||||
shape = CircleShape
|
||||
)
|
||||
.padding(padding)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* A circular icon with a background.
|
||||
*
|
||||
* @param imageVector The vector image to be used for the icon.
|
||||
* @param modifier The modifier to be applied to the icon.
|
||||
* @param backgroundColor The background color of the circle.
|
||||
* @param enabled If false, the icon will be displayed in a disabled state.
|
||||
* @param size The size of the circular background.
|
||||
* @param iconSize The size of the icon inside the circle.
|
||||
*/
|
||||
@Composable
|
||||
fun CircularIcon(
|
||||
imageVector: ImageVector,
|
||||
modifier: Modifier = Modifier,
|
||||
backgroundColor: Color = MaterialTheme.colorScheme.secondary,
|
||||
enabled: Boolean = true,
|
||||
size: Dp = 40.dp,
|
||||
iconSize: Dp = 24.dp,
|
||||
iconTint: Color = MaterialTheme.colorScheme.onSecondary
|
||||
) {
|
||||
CircularIcon(
|
||||
painter = rememberVectorPainter(image = imageVector),
|
||||
modifier = modifier,
|
||||
backgroundColor = backgroundColor,
|
||||
enabled = enabled,
|
||||
iconSize = iconSize,
|
||||
iconTint = iconTint
|
||||
)
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun CircularIconPreview() {
|
||||
MaterialTheme {
|
||||
CircularIcon(
|
||||
painter = painterResource(Res.drawable.ic_check)
|
||||
)
|
||||
}
|
||||
}
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
package com.digitoolsolutions.app.torquevaultkmp.components
|
||||
|
||||
import androidx.compose.material3.FilterChip
|
||||
import androidx.compose.material3.FilterChipDefaults
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.painter.Painter
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.graphics.vector.rememberVectorPainter
|
||||
import org.jetbrains.compose.resources.DrawableResource
|
||||
import org.jetbrains.compose.resources.painterResource
|
||||
import torquevaultkmp.composeapp.generated.resources.Res
|
||||
import torquevaultkmp.composeapp.generated.resources.baseline_close_24
|
||||
|
||||
|
||||
@Composable
|
||||
fun FilterButton(
|
||||
title: String,
|
||||
icon: DrawableResource,
|
||||
isSelected: Boolean,
|
||||
containerColorEnabled: Color = Color.Unspecified,
|
||||
containerColorDisabled: Color = Color.Unspecified,
|
||||
onClick: () -> Unit = {},
|
||||
) {
|
||||
FilterButton(
|
||||
title = title,
|
||||
painter = painterResource(icon),
|
||||
isSelected = isSelected,
|
||||
containerColorEnabled = containerColorEnabled,
|
||||
containerColorDisabled = containerColorDisabled,
|
||||
onClick = onClick
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun FilterButton(
|
||||
title: String,
|
||||
painter: Painter,
|
||||
isSelected: Boolean,
|
||||
containerColorEnabled: Color = Color.Unspecified,
|
||||
containerColorDisabled: Color = Color.Unspecified,
|
||||
onClick: () -> Unit = {},
|
||||
) {
|
||||
FilterChip(
|
||||
selected = isSelected,
|
||||
onClick = onClick,
|
||||
label = { Text(title) },
|
||||
leadingIcon = {
|
||||
Icon(
|
||||
painter = if (isSelected) painterResource(Res.drawable.baseline_close_24) else painter,
|
||||
contentDescription = null
|
||||
)
|
||||
},
|
||||
colors = FilterChipDefaults.filterChipColors(
|
||||
containerColor = containerColorDisabled,
|
||||
selectedContainerColor = containerColorEnabled,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun FilterButton(
|
||||
title: String,
|
||||
icon: ImageVector,
|
||||
isSelected: Boolean,
|
||||
containerColorEnabled: Color = Color.Unspecified,
|
||||
containerColorDisabled: Color = Color.Unspecified,
|
||||
onClick: () -> Unit = {},
|
||||
) {
|
||||
FilterButton(
|
||||
title = title,
|
||||
painter = rememberVectorPainter(icon),
|
||||
isSelected = isSelected,
|
||||
containerColorEnabled = containerColorEnabled,
|
||||
containerColorDisabled = containerColorDisabled,
|
||||
onClick = onClick
|
||||
)
|
||||
}
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
package com.digitoolsolutions.app.torquevaultkmp.components
|
||||
|
||||
import androidx.compose.material3.LocalContentColor
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.text.AnnotatedString
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
|
||||
|
||||
@Composable
|
||||
internal fun Hint(
|
||||
text: String,
|
||||
modifier: Modifier = Modifier,
|
||||
color: Color = LocalContentColor.current,
|
||||
style: TextStyle = MaterialTheme.typography.bodyMedium,
|
||||
textAlign: TextAlign? = TextAlign.Center,
|
||||
) {
|
||||
Text(
|
||||
text = text,
|
||||
color = color,
|
||||
style = style,
|
||||
modifier = modifier,
|
||||
textAlign = textAlign
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
internal fun Hint(
|
||||
text: AnnotatedString,
|
||||
modifier: Modifier = Modifier,
|
||||
color: Color = LocalContentColor.current,
|
||||
style: TextStyle = MaterialTheme.typography.bodyMedium,
|
||||
textAlign: TextAlign? = TextAlign.Center,
|
||||
) {
|
||||
Text(
|
||||
text = text,
|
||||
color = color,
|
||||
style = style,
|
||||
modifier = modifier,
|
||||
textAlign = textAlign
|
||||
)
|
||||
}
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
package com.digitoolsolutions.app.torquevaultkmp.components
|
||||
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import org.jetbrains.compose.resources.DrawableResource
|
||||
import org.jetbrains.compose.resources.painterResource
|
||||
import org.jetbrains.compose.resources.stringResource
|
||||
import torquevaultkmp.composeapp.generated.resources.Res
|
||||
import torquevaultkmp.composeapp.generated.resources.dbm
|
||||
import torquevaultkmp.composeapp.generated.resources.ic_signal_max
|
||||
import torquevaultkmp.composeapp.generated.resources.ic_signal_medium
|
||||
import torquevaultkmp.composeapp.generated.resources.ic_signal_min
|
||||
|
||||
|
||||
private const val MEDIUM_RSSI = -80
|
||||
private const val MAX_RSSI = -60
|
||||
|
||||
/**
|
||||
* A component that displays an icon and a text representing the RSSI value.
|
||||
*
|
||||
* @param rssi The RSSI value.
|
||||
*/
|
||||
@Composable
|
||||
fun RssiIcon(rssi: Int) {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.spacedBy(2.dp)
|
||||
) {
|
||||
Image(
|
||||
painter = painterResource(getImageRes(rssi)),
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(24.dp),
|
||||
)
|
||||
Text(
|
||||
text = stringResource(Res.string.dbm, rssi),
|
||||
style = MaterialTheme.typography.bodySmall
|
||||
)
|
||||
}
|
||||
}
|
||||
private fun getImageRes(rssi: Int): DrawableResource = when {
|
||||
rssi < MEDIUM_RSSI -> Res.drawable.ic_signal_min
|
||||
rssi < MAX_RSSI -> Res.drawable.ic_signal_medium
|
||||
else -> Res.drawable.ic_signal_max
|
||||
}
|
||||
|
||||
@Preview(showBackground = true, widthDp = 520)
|
||||
@Composable
|
||||
private fun RssiIconPreview() {
|
||||
MaterialTheme {
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(16.dp)) {
|
||||
RssiIcon(rssi = -30)
|
||||
RssiIcon(rssi = -40)
|
||||
RssiIcon(rssi = -50)
|
||||
RssiIcon(rssi = -60)
|
||||
RssiIcon(rssi = -70)
|
||||
RssiIcon(rssi = -80)
|
||||
RssiIcon(rssi = -90)
|
||||
RssiIcon(rssi = -100)
|
||||
}
|
||||
}
|
||||
}
|
||||
+211
@@ -0,0 +1,211 @@
|
||||
package com.digitoolsolutions.app.torquevaultkmp.components
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.ColumnScope
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.painter.Painter
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.graphics.vector.rememberVectorPainter
|
||||
import androidx.compose.ui.text.AnnotatedString
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import org.jetbrains.compose.resources.painterResource
|
||||
import torquevaultkmp.composeapp.generated.resources.Res
|
||||
import torquevaultkmp.composeapp.generated.resources.baseline_warning_24
|
||||
|
||||
|
||||
/**
|
||||
* A warning view is a component that displays a warning icon, a title, and a hint.
|
||||
*
|
||||
* @param imageVector The vector icon to be displayed.
|
||||
* @param title The title of the warning.
|
||||
* @param hint The hint to be displayed.
|
||||
* @param modifier The modifier to be applied to the layout.
|
||||
* @param hintTextAlign The text alignment of the hint, defaults to [TextAlign.Center].
|
||||
* @param content The content to be displayed below the hint.
|
||||
*/
|
||||
@Composable
|
||||
fun WarningView(
|
||||
imageVector: ImageVector,
|
||||
title: String,
|
||||
hint: String,
|
||||
modifier: Modifier = Modifier,
|
||||
hintTextAlign: TextAlign? = TextAlign.Center,
|
||||
content: @Composable ColumnScope.() -> Unit = {}
|
||||
) {
|
||||
WarningView(
|
||||
painterResource = rememberVectorPainter(image = imageVector),
|
||||
title = title,
|
||||
hint = AnnotatedString(text = hint),
|
||||
modifier = modifier,
|
||||
hintTextAlign = hintTextAlign,
|
||||
content = content,
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* A warning view is a component that displays a warning icon, a title, and a hint.
|
||||
*
|
||||
* @param imageVector The vector icon to be displayed.
|
||||
* @param title The title of the warning.
|
||||
* @param hint The hint to be displayed, as [AnnotatedString].
|
||||
* @param modifier The modifier to be applied to the layout.
|
||||
* @param hintTextAlign The text alignment of the hint, defaults to [TextAlign.Center].
|
||||
* @param content The content to be displayed below the hint.
|
||||
*/
|
||||
@Composable
|
||||
fun WarningView(
|
||||
imageVector: ImageVector,
|
||||
title: String,
|
||||
hint: AnnotatedString,
|
||||
modifier: Modifier = Modifier,
|
||||
hintTextAlign: TextAlign? = TextAlign.Center,
|
||||
content: @Composable ColumnScope.() -> Unit = {}
|
||||
) {
|
||||
WarningView(
|
||||
painterResource = rememberVectorPainter(image = imageVector),
|
||||
title = title,
|
||||
hint = hint,
|
||||
modifier = modifier,
|
||||
hintTextAlign = hintTextAlign,
|
||||
content = content,
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* A warning view is a component that displays a warning icon, a title, and a hint.
|
||||
*
|
||||
* @param painterResource The painter to be displayed.
|
||||
* @param title The title of the warning.
|
||||
* @param hint The hint to be displayed, as [AnnotatedString].
|
||||
* @param modifier The modifier to be applied to the layout.
|
||||
* @param hintTextAlign The text alignment of the hint, defaults to [TextAlign.Center].
|
||||
* @param content The content to be displayed below the hint.
|
||||
*/
|
||||
@Composable
|
||||
fun WarningView(
|
||||
painterResource: Painter,
|
||||
title: String,
|
||||
hint: AnnotatedString,
|
||||
modifier: Modifier = Modifier,
|
||||
hintTextAlign: TextAlign? = TextAlign.Center,
|
||||
content: @Composable ColumnScope.() -> Unit = {}
|
||||
) {
|
||||
WarningView(
|
||||
painterResource = painterResource,
|
||||
title = { Text(text = title) },
|
||||
hint = { Hint(text = hint, textAlign = hintTextAlign) },
|
||||
modifier = modifier,
|
||||
content = content,
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* A warning view is a component that displays a warning icon, a title, and a hint.
|
||||
*
|
||||
* @param painterResource The painter to be displayed.
|
||||
* @param title The title of the warning.
|
||||
* @param hint The hint to be displayed.
|
||||
* @param modifier The modifier to be applied to the layout.
|
||||
* @param content The content to be displayed below the hint.
|
||||
*/
|
||||
@Composable
|
||||
fun WarningView(
|
||||
painterResource: Painter,
|
||||
title: String,
|
||||
hint: String,
|
||||
modifier: Modifier = Modifier,
|
||||
hintTextAlign: TextAlign? = TextAlign.Center,
|
||||
content: @Composable ColumnScope.() -> Unit = {}
|
||||
) {
|
||||
WarningView(
|
||||
painterResource = painterResource,
|
||||
title = { Text(text = title) },
|
||||
hint = { Hint(text = hint, textAlign = hintTextAlign) },
|
||||
modifier = modifier,
|
||||
content = content,
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* A warning view is a component that displays a warning icon, a title, and a hint.
|
||||
*
|
||||
* @param imageVector The vector icon to be displayed.
|
||||
* @param title The title of the warning.
|
||||
* @param hint The hint to be displayed.
|
||||
* @param modifier The modifier to be applied to the layout.
|
||||
* @param content The content to be displayed below the hint.
|
||||
*/
|
||||
@Composable
|
||||
fun WarningView(
|
||||
imageVector: ImageVector,
|
||||
title: @Composable () -> Unit,
|
||||
hint: @Composable () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
content: @Composable ColumnScope.() -> Unit = {}
|
||||
) {
|
||||
WarningView(
|
||||
painterResource = rememberVectorPainter(image = imageVector),
|
||||
title = title,
|
||||
hint = hint,
|
||||
modifier = modifier,
|
||||
content = content,
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* A warning view is a component that displays a warning icon, a title, and a hint.
|
||||
*
|
||||
* @param painterResource The painter to be displayed.
|
||||
* @param title The title of the warning.
|
||||
* @param hint The hint to be displayed.
|
||||
* @param modifier The modifier to be applied to the layout.
|
||||
* @param content The content to be displayed below the hint.
|
||||
*/
|
||||
@Composable
|
||||
fun WarningView(
|
||||
painterResource: Painter,
|
||||
title: @Composable () -> Unit,
|
||||
hint: @Composable () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
content: @Composable ColumnScope.() -> Unit = {}
|
||||
) {
|
||||
Column(
|
||||
modifier = modifier,
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp, Alignment.CenterVertically),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
BigIcon(painterResource = painterResource)
|
||||
|
||||
title()
|
||||
|
||||
hint()
|
||||
|
||||
content()
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun WarningViewPreview() {
|
||||
MaterialTheme {
|
||||
WarningView(
|
||||
painterResource = painterResource(Res.drawable.baseline_warning_24),
|
||||
title = "Warning",
|
||||
hint = "This is a warning view",
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
) {
|
||||
Button(onClick = { /*TODO*/ }) {
|
||||
Text(text = "This is content")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
package com.digitoolsolutions.app.torquevaultkmp.di
|
||||
|
||||
class Koin {
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
package com.digitoolsolutions.app.torquevaultkmp.di
|
||||
|
||||
import org.koin.dsl.module
|
||||
|
||||
val homeModule = module { }
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
package com.digitoolsolutions.app.torquevaultkmp.screens
|
||||
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import org.jetbrains.compose.resources.DrawableResource
|
||||
|
||||
sealed class AppIcon {
|
||||
data class Resource(val resId: DrawableResource) : AppIcon()
|
||||
data class Vector(val imageVector: ImageVector) : AppIcon()
|
||||
}
|
||||
|
||||
open class Destinations(
|
||||
val label: String,
|
||||
val icon: AppIcon,
|
||||
val route: String = label.lowercase().replace(" ", "_")
|
||||
)
|
||||
+92
@@ -0,0 +1,92 @@
|
||||
package com.digitoolsolutions.app.torquevaultkmp.screens
|
||||
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.NavigationBar
|
||||
import androidx.compose.material3.NavigationBarItem
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.navigation.compose.NavHost
|
||||
import androidx.navigation.compose.composable
|
||||
import androidx.navigation.compose.currentBackStackEntryAsState
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
import com.digitoolsolutions.app.torquevaultkmp.screens.auth.AuthDestination
|
||||
import com.digitoolsolutions.app.torquevaultkmp.screens.auth.LoginScreen
|
||||
import com.digitoolsolutions.app.torquevaultkmp.screens.bonded.BondedDestination
|
||||
import com.digitoolsolutions.app.torquevaultkmp.screens.bonded.BondedScreen
|
||||
import com.digitoolsolutions.app.torquevaultkmp.screens.home.HomeDestination
|
||||
import com.digitoolsolutions.app.torquevaultkmp.screens.home.HomeScreen
|
||||
import com.digitoolsolutions.app.torquevaultkmp.screens.logs.LogDestination
|
||||
import com.digitoolsolutions.app.torquevaultkmp.screens.logs.LogScreen
|
||||
import com.digitoolsolutions.app.torquevaultkmp.screens.settings.SettingDestination
|
||||
import com.digitoolsolutions.app.torquevaultkmp.screens.settings.SettingScreen
|
||||
import org.jetbrains.compose.resources.painterResource
|
||||
import org.koin.compose.viewmodel.koinViewModel
|
||||
@Composable
|
||||
fun MainScreen(
|
||||
isLoggedIn: Boolean,
|
||||
isDarkTheme: Boolean,
|
||||
onThemeToggle: (Boolean) -> Unit,
|
||||
) {
|
||||
val destinations = listOf(
|
||||
HomeDestination,
|
||||
BondedDestination,
|
||||
LogDestination,
|
||||
SettingDestination,
|
||||
)
|
||||
val navController = rememberNavController()
|
||||
val navBackStackEntry by navController.currentBackStackEntryAsState()
|
||||
val currentDestination = navBackStackEntry?.destination
|
||||
|
||||
Scaffold(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
bottomBar = {
|
||||
if (currentDestination?.route != AuthDestination.route)
|
||||
NavigationBar {
|
||||
destinations.forEach { dest ->
|
||||
NavigationBarItem(
|
||||
icon = {
|
||||
when (val icon = dest.icon) {
|
||||
is AppIcon.Resource -> Icon(
|
||||
painter = painterResource(icon.resId),
|
||||
contentDescription = dest.label
|
||||
)
|
||||
is AppIcon.Vector -> Icon(
|
||||
imageVector = icon.imageVector,
|
||||
contentDescription = dest.label
|
||||
)
|
||||
}
|
||||
},
|
||||
label = { Text(dest.label) },
|
||||
selected = currentDestination?.route == dest.route,
|
||||
onClick = {
|
||||
navController.navigate(dest.route) {
|
||||
popUpTo(navController.graph.startDestinationId) { saveState = true }
|
||||
launchSingleTop = true
|
||||
restoreState = true
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
) { innerPadding ->
|
||||
NavHost(
|
||||
navController = navController,
|
||||
startDestination = HomeDestination.route,
|
||||
modifier = Modifier.padding(innerPadding).fillMaxSize()
|
||||
) {
|
||||
composable(AuthDestination.route) { LoginScreen(navController) }
|
||||
composable(HomeDestination.route) { HomeScreen(navController) }
|
||||
composable(BondedDestination.route) { BondedScreen(navController) }
|
||||
composable(LogDestination.route) { LogScreen(navController) }
|
||||
composable(SettingDestination.route) {
|
||||
SettingScreen(navController, isDarkTheme, onThemeToggle)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
package com.digitoolsolutions.app.torquevaultkmp.screens.auth
|
||||
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Key
|
||||
import com.digitoolsolutions.app.torquevaultkmp.screens.AppIcon
|
||||
import com.digitoolsolutions.app.torquevaultkmp.screens.Destinations
|
||||
|
||||
object AuthDestination: Destinations(
|
||||
label = "Login",
|
||||
icon = AppIcon.Vector(Icons.Default.Key),
|
||||
route = "login"
|
||||
)
|
||||
+232
@@ -0,0 +1,232 @@
|
||||
package com.digitoolsolutions.app.torquevaultkmp.screens.auth
|
||||
|
||||
import androidx.compose.foundation.Image
|
||||
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.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.imePadding
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Visibility
|
||||
import androidx.compose.material.icons.filled.VisibilityOff
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.SnackbarHost
|
||||
import androidx.compose.material3.SnackbarHostState
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.focus.FocusDirection
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalFocusManager
|
||||
import androidx.compose.ui.text.input.ImeAction
|
||||
import androidx.compose.ui.text.input.PasswordVisualTransformation
|
||||
import androidx.compose.ui.text.input.VisualTransformation
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import androidx.navigation.NavController
|
||||
import com.digitoolsolutions.app.torquevaultkmp.screens.home.HomeDestination
|
||||
import kotlinx.coroutines.launch
|
||||
import org.jetbrains.compose.resources.painterResource
|
||||
import org.jetbrains.compose.resources.stringResource
|
||||
import torquevaultkmp.composeapp.generated.resources.Res
|
||||
import torquevaultkmp.composeapp.generated.resources.btn_login
|
||||
import torquevaultkmp.composeapp.generated.resources.dts_text
|
||||
import torquevaultkmp.composeapp.generated.resources.lbl_password
|
||||
import torquevaultkmp.composeapp.generated.resources.lbl_server_url
|
||||
import torquevaultkmp.composeapp.generated.resources.lbl_username
|
||||
import torquevaultkmp.composeapp.generated.resources.server_url_placeholder
|
||||
|
||||
@Composable
|
||||
fun LoginScreen(
|
||||
navController: NavController,
|
||||
// viewModel: AuthViewModel = hiltViewModel()
|
||||
) {
|
||||
// val loginState by viewModel.loginState.collectAsState()
|
||||
|
||||
LoginScreenContent(
|
||||
// viewModel = viewModel,
|
||||
// loginState = loginState,
|
||||
onLogin = { serverUrl, username, password ->
|
||||
// viewModel.updateServerUrl(serverUrl)
|
||||
// viewModel.login(username, password)
|
||||
},
|
||||
onLoginSuccess = {
|
||||
navController.navigate(HomeDestination.route)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun LoginScreenContent(
|
||||
// viewModel: AuthViewModel,
|
||||
// loginState: LoginUiState,
|
||||
onLogin: (String, String, String) -> Unit,
|
||||
onLoginSuccess: () -> Unit
|
||||
) {
|
||||
val focusManager = LocalFocusManager.current
|
||||
// val serverUrl by viewModel.serverUrl.collectAsState()
|
||||
val serverUrl = ""
|
||||
var username by remember { mutableStateOf("admin") }
|
||||
var password by remember { mutableStateOf("admin") }
|
||||
var passwordVisible by remember { mutableStateOf(false) }
|
||||
val snackbarHostState = remember { SnackbarHostState() }
|
||||
val scope = rememberCoroutineScope()
|
||||
|
||||
// LaunchedEffect(loginState) {
|
||||
// if (loginState is LoginUiState.Error) {
|
||||
// snackbarHostState.showSnackbar(loginState.message)
|
||||
// }
|
||||
// if (loginState is LoginUiState.Success) {
|
||||
// onLoginSuccess()
|
||||
// }
|
||||
// }
|
||||
Scaffold(
|
||||
modifier = Modifier.imePadding(),
|
||||
snackbarHost = { SnackbarHost(snackbarHostState) })
|
||||
{ padding ->
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.background(MaterialTheme.colorScheme.background)
|
||||
.fillMaxSize()
|
||||
.padding(padding)
|
||||
.padding(16.dp),
|
||||
verticalArrangement = Arrangement.Center,
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.background(Color.White, shape = RoundedCornerShape(8.dp))
|
||||
.padding(4.dp)
|
||||
.height(70.dp)
|
||||
.width(190.dp),
|
||||
contentAlignment = Alignment.Center
|
||||
){
|
||||
Image(
|
||||
painter = painterResource(Res.drawable.dts_text),
|
||||
contentDescription = "App Logo",
|
||||
modifier = Modifier.fillMaxSize()
|
||||
)
|
||||
}
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
OutlinedTextField(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
singleLine = true,
|
||||
keyboardOptions = KeyboardOptions.Default.copy(imeAction = ImeAction.Next),
|
||||
keyboardActions = KeyboardActions(
|
||||
onNext = { focusManager.moveFocus(FocusDirection.Down) }
|
||||
),
|
||||
value = serverUrl,
|
||||
onValueChange = { /** viewModel.onChangeServerUrl(it) */ },
|
||||
label = { Text(stringResource(Res.string.lbl_server_url)) },
|
||||
placeholder = { Text(stringResource(Res.string.server_url_placeholder)) }
|
||||
|
||||
)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
OutlinedTextField(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
singleLine = true,
|
||||
keyboardOptions = KeyboardOptions.Default.copy(imeAction = ImeAction.Next),
|
||||
keyboardActions = KeyboardActions(
|
||||
onNext = { focusManager.moveFocus(FocusDirection.Down) }
|
||||
),
|
||||
value = username,
|
||||
onValueChange = { username = it },
|
||||
label = { Text(stringResource(Res.string.lbl_username)) }
|
||||
)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
OutlinedTextField(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
singleLine = true,
|
||||
visualTransformation = if (passwordVisible) VisualTransformation.None else PasswordVisualTransformation(),
|
||||
trailingIcon = {
|
||||
val image =
|
||||
if (passwordVisible) Icons.Default.Visibility else Icons.Default.VisibilityOff
|
||||
IconButton(onClick = { passwordVisible = !passwordVisible }) {
|
||||
Icon(
|
||||
imageVector = image,
|
||||
contentDescription = if (passwordVisible) "Hide password" else "Show password"
|
||||
)
|
||||
}
|
||||
},
|
||||
keyboardOptions = KeyboardOptions.Default.copy(imeAction = ImeAction.Done),
|
||||
keyboardActions = KeyboardActions(
|
||||
onDone = {
|
||||
if (username.isBlank() || password.isBlank())
|
||||
scope.launch {
|
||||
snackbarHostState.showSnackbar("Username and Password is required!")
|
||||
}
|
||||
else
|
||||
onLogin(serverUrl, username, password)
|
||||
}
|
||||
),
|
||||
value = password,
|
||||
onValueChange = { password = it },
|
||||
label = { Text(stringResource(Res.string.lbl_password)) }
|
||||
)
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
Button(
|
||||
modifier = Modifier
|
||||
.width(150.dp)
|
||||
.height(45.dp),
|
||||
onClick = {
|
||||
if (username.isBlank() || password.isBlank())
|
||||
scope.launch {
|
||||
snackbarHostState.showSnackbar("Username and password is required!")
|
||||
}
|
||||
else
|
||||
onLogin(serverUrl.trim(), username.trim(), password.trim())
|
||||
},
|
||||
// enabled = loginState !is LoginUiState.Loading
|
||||
) {
|
||||
Text(stringResource(Res.string.btn_login))
|
||||
// if (loginState is LoginUiState.Loading) {
|
||||
// Spacer(modifier = Modifier.width(16.dp))
|
||||
// CircularProgressIndicator(
|
||||
// modifier = Modifier.size(20.dp),
|
||||
// color = Color.Blue,
|
||||
// strokeWidth = 2.dp
|
||||
// )
|
||||
//
|
||||
// }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun LoginScreenPreview() {
|
||||
LoginScreenContent(
|
||||
// viewModel = viewModel(),
|
||||
// loginState = LoginUiState.Idle,
|
||||
onLogin = { _, _, _ -> },
|
||||
onLoginSuccess = {}
|
||||
)
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
package com.digitoolsolutions.app.torquevaultkmp.screens.bonded
|
||||
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.Link
|
||||
import com.digitoolsolutions.app.torquevaultkmp.screens.AppIcon
|
||||
import com.digitoolsolutions.app.torquevaultkmp.screens.Destinations
|
||||
|
||||
object BondedDestination: Destinations(
|
||||
label = "Bonded",
|
||||
icon = AppIcon.Vector(Icons.Outlined.Link),
|
||||
route = "bonded"
|
||||
)
|
||||
+108
@@ -0,0 +1,108 @@
|
||||
package com.digitoolsolutions.app.torquevaultkmp.screens.bonded
|
||||
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.imePadding
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.SnackbarHost
|
||||
import androidx.compose.material3.SnackbarHostState
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.pulltorefresh.PullToRefreshBox
|
||||
import androidx.compose.material3.pulltorefresh.rememberPullToRefreshState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.navigation.NavController
|
||||
import kotlin.uuid.ExperimentalUuidApi
|
||||
import com.digitoolsolutions.app.torquevaultkmp.components.AppBar
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
import kotlinx.coroutines.launch
|
||||
import org.jetbrains.compose.resources.stringResource
|
||||
import torquevaultkmp.composeapp.generated.resources.Res
|
||||
import torquevaultkmp.composeapp.generated.resources.bonded_title
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class, ExperimentalUuidApi::class)
|
||||
@Composable
|
||||
fun BondedScreen(
|
||||
navController: NavController,
|
||||
// bleScannerViewModel: ScannerViewModel,
|
||||
// viewModel: BondedViewModel = hiltViewModel()
|
||||
) {
|
||||
// val devices = viewModel.bondedDevices.collectAsLazyPagingItems()
|
||||
|
||||
BondedContent(
|
||||
// devices = devices,
|
||||
// onRefresh = {devices.refresh()},
|
||||
// onDelete = {
|
||||
// viewModel.removeDevice(it)
|
||||
// bleScannerViewModel.removeBondedCache(it.id)
|
||||
// }
|
||||
)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
internal fun BondedContent(
|
||||
// devices: LazyPagingItems<Device>,
|
||||
// onRefresh: () -> Unit,
|
||||
// onDelete: (Device) -> Unit
|
||||
) {
|
||||
val snackbarHostState = remember { SnackbarHostState() }
|
||||
Scaffold (
|
||||
modifier = Modifier.imePadding(),
|
||||
topBar = {
|
||||
AppBar(
|
||||
title = {Text(stringResource(Res.string.bonded_title))},
|
||||
)
|
||||
},
|
||||
snackbarHost = { SnackbarHost(snackbarHostState) })
|
||||
{ padding ->
|
||||
val pullToRefreshState = rememberPullToRefreshState()
|
||||
val scope = rememberCoroutineScope()
|
||||
PullToRefreshBox(
|
||||
isRefreshing = false,
|
||||
onRefresh = {
|
||||
// onRefresh()
|
||||
scope.launch {
|
||||
pullToRefreshState.animateToHidden()
|
||||
}
|
||||
},
|
||||
state = pullToRefreshState,
|
||||
modifier = Modifier.fillMaxSize().imePadding().padding(padding).padding(horizontal = 16.dp, vertical = 12.dp),
|
||||
){
|
||||
// if (devices.itemCount == 0) {
|
||||
// EmptyBondedView()
|
||||
// }else {
|
||||
// LazyColumn{
|
||||
// items(count = devices.itemCount, key = { index -> devices[index]?.id ?: index }) { index ->
|
||||
// val device = devices[index]
|
||||
// device?.let { it ->
|
||||
// BondedDeviceItem(it, onDelete = { onDelete(it) })
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun LogScreenPreview() {
|
||||
// val fakeDevices = listOf(
|
||||
// Device("AA:BB:CC:DD:EE", "Device 1"),
|
||||
// Device("AA:BB:CC:DD:FF", "Device 2")
|
||||
// )
|
||||
// val pagingData = PagingData.from(fakeDevices)
|
||||
// val lazyPagingItems = flowOf(pagingData).collectAsLazyPagingItems()
|
||||
//
|
||||
// BondedContent(
|
||||
// devices = lazyPagingItems,
|
||||
// onRefresh = {lazyPagingItems.refresh()},
|
||||
// onDelete = {}
|
||||
// )
|
||||
}
|
||||
+122
@@ -0,0 +1,122 @@
|
||||
package com.digitoolsolutions.app.torquevaultkmp.screens.bonded.view
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
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.padding
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Bluetooth
|
||||
import androidx.compose.material.icons.filled.DeleteSweep
|
||||
import androidx.compose.material3.CardDefaults
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.OutlinedCard
|
||||
import androidx.compose.material3.SwipeToDismissBox
|
||||
import androidx.compose.material3.SwipeToDismissBoxDefaults
|
||||
import androidx.compose.material3.SwipeToDismissBoxState
|
||||
import androidx.compose.material3.SwipeToDismissBoxValue
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.rememberSwipeToDismissBoxState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.lerp
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.digitoolsolutions.app.torquevaultkmp.components.CircularIcon
|
||||
|
||||
@Composable
|
||||
fun BondedDeviceItem(/** device: Device, onDelete: (Device) -> Unit,*/ modifier: Modifier = Modifier) {
|
||||
val swipeToDismissBoxState = rememberSwipeToDismissBoxState(
|
||||
SwipeToDismissBoxValue.Settled,
|
||||
SwipeToDismissBoxDefaults.positionalThreshold
|
||||
)
|
||||
SwipeToDismissBox(
|
||||
state = swipeToDismissBoxState,
|
||||
modifier = modifier.fillMaxSize().padding(vertical = 4.dp),
|
||||
enableDismissFromStartToEnd = false,
|
||||
backgroundContent = {
|
||||
when (swipeToDismissBoxState.dismissDirection) {
|
||||
SwipeToDismissBoxValue.StartToEnd -> {
|
||||
SwipeToDismissBackgroundItem(label = "Swipe to delete item", contentAlignment = Alignment.CenterStart, state = swipeToDismissBoxState)
|
||||
}
|
||||
SwipeToDismissBoxValue.EndToStart -> {SwipeToDismissBackgroundItem(label = "Swipe to delete item", state = swipeToDismissBoxState)}
|
||||
SwipeToDismissBoxValue.Settled -> {}
|
||||
}
|
||||
},
|
||||
// onDismiss = {onDelete(device)},
|
||||
) {
|
||||
OutlinedCard(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
elevation = CardDefaults.cardElevation(defaultElevation = 4.dp)
|
||||
) {
|
||||
Row (
|
||||
modifier = Modifier.padding(16.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
CircularIcon( imageVector = Icons.Default.Bluetooth, backgroundColor = Color(0xFF0082FC))
|
||||
Spacer(modifier = Modifier.width(16.dp))
|
||||
Column {
|
||||
// Text(
|
||||
// text = device.name,
|
||||
// style = MaterialTheme.typography.titleMedium
|
||||
// )
|
||||
// Text(
|
||||
// text = device.id,
|
||||
// style = MaterialTheme.typography.bodyMedium,
|
||||
// color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
// )
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
internal fun SwipeToDismissBackgroundItem(
|
||||
label: String,
|
||||
contentAlignment: Alignment = Alignment.CenterEnd,
|
||||
icon: @Composable () -> Unit = {
|
||||
Icon(
|
||||
imageVector = Icons.Default.DeleteSweep,
|
||||
contentDescription = "Delete",
|
||||
tint = Color.White
|
||||
)
|
||||
},
|
||||
state: SwipeToDismissBoxState
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.clip(RoundedCornerShape(12.dp))
|
||||
.background(lerp(Color.LightGray, Color.Red, state.progress))
|
||||
.padding(end = 20.dp),
|
||||
contentAlignment = contentAlignment
|
||||
) {
|
||||
Row {
|
||||
Text(
|
||||
text = label,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
)
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
icon()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun BondedDeviceItemPreview() {
|
||||
BondedDeviceItem(
|
||||
// Device("AA:BB:CC:DD:EE", "Device 1"),
|
||||
// onDelete = {}
|
||||
)
|
||||
}
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
package com.digitoolsolutions.app.torquevaultkmp.screens.bonded.view
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.BluetoothDisabled
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
||||
@Composable
|
||||
fun EmptyBondedView() {
|
||||
Column(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
verticalArrangement = Arrangement.Center,
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.BluetoothDisabled,
|
||||
contentDescription = null,
|
||||
tint = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
modifier = Modifier.size(64.dp)
|
||||
)
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
Text(
|
||||
text = "No bonded devices found",
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
}
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
package com.digitoolsolutions.app.torquevaultkmp.screens.home
|
||||
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.Home
|
||||
import com.digitoolsolutions.app.torquevaultkmp.screens.AppIcon
|
||||
import com.digitoolsolutions.app.torquevaultkmp.screens.Destinations
|
||||
|
||||
object HomeDestination: Destinations(
|
||||
label = "Home",
|
||||
icon = AppIcon.Vector(Icons.Outlined.Home),
|
||||
route = "home"
|
||||
)
|
||||
+104
@@ -0,0 +1,104 @@
|
||||
package com.digitoolsolutions.app.torquevaultkmp.screens.home
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.WindowInsets
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.imePadding
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Bluetooth
|
||||
import androidx.compose.material.icons.outlined.SyncAlt
|
||||
import androidx.compose.material3.CardDefaults
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.OutlinedCard
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.SnackbarHost
|
||||
import androidx.compose.material3.SnackbarHostState
|
||||
import androidx.compose.material3.SwipeToDismissBox
|
||||
import androidx.compose.material3.SwipeToDismissBoxDefaults
|
||||
import androidx.compose.material3.SwipeToDismissBoxValue
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.rememberSwipeToDismissBoxState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
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.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.navigation.NavController
|
||||
import com.digitoolsolutions.app.torquevaultkmp.components.AppBar
|
||||
import com.digitoolsolutions.app.torquevaultkmp.screens.home.views.HomeEmpty
|
||||
import com.digitoolsolutions.app.torquevaultkmp.screens.home.views.HomeItem
|
||||
import org.jetbrains.compose.resources.stringResource
|
||||
import torquevaultkmp.composeapp.generated.resources.Res
|
||||
import torquevaultkmp.composeapp.generated.resources.home_title
|
||||
//import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
//import androidx.navigation.NavController
|
||||
//import com.digitoolsolutions.android.components.ui.AppBar
|
||||
//import com.digitoolsolutions.android.components.ui.CircularIcon
|
||||
//import com.digitoolsolutions.android.home.view.HomeEmpty
|
||||
//import com.digitoolsolutions.android.home.view.HomeItem
|
||||
//import com.digitoolsolutions.android.scanner.viewmodel.ScannerViewModel
|
||||
//import com.digitoolsolutions.android.uart.spec.ConnectionState
|
||||
//import com.digitoolsolutions.android.uart.spec.UartDevice
|
||||
import kotlin.uuid.ExperimentalUuidApi
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class, ExperimentalUuidApi::class)
|
||||
@Composable
|
||||
fun HomeScreen(
|
||||
navController: NavController,
|
||||
// bleScannerViewModel: ScannerViewModel
|
||||
) {
|
||||
val snackbarHostState = remember { SnackbarHostState() }
|
||||
// val connectedDevice by bleScannerViewModel.connectedDevices.collectAsState(initial = emptyList())
|
||||
|
||||
Scaffold (
|
||||
modifier = Modifier.imePadding(),
|
||||
topBar = {
|
||||
AppBar(
|
||||
title = {Text(stringResource(Res.string.home_title))},
|
||||
)
|
||||
},
|
||||
snackbarHost = { SnackbarHost(snackbarHostState) })
|
||||
{ padding ->
|
||||
Column (
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.imePadding()
|
||||
.padding(padding)
|
||||
.padding(horizontal = 16.dp, vertical = 12.dp),
|
||||
) {
|
||||
// if(connectedDevice.isEmpty())
|
||||
// HomeEmpty()
|
||||
// else
|
||||
// LazyColumn {
|
||||
// items(connectedDevice) { device ->
|
||||
// HomeItem(device)
|
||||
// }
|
||||
// }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun HomeScreenPreview() {
|
||||
// HomeScreen(
|
||||
// navController = NavController(LocalContext.current),
|
||||
//// bleScannerViewModel = hiltViewModel()
|
||||
// )
|
||||
}
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
package com.digitoolsolutions.app.torquevaultkmp.screens.home.views
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.BluetoothConnected
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
||||
@Composable
|
||||
fun HomeEmpty() {
|
||||
Column(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
verticalArrangement = Arrangement.Center,
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Outlined.BluetoothConnected,
|
||||
contentDescription = null,
|
||||
tint = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
modifier = Modifier.size(64.dp)
|
||||
)
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
Text(
|
||||
text = "No active devices",
|
||||
style = MaterialTheme.typography.titleMedium
|
||||
)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
Text(
|
||||
text = "Currently no devices are connected.",
|
||||
style = MaterialTheme.typography.bodyMedium
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
fun HomeEmptyPreview() {
|
||||
HomeEmpty()
|
||||
}
|
||||
+73
@@ -0,0 +1,73 @@
|
||||
package com.digitoolsolutions.app.torquevaultkmp.screens.home.views
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.SyncAlt
|
||||
import androidx.compose.material3.CardDefaults
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.OutlinedCard
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.digitoolsolutions.app.torquevaultkmp.components.CircularIcon
|
||||
|
||||
//import com.digitoolsolutions.android.components.ui.CircularIcon
|
||||
//import com.digitoolsolutions.android.uart.spec.ConnectionState
|
||||
//import com.digitoolsolutions.android.uart.spec.UartDevice
|
||||
|
||||
|
||||
@Composable
|
||||
//fun HomeItem(device: UartDevice, modifier: Modifier = Modifier) {
|
||||
fun HomeItem(device: Any, modifier: Modifier = Modifier) {
|
||||
OutlinedCard(
|
||||
modifier = modifier.fillMaxWidth().padding(vertical = 4.dp),
|
||||
elevation = CardDefaults.cardElevation(defaultElevation = 4.dp)
|
||||
) {
|
||||
Row (
|
||||
modifier = Modifier.padding(16.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
CircularIcon(Icons.Outlined.SyncAlt)
|
||||
Spacer(modifier = Modifier.width(16.dp))
|
||||
Column(modifier = Modifier.weight(1f)) {
|
||||
// device.name?.let {
|
||||
// Text(
|
||||
// text = it,
|
||||
// style = MaterialTheme.typography.titleMedium
|
||||
// )
|
||||
// }
|
||||
// Text(
|
||||
// text = device.identifier,
|
||||
// style = MaterialTheme.typography.bodyMedium,
|
||||
// color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
// )
|
||||
}
|
||||
Spacer(modifier = Modifier.width(16.dp))
|
||||
// val statusColor = when (device.status) {
|
||||
// ConnectionState.CONNECTING -> MaterialTheme.colorScheme.onSurfaceVariant
|
||||
// ConnectionState.READY -> Color.Green
|
||||
// else -> MaterialTheme.colorScheme.error
|
||||
// }
|
||||
// Text(
|
||||
// text = device.status.name,
|
||||
// style = MaterialTheme.typography.bodyMedium,
|
||||
// color = statusColor
|
||||
// )
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun ItemPreview() {
|
||||
// HomeItem(device = UartDevice("AA:BB:CC:DD:EE", "Device 1"))
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
package com.digitoolsolutions.app.torquevaultkmp.screens.logs
|
||||
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.List
|
||||
import com.digitoolsolutions.app.torquevaultkmp.screens.AppIcon
|
||||
import com.digitoolsolutions.app.torquevaultkmp.screens.Destinations
|
||||
|
||||
object LogDestination: Destinations(
|
||||
label = "Logs",
|
||||
icon = AppIcon.Vector(Icons.AutoMirrored.Filled.List),
|
||||
route = "logs"
|
||||
)
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
//package com.digitoolsolutions.app.torquevaultkmp.screens.logs
|
||||
//
|
||||
//import androidx.compose.runtime.Composable
|
||||
//import androidx.compose.runtime.Stable
|
||||
//import androidx.compose.runtime.getValue
|
||||
//import androidx.compose.runtime.mutableStateOf
|
||||
//import androidx.compose.runtime.saveable.Saver
|
||||
//import androidx.compose.runtime.saveable.rememberSaveable
|
||||
//import androidx.compose.runtime.setValue
|
||||
//import com.digitoolsolutions.android.storage.model.LogFilter
|
||||
//
|
||||
//@Stable
|
||||
//interface LogFilterState {
|
||||
// val filters: List<LogFilter>
|
||||
//// val sortingOptions: List<LogSortOption>
|
||||
// val activeFilter: LogFilter
|
||||
//// val activeSort: LogSortOption
|
||||
// val isEmpty: Boolean
|
||||
//
|
||||
// fun setFilter(filter: LogFilter)
|
||||
//// fun sortBy(option: LogSortOption)
|
||||
// fun clear()
|
||||
//}
|
||||
//
|
||||
//val logFilterSaver = Saver<LogFilter, String>(
|
||||
// save = { filter ->
|
||||
// when (filter) {
|
||||
// LogFilter.All -> "All"
|
||||
// LogFilter.System -> "System"
|
||||
// LogFilter.Device -> "Device"
|
||||
// is LogFilter.ByDeviceId -> "ByDeviceId:${filter.deviceId}"
|
||||
// }
|
||||
// },
|
||||
// restore = { str ->
|
||||
// when {
|
||||
// str == "All" -> LogFilter.All
|
||||
// str == "System" -> LogFilter.System
|
||||
// str == "Device" -> LogFilter.Device
|
||||
// str.startsWith("ByDeviceId:") -> LogFilter.ByDeviceId(str.removePrefix("ByDeviceId:"))
|
||||
// else -> LogFilter.All
|
||||
// }
|
||||
// }
|
||||
//)
|
||||
//
|
||||
////val logSortSaver = Saver<LogSortOption, String>(
|
||||
//// save = { sort ->
|
||||
//// when (sort) {
|
||||
//// LogSortOption.ByDate -> "ByDate"
|
||||
//// }
|
||||
//// },
|
||||
//// restore = { str ->
|
||||
//// when (str) {
|
||||
//// "ByDate" -> LogSortOption.ByDate
|
||||
//// else -> LogSortOption.ByDate
|
||||
//// }
|
||||
//// }
|
||||
////)
|
||||
//
|
||||
//
|
||||
//@Composable
|
||||
//fun rememberLogFilterState(
|
||||
// filters: List<LogFilter> = listOf(LogFilter.All, LogFilter.System, LogFilter.Device),
|
||||
//// sortingOptions: List<LogSortOption> = listOf()
|
||||
//): LogFilterState {
|
||||
// var selectedFilter by rememberSaveable(stateSaver = logFilterSaver) {
|
||||
// mutableStateOf<LogFilter>(LogFilter.All)
|
||||
// }
|
||||
//// var selectedSort by rememberSaveable(stateSaver = logSortSaver) {
|
||||
//// mutableStateOf<LogSortOption>(LogSortOption.ByDate)
|
||||
//// }
|
||||
//
|
||||
// return object : LogFilterState {
|
||||
// override val filters = filters
|
||||
//// override val sortingOptions = sortingOptions
|
||||
// override val activeFilter: LogFilter = selectedFilter
|
||||
//// override val activeSort: LogSortOption = selectedSort
|
||||
// override val isEmpty: Boolean get() = selectedFilter == LogFilter.All // && selectedSort == LogSortOption.ByDate
|
||||
//
|
||||
// override fun setFilter(filter: LogFilter) { selectedFilter = filter }
|
||||
//// override fun sortBy(option: LogSortOption) { selectedSort = option }
|
||||
// override fun clear() {
|
||||
// selectedFilter = LogFilter.All
|
||||
//// selectedSort = LogSortOption.ByDate
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
+169
@@ -0,0 +1,169 @@
|
||||
package com.digitoolsolutions.app.torquevaultkmp.screens.logs
|
||||
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.imePadding
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.DeleteForever
|
||||
import androidx.compose.material.icons.outlined.FilterList
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.SnackbarHost
|
||||
import androidx.compose.material3.SnackbarHostState
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.material3.pulltorefresh.PullToRefreshBox
|
||||
import androidx.compose.material3.pulltorefresh.rememberPullToRefreshState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.navigation.NavController
|
||||
import com.digitoolsolutions.app.torquevaultkmp.screens.logs.view.FilterBottomSheet
|
||||
import com.digitoolsolutions.app.torquevaultkmp.components.AppBar
|
||||
import kotlinx.coroutines.launch
|
||||
import org.jetbrains.compose.resources.stringResource
|
||||
import torquevaultkmp.composeapp.generated.resources.Res
|
||||
import torquevaultkmp.composeapp.generated.resources.log_title
|
||||
import kotlin.uuid.ExperimentalUuidApi
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class, ExperimentalUuidApi::class)
|
||||
@Composable
|
||||
fun LogScreen(
|
||||
navController: NavController,
|
||||
// viewModel: LogViewModel = hiltViewModel()
|
||||
) {
|
||||
// val state = rememberLogFilterState()
|
||||
// val logs = viewModel.getLogs(filter = state.activeFilter).collectAsLazyPagingItems()
|
||||
|
||||
LogContent(
|
||||
// state = state,
|
||||
// logs = logs,
|
||||
// onRefresh = {logs.refresh()},
|
||||
// onDeleteAllLogs = {viewModel.removeAllLog()},
|
||||
)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
internal fun LogContent(
|
||||
// state: LogFilterState,
|
||||
// logs: LazyPagingItems<Log>,
|
||||
// onRefresh: () -> Unit,
|
||||
// onDeleteAllLogs: () -> Unit,
|
||||
) {
|
||||
val snackbarHostState = remember { SnackbarHostState() }
|
||||
var showConfirmDialog by remember { mutableStateOf(false) }
|
||||
var showBottomSheet by remember { mutableStateOf(false) }
|
||||
|
||||
Scaffold (
|
||||
modifier = Modifier.imePadding(),
|
||||
topBar = {
|
||||
AppBar(
|
||||
title = {Text(stringResource(Res.string.log_title))},
|
||||
actions = {
|
||||
IconButton(onClick = {showConfirmDialog = true}){
|
||||
Icon(
|
||||
Icons.Default.DeleteForever,
|
||||
contentDescription = "Delete all log",
|
||||
tint = Color.Red
|
||||
)
|
||||
}
|
||||
IconButton(onClick = {showBottomSheet = true}){
|
||||
Icon(
|
||||
Icons.Outlined.FilterList,
|
||||
contentDescription = "Filter logs",
|
||||
tint = Color.White
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
},
|
||||
snackbarHost = { SnackbarHost(snackbarHostState) })
|
||||
{ padding ->
|
||||
val pullToRefreshState = rememberPullToRefreshState()
|
||||
val scope = rememberCoroutineScope()
|
||||
PullToRefreshBox(
|
||||
isRefreshing = false,
|
||||
onRefresh = {
|
||||
// onRefresh()
|
||||
scope.launch {
|
||||
pullToRefreshState.animateToHidden()
|
||||
}
|
||||
},
|
||||
state = pullToRefreshState,
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.imePadding()
|
||||
.padding(padding)
|
||||
.padding(horizontal = 16.dp, vertical = 12.dp),
|
||||
){
|
||||
// if (logs.itemCount == 0) {
|
||||
// EmptyLogView()
|
||||
// }else {
|
||||
// LazyColumn{
|
||||
// items(logs.itemCount, key = { index -> logs[index]?.id ?: index }) { index ->
|
||||
// val log = logs[index]
|
||||
// log?.let { it ->
|
||||
// LogItem(it)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
}
|
||||
if (showConfirmDialog) {
|
||||
AlertDialog(
|
||||
onDismissRequest = { showConfirmDialog = false },
|
||||
title = { Text("Delete Logs") },
|
||||
text = { Text("Warning: This will permanently delete all current logs. This action cannot be undone.") },
|
||||
confirmButton = {
|
||||
TextButton(onClick = {
|
||||
// onDeleteAllLogs()
|
||||
showConfirmDialog = false
|
||||
}) {
|
||||
Text("Delete", color = Color.Red)
|
||||
}
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(onClick = { showConfirmDialog = false }) {
|
||||
Text("Cancel", color = Color.White)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
if (showBottomSheet) {
|
||||
FilterBottomSheet(
|
||||
// state = state,
|
||||
onDismissRequest = {showBottomSheet = false}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun LogScreenPreview() {
|
||||
// val fakeDevices = listOf(
|
||||
// Log(1, "Log 1", "This is log 1"),
|
||||
// Log(2, "Log 2", "This is log 2"),
|
||||
// Log(3, "Log 3", "This is log 3"),
|
||||
// )
|
||||
// val pagingData = PagingData.from(fakeDevices)
|
||||
// val lazyPagingItems = flowOf(pagingData).collectAsLazyPagingItems()
|
||||
|
||||
LogContent(
|
||||
// state = rememberLogFilterState(),
|
||||
// logs = lazyPagingItems,
|
||||
// onRefresh = {lazyPagingItems.refresh()},
|
||||
// onDeleteAllLogs = {}
|
||||
)
|
||||
}
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
package com.digitoolsolutions.app.torquevaultkmp.screens.logs.view
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.NoteAlt
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
||||
@Composable
|
||||
fun EmptyLogView() {
|
||||
Column(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
verticalArrangement = Arrangement.Center,
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Outlined.NoteAlt,
|
||||
contentDescription = null,
|
||||
tint = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
modifier = Modifier.size(64.dp)
|
||||
)
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
Text(
|
||||
text = "Empty logs",
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
}
|
||||
}
|
||||
+201
@@ -0,0 +1,201 @@
|
||||
package com.digitoolsolutions.app.torquevaultkmp.screens.logs.view
|
||||
|
||||
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.FlowRow
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.selection.selectable
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Delete
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.ModalBottomSheet
|
||||
import androidx.compose.material3.RadioButton
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.semantics.Role
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import org.jetbrains.compose.resources.stringResource
|
||||
import torquevaultkmp.composeapp.generated.resources.Res
|
||||
import torquevaultkmp.composeapp.generated.resources.clear
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun FilterBottomSheet(
|
||||
// state: LogFilterState,
|
||||
onDismissRequest: () -> Unit,
|
||||
) {
|
||||
ModalBottomSheet(
|
||||
onDismissRequest = {
|
||||
onDismissRequest()
|
||||
},
|
||||
shape = RoundedCornerShape(topStart = 16.dp, topEnd = 16.dp),
|
||||
containerColor = MaterialTheme.colorScheme.surface,
|
||||
tonalElevation = 16.dp,
|
||||
dragHandle = {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.padding(8.dp)
|
||||
.width(50.dp)
|
||||
.height(6.dp)
|
||||
.clip(RoundedCornerShape(50))
|
||||
.background(Color.White)
|
||||
)
|
||||
}
|
||||
) {
|
||||
FilterContent(/** state = state,*/ onDismissRequest = onDismissRequest)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
internal fun FilterContent(
|
||||
// state: LogFilterState,
|
||||
modifier: Modifier = Modifier,
|
||||
onDismissRequest: () -> Unit,
|
||||
) {
|
||||
Column(
|
||||
modifier = modifier,
|
||||
) {
|
||||
Header(
|
||||
modifier = Modifier.padding(horizontal = 16.dp),
|
||||
text = "Filter",
|
||||
isSelectedFilterEmpty = true ,// state.isEmpty,
|
||||
onFilterReset = {
|
||||
// state.clear()
|
||||
onDismissRequest()
|
||||
}
|
||||
)
|
||||
|
||||
// if (state.filters.isNotEmpty()) {
|
||||
// FlowRow(
|
||||
// modifier = Modifier.padding(horizontal = 8.dp),
|
||||
// horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
// verticalArrangement = Arrangement.spacedBy(0.dp),
|
||||
// ) {
|
||||
// state.filters.forEach { filter ->
|
||||
// Row(
|
||||
// modifier = Modifier
|
||||
// .clip(RoundedCornerShape(8.dp))
|
||||
// .selectable(
|
||||
// selected = state.activeFilter == filter,
|
||||
// onClick = { state.setFilter(filter) },
|
||||
// role = Role.RadioButton
|
||||
// )
|
||||
// .padding(end = 16.dp),
|
||||
// verticalAlignment = Alignment.CenterVertically
|
||||
// ) {
|
||||
// RadioButton(
|
||||
// selected = state.activeFilter == filter,
|
||||
// onClick = { state.setFilter(filter) }
|
||||
// )
|
||||
// Text(filter.toString().replaceFirstChar { it.uppercase() })
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// if (state.filters.isNotEmpty() && state.sortingOptions.isNotEmpty()) {
|
||||
// HorizontalDivider()
|
||||
//
|
||||
// Text(
|
||||
// text = "Sort by",
|
||||
// style = MaterialTheme.typography.titleMedium,
|
||||
// modifier = Modifier.padding(horizontal = 16.dp).padding(top = 8.dp)
|
||||
// )
|
||||
// }
|
||||
//
|
||||
// if (state.sortingOptions.isNotEmpty()) {
|
||||
// state.sortingOptions.forEach { sort ->
|
||||
// Row(
|
||||
// modifier = Modifier
|
||||
// .clip(RoundedCornerShape(8.dp))
|
||||
// .selectable(
|
||||
// selected = state.activeSort == sort,
|
||||
// onClick = { state.sortBy(sort) },
|
||||
// role = Role.RadioButton
|
||||
// )
|
||||
// .padding(horizontal = 16.dp),
|
||||
// verticalAlignment = Alignment.CenterVertically
|
||||
// ) {
|
||||
// RadioButton(
|
||||
// selected = state.activeSort == sort,
|
||||
// onClick = { state.sortBy(sort) }
|
||||
// )
|
||||
// Text(sort.toString().replaceFirstChar { it.uppercase() })
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
internal fun Header(
|
||||
text: String,
|
||||
isSelectedFilterEmpty: Boolean,
|
||||
modifier: Modifier = Modifier,
|
||||
onFilterReset: () -> Unit
|
||||
) {
|
||||
Row(
|
||||
modifier = modifier,
|
||||
horizontalArrangement = Arrangement.Start,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Text(
|
||||
text = text,
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
)
|
||||
TextButton(
|
||||
onClick = onFilterReset,
|
||||
enabled = !isSelectedFilterEmpty,
|
||||
) {
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Icon(
|
||||
Icons.Filled.Delete,
|
||||
contentDescription = null,
|
||||
)
|
||||
Text(
|
||||
text = stringResource(Res.string.clear),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
fun FilterBottomSheetPreview() {
|
||||
FilterBottomSheet(
|
||||
// state = rememberLogFilterState(),
|
||||
onDismissRequest = {}
|
||||
)
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
fun HeaderPreview(){
|
||||
Header(text = "Filter", isSelectedFilterEmpty = true, onFilterReset = {})
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
fun FilterContentPreview(){
|
||||
// FilterContent(state = rememberLogFilterState(), onDismissRequest = {})
|
||||
}
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
package com.digitoolsolutions.app.torquevaultkmp.screens.logs.view
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.DividerDefaults
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
//import com.digitoolsolutions.android.storage.data.local.db.entity.Log
|
||||
//import com.digitoolsolutions.android.storage.model.LogFilter
|
||||
//import java.time.Instant
|
||||
//import java.time.ZoneId
|
||||
//import java.time.format.DateTimeFormatter
|
||||
|
||||
@Composable
|
||||
fun LogItem(/** log: Log,*/ modifier: Modifier = Modifier) {
|
||||
// val formatter = DateTimeFormatter.ofPattern("MM/dd/yyyy HH:mm:ss")
|
||||
// .withZone(ZoneId.systemDefault())
|
||||
// val formattedTime = formatter.format(Instant.ofEpochMilli(log.timestamp))
|
||||
|
||||
Column(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
){
|
||||
// Column(
|
||||
// modifier = Modifier.padding(16.dp),
|
||||
// ) {
|
||||
// Text(
|
||||
// text = formattedTime,
|
||||
// style = MaterialTheme.typography.bodySmall
|
||||
// )
|
||||
// Text(
|
||||
// text = log.title,
|
||||
// style = MaterialTheme.typography.titleMedium
|
||||
// )
|
||||
// Text(
|
||||
// text = "${if(log.source == LogFilter.Device.toString()) log.deviceId ?: "Unknown" else log.source.replaceFirstChar{it.uppercase()}} - ${log.content}",
|
||||
// style = MaterialTheme.typography.bodyMedium,
|
||||
// color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
// )
|
||||
// }
|
||||
HorizontalDivider(Modifier, DividerDefaults.Thickness, DividerDefaults.color)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun BondedDeviceItemPreview() {
|
||||
LogItem(
|
||||
// Log( 1, "Log 1", "This is a log"),
|
||||
)
|
||||
}
|
||||
+214
@@ -0,0 +1,214 @@
|
||||
package com.digitoolsolutions.app.torquevaultkmp.screens.settings
|
||||
|
||||
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.imePadding
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.SnackbarHost
|
||||
import androidx.compose.material3.SnackbarHostState
|
||||
import androidx.compose.material3.Switch
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.input.PasswordVisualTransformation
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.navigation.NavController
|
||||
import com.digitoolsolutions.app.torquevaultkmp.components.AppBar
|
||||
import kotlinx.coroutines.launch
|
||||
import org.jetbrains.compose.resources.stringResource
|
||||
import torquevaultkmp.composeapp.generated.resources.Res
|
||||
import torquevaultkmp.composeapp.generated.resources.auto_connect
|
||||
import torquevaultkmp.composeapp.generated.resources.dark_theme
|
||||
import torquevaultkmp.composeapp.generated.resources.exit
|
||||
import torquevaultkmp.composeapp.generated.resources.logout
|
||||
import torquevaultkmp.composeapp.generated.resources.need_restart
|
||||
import torquevaultkmp.composeapp.generated.resources.restart_app_msg
|
||||
import torquevaultkmp.composeapp.generated.resources.save_settings
|
||||
import torquevaultkmp.composeapp.generated.resources.server_url
|
||||
import torquevaultkmp.composeapp.generated.resources.server_url_placeholder
|
||||
import torquevaultkmp.composeapp.generated.resources.setting_title
|
||||
import torquevaultkmp.composeapp.generated.resources.settings_saved_successfully
|
||||
import torquevaultkmp.composeapp.generated.resources.token
|
||||
|
||||
@Composable
|
||||
fun SettingScreen(
|
||||
navController: NavController,
|
||||
isDarkTheme: Boolean,
|
||||
onThemeToggle: (Boolean) -> Unit,
|
||||
// viewModel: SettingViewModel = hiltViewModel()
|
||||
) {
|
||||
// val serverUrl by viewModel.serverUrl.collectAsState()
|
||||
// val token by viewModel.refreshToken.collectAsState()
|
||||
// val autoConnect by viewModel.autoConnect.collectAsState()
|
||||
val serverUrl = ""
|
||||
val token = ""
|
||||
val autoConnect = ""
|
||||
|
||||
SettingContent(
|
||||
isDarkTheme = isDarkTheme,
|
||||
onThemeToggle = onThemeToggle,
|
||||
serverUrl = serverUrl,
|
||||
onServerUrlChange = { /** viewModel.updateServerUrl(it) */ },
|
||||
token = token,
|
||||
autoConnect = autoConnect == "true",
|
||||
onTokenChange = { /** viewModel.updateRefreshToken(it) */ },
|
||||
onSaveSettings = { /** viewModel.saveSettings() */ },
|
||||
onLogout = {
|
||||
// viewModel.logout()
|
||||
// navController.navigate(AuthDestination.route)
|
||||
},
|
||||
onChangeAutoConnect = { /** viewModel.updateAutoConnect(it) */ }
|
||||
)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun SettingContent(
|
||||
isDarkTheme: Boolean,
|
||||
onThemeToggle: (Boolean) -> Unit,
|
||||
serverUrl: String,
|
||||
onServerUrlChange: (String) -> Unit,
|
||||
token: String,
|
||||
autoConnect: Boolean,
|
||||
onTokenChange: (String) -> Unit,
|
||||
onSaveSettings: () -> Unit,
|
||||
onLogout: () -> Unit,
|
||||
onChangeAutoConnect: (Boolean) -> Unit
|
||||
) {
|
||||
// val context = LocalContext.current
|
||||
val snackbarHostState = remember { SnackbarHostState() }
|
||||
val scope = rememberCoroutineScope()
|
||||
// remember the initial serverUrl to check if it has changed when saving
|
||||
val oldUrl by remember { mutableStateOf(serverUrl) }
|
||||
var showRestartDialog by remember { mutableStateOf(false) }
|
||||
val message = stringResource(Res.string.settings_saved_successfully)
|
||||
|
||||
if (showRestartDialog) {
|
||||
AlertDialog(
|
||||
onDismissRequest = { },
|
||||
title = { Text(stringResource(Res.string.need_restart)) },
|
||||
text = { Text(stringResource(Res.string.restart_app_msg)) },
|
||||
confirmButton = {
|
||||
TextButton (onClick = { onLogout() }) {
|
||||
Text(stringResource(Res.string.exit))
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
Scaffold (
|
||||
modifier = Modifier.imePadding(),
|
||||
topBar = {
|
||||
AppBar(
|
||||
title = {Text(stringResource(Res.string.setting_title))},
|
||||
)
|
||||
},
|
||||
snackbarHost = { SnackbarHost(snackbarHostState) })
|
||||
{ padding ->
|
||||
Column (
|
||||
modifier = Modifier.fillMaxSize().imePadding().padding(padding).padding(16.dp),
|
||||
) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
Text(stringResource(Res.string.dark_theme))
|
||||
Spacer(modifier = Modifier.weight(1f))
|
||||
Switch(
|
||||
checked = isDarkTheme,
|
||||
onCheckedChange = { onThemeToggle(it) }
|
||||
)
|
||||
}
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
Text(stringResource(Res.string.auto_connect))
|
||||
Spacer(modifier = Modifier.weight(1f))
|
||||
Switch(
|
||||
checked = autoConnect,
|
||||
onCheckedChange = { onChangeAutoConnect(it) }
|
||||
)
|
||||
}
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
OutlinedTextField(
|
||||
value = serverUrl,
|
||||
onValueChange = { onServerUrlChange(it) },
|
||||
label = { Text(stringResource(Res.string.server_url)) },
|
||||
placeholder = { Text(stringResource(Res.string.server_url_placeholder)) },
|
||||
singleLine = true,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
OutlinedTextField(
|
||||
value = token,
|
||||
onValueChange = { onTokenChange(it) },
|
||||
label = { Text(stringResource(Res.string.token)) },
|
||||
singleLine = true,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
readOnly = true
|
||||
// visualTransformation = PasswordVisualTransformation(),
|
||||
)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
Button(
|
||||
onClick = {
|
||||
onSaveSettings()
|
||||
if(oldUrl.trimEnd('/') != serverUrl.trimEnd('/'))
|
||||
showRestartDialog = true
|
||||
else
|
||||
scope.launch {
|
||||
snackbarHostState.showSnackbar(message)
|
||||
}
|
||||
},
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
Text(stringResource(Res.string.save_settings))
|
||||
}
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
Button(
|
||||
onClick = {
|
||||
onLogout()
|
||||
},
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
Text(stringResource(Res.string.logout))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun SettingScreenPreview() {
|
||||
SettingContent(
|
||||
isDarkTheme = true,
|
||||
onThemeToggle = {},
|
||||
serverUrl = "https://example.com",
|
||||
onServerUrlChange = {},
|
||||
token = "dummy_token",
|
||||
onTokenChange = {},
|
||||
onSaveSettings = {},
|
||||
onLogout = {},
|
||||
autoConnect = false,
|
||||
onChangeAutoConnect = {},
|
||||
)
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
package com.digitoolsolutions.app.torquevaultkmp.screens.settings
|
||||
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.Settings
|
||||
import com.digitoolsolutions.app.torquevaultkmp.screens.AppIcon
|
||||
import com.digitoolsolutions.app.torquevaultkmp.screens.Destinations
|
||||
|
||||
object SettingDestination: Destinations(
|
||||
label = "Settings",
|
||||
icon = AppIcon.Vector(Icons.Outlined.Settings),
|
||||
route = "settings"
|
||||
)
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
package com.digitoolsolutions.app.torquevaultkmp.theme
|
||||
|
||||
import androidx.compose.ui.graphics.Color
|
||||
|
||||
val Purple80 = Color(0xFFFFBCF0)
|
||||
val PurpleGrey80 = Color(0xFFCCC2DC)
|
||||
val Pink80 = Color(0xFFEFB8C8)
|
||||
|
||||
val Purple40 = Color(0xFF6650a4)
|
||||
val PurpleGrey40 = Color(0xFF625b71)
|
||||
val Pink40 = Color(0xFF7D5260)
|
||||
|
||||
val AppBarColor = Color(0xFF00A9CE)
|
||||
val ColorError = Color(0xFFBA1B1B)
|
||||
val ColorWarning = Color(0xFFF1750B)
|
||||
val ColorSuccess = Color(0xFF008D45)
|
||||
val ColorNeutral = Color(0xFF70787C)
|
||||
val ColorNordicLogo = Color(0xFF00A9CE)
|
||||
val ColorNordicLogoText = Color.Black
|
||||
+77
@@ -0,0 +1,77 @@
|
||||
package com.digitoolsolutions.android.torquevault.ui.theme
|
||||
|
||||
//import android.os.Build
|
||||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.darkColorScheme
|
||||
//import androidx.compose.material3.dynamicDarkColorScheme
|
||||
//import androidx.compose.material3.dynamicLightColorScheme
|
||||
import androidx.compose.material3.lightColorScheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.graphics.Color
|
||||
//import androidx.compose.ui.platform.LocalContext
|
||||
import com.digitoolsolutions.app.torquevaultkmp.theme.Typography
|
||||
|
||||
|
||||
//private val DarkColorScheme = darkColorScheme(
|
||||
// primary = Color(0xFF8B0000),
|
||||
// secondary = Color(0xFF616161),
|
||||
// tertiary = Color(0xFFFFA000)
|
||||
//)
|
||||
//
|
||||
//private val LightColorScheme = lightColorScheme(
|
||||
// primary = Purple40,
|
||||
// secondary = PurpleGrey40,
|
||||
// tertiary = Pink40
|
||||
//
|
||||
//
|
||||
//)
|
||||
private val DarkColorScheme = darkColorScheme(
|
||||
primary = Color(0xFFB71C1C),
|
||||
secondary = Color(0xFF424242),
|
||||
tertiary = Color(0xFFFFA000),
|
||||
background = Color(0xFF121212),
|
||||
surface = Color(0xFF1E1E1E),
|
||||
onPrimary = Color.White,
|
||||
onSecondary = Color.White,
|
||||
onTertiary = Color.Black,
|
||||
onBackground = Color.White,
|
||||
onSurface = Color.White
|
||||
)
|
||||
|
||||
private val LightColorScheme = lightColorScheme(
|
||||
primary = Color(0xFFB71C1C),
|
||||
secondary = Color(0xFF616161),
|
||||
tertiary = Color(0xFFFFC107),
|
||||
background = Color(0xFFFFFBFE),
|
||||
surface = Color(0xFFFFFBFE),
|
||||
onPrimary = Color.White,
|
||||
onSecondary = Color.White,
|
||||
onTertiary = Color.Black,
|
||||
onBackground = Color(0xFF1C1B1F),
|
||||
onSurface = Color(0xFF1C1B1F)
|
||||
)
|
||||
|
||||
@Composable
|
||||
fun TorqueVaultTheme(
|
||||
darkTheme: Boolean = isSystemInDarkTheme(),
|
||||
// Dynamic color is available on Android 12+
|
||||
dynamicColor: Boolean = true,
|
||||
content: @Composable () -> Unit
|
||||
) {
|
||||
val colorScheme = when {
|
||||
// dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
|
||||
// val context = LocalContext.current
|
||||
// if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
|
||||
// }
|
||||
|
||||
darkTheme -> DarkColorScheme
|
||||
else -> LightColorScheme
|
||||
}
|
||||
|
||||
MaterialTheme(
|
||||
colorScheme = colorScheme,
|
||||
typography = Typography,
|
||||
content = content
|
||||
)
|
||||
}
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
package com.digitoolsolutions.app.torquevaultkmp.theme
|
||||
|
||||
import androidx.compose.material3.Typography
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.font.FontFamily
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.sp
|
||||
|
||||
// Set of Material typography styles to start with
|
||||
|
||||
val Typography = Typography(
|
||||
bodyLarge = TextStyle(
|
||||
fontFamily = FontFamily.Default,
|
||||
fontWeight = FontWeight.Normal,
|
||||
fontSize = 16.sp,
|
||||
lineHeight = 24.sp,
|
||||
letterSpacing = 0.5.sp
|
||||
)
|
||||
/* Other default text styles to override
|
||||
titleLarge = TextStyle(
|
||||
fontFamily = FontFamily.Default,
|
||||
fontWeight = FontWeight.Normal,
|
||||
fontSize = 22.sp,
|
||||
lineHeight = 28.sp,
|
||||
letterSpacing = 0.sp
|
||||
),
|
||||
labelSmall = TextStyle(
|
||||
fontFamily = FontFamily.Default,
|
||||
fontWeight = FontWeight.Medium,
|
||||
fontSize = 11.sp,
|
||||
lineHeight = 16.sp,
|
||||
letterSpacing = 0.5.sp
|
||||
)
|
||||
*/
|
||||
)
|
||||
Reference in New Issue
Block a user