android: log screen - level filter chips, snackbar instead of toast
This commit is contained in:
1 parent
42cd361c58
commit
8bedf432e2
5 files changed
+100
-29
No files matched your search
@@ -46,6 +46,13 @@ import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.isActive
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
import org.strongswan.android.R
|
||||
import org.strongswan.android.data.LogContentProvider
|
||||
import org.strongswan.android.logic.CharonVpnService
|
||||
@@ -59,13 +66,15 @@ import java.io.FileReader
|
||||
|
||||
class LogActivity : AppCompatActivity()
|
||||
{
|
||||
private val messages = kotlinx.coroutines.flow.MutableSharedFlow<Int>(extraBufferCapacity = 1)
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?)
|
||||
{
|
||||
super.onCreate(savedInstanceState)
|
||||
enableEdgeToEdge()
|
||||
setContent {
|
||||
ShxTheme {
|
||||
LogScreen(onBack = { finish() }, onSend = { sendLog() })
|
||||
LogScreen(onBack = { finish() }, onSend = { sendLog() }, messages = messages)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -80,7 +89,7 @@ class LogActivity : AppCompatActivity()
|
||||
).filter { it.exists() && it.length() > 0L }
|
||||
if (files.isEmpty())
|
||||
{
|
||||
Toast.makeText(this, R.string.empty_log, Toast.LENGTH_SHORT).show()
|
||||
messages.tryEmit(R.string.empty_log)
|
||||
return
|
||||
}
|
||||
var version = ""
|
||||
@@ -165,17 +174,47 @@ class LogViewModel : ViewModel()
|
||||
}
|
||||
}
|
||||
|
||||
private enum class LogLevelFilter(val labelRes: Int)
|
||||
{
|
||||
ALL(R.string.log_filter_all),
|
||||
WARN(R.string.log_filter_warn),
|
||||
ERROR(R.string.log_filter_error),
|
||||
}
|
||||
|
||||
private fun String.isErrorLine() = contains(" ERROR ")
|
||||
|
||||
private fun String.isWarnLine() = contains(" WARN ") || contains(" WARNING ")
|
||||
|
||||
@OptIn(androidx.compose.material3.ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
private fun LogScreen(onBack: () -> Unit, onSend: () -> Unit, viewModel: LogViewModel = viewModel())
|
||||
private fun LogScreen(
|
||||
onBack: () -> Unit,
|
||||
onSend: () -> Unit,
|
||||
messages: kotlinx.coroutines.flow.Flow<Int>,
|
||||
viewModel: LogViewModel = viewModel(),
|
||||
)
|
||||
{
|
||||
val context = LocalContext.current
|
||||
val lines by viewModel.lines.collectAsStateWithLifecycle()
|
||||
val allLines by viewModel.lines.collectAsStateWithLifecycle()
|
||||
val listState = rememberLazyListState()
|
||||
val snackbarHostState = remember { androidx.compose.material3.SnackbarHostState() }
|
||||
var filter by rememberSaveable { mutableStateOf(LogLevelFilter.ALL) }
|
||||
val lines = remember(allLines, filter) {
|
||||
when (filter)
|
||||
{
|
||||
LogLevelFilter.ALL -> allLines
|
||||
LogLevelFilter.WARN -> allLines.filter { it.isErrorLine() || it.isWarnLine() }
|
||||
LogLevelFilter.ERROR -> allLines.filter { it.isErrorLine() }
|
||||
}
|
||||
}
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
viewModel.start(File(context.filesDir, CharonVpnService.LOG_FILE))
|
||||
}
|
||||
LaunchedEffect(lines.size) {
|
||||
LaunchedEffect(messages) {
|
||||
messages.collect { snackbarHostState.showSnackbar(context.getString(it)) }
|
||||
}
|
||||
LaunchedEffect(lines.size, filter) {
|
||||
if (lines.isNotEmpty())
|
||||
{
|
||||
listState.scrollToItem(lines.lastIndex)
|
||||
@@ -190,36 +229,57 @@ private fun LogScreen(onBack: () -> Unit, onSend: () -> Unit, viewModel: LogView
|
||||
Icon(Icons.Default.Share, contentDescription = stringResource(R.string.send_log))
|
||||
}
|
||||
},
|
||||
snackbarHost = { org.strongswan.android.ui.compose.components.ShxSnackbarHost(snackbarHostState) },
|
||||
) { padding ->
|
||||
if (lines.isEmpty())
|
||||
Column(Modifier.fillMaxSize().padding(padding))
|
||||
{
|
||||
Text(
|
||||
stringResource(R.string.empty_log),
|
||||
modifier = Modifier.padding(padding).padding(ShxSpacing.xl),
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
Row(
|
||||
modifier = Modifier.padding(horizontal = ShxSpacing.lg),
|
||||
horizontalArrangement = Arrangement.spacedBy(ShxSpacing.sm),
|
||||
)
|
||||
}
|
||||
else
|
||||
{
|
||||
LazyColumn(
|
||||
state = listState,
|
||||
modifier = Modifier.fillMaxSize().padding(padding).padding(horizontal = ShxSpacing.md),
|
||||
) {
|
||||
items(lines.size) { index ->
|
||||
val line = lines[index]
|
||||
val isError = line.contains(" ERROR ", ignoreCase = false)
|
||||
Text(
|
||||
line,
|
||||
style = MaterialTheme.typography.bodySmall.copy(
|
||||
fontSize = 12.sp,
|
||||
fontFamily = FontFamily.Monospace,
|
||||
),
|
||||
color = if (isError) MaterialTheme.colorScheme.error
|
||||
else MaterialTheme.colorScheme.onSurface,
|
||||
modifier = Modifier.padding(vertical = 2.dp),
|
||||
{
|
||||
LogLevelFilter.entries.forEach { level ->
|
||||
androidx.compose.material3.FilterChip(
|
||||
selected = filter == level,
|
||||
onClick = { filter = level },
|
||||
label = { Text(stringResource(level.labelRes)) },
|
||||
)
|
||||
}
|
||||
}
|
||||
if (lines.isEmpty())
|
||||
{
|
||||
Text(
|
||||
stringResource(R.string.empty_log),
|
||||
modifier = Modifier.padding(ShxSpacing.xl),
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
}
|
||||
else
|
||||
{
|
||||
LazyColumn(
|
||||
state = listState,
|
||||
modifier = Modifier.fillMaxSize().padding(horizontal = ShxSpacing.md),
|
||||
) {
|
||||
items(lines.size) { index ->
|
||||
val line = lines[index]
|
||||
val color = when
|
||||
{
|
||||
line.isErrorLine() -> MaterialTheme.colorScheme.error
|
||||
line.isWarnLine() -> MaterialTheme.colorScheme.tertiary
|
||||
else -> MaterialTheme.colorScheme.onSurface
|
||||
}
|
||||
Text(
|
||||
line,
|
||||
style = MaterialTheme.typography.bodySmall.copy(
|
||||
fontSize = 12.sp,
|
||||
fontFamily = FontFamily.Monospace,
|
||||
),
|
||||
color = color,
|
||||
modifier = Modifier.padding(vertical = 2.dp),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+2
@@ -33,10 +33,12 @@ fun SecondaryScaffold(
|
||||
title: String,
|
||||
onBack: () -> Unit,
|
||||
actions: @Composable RowScope.() -> Unit = {},
|
||||
snackbarHost: @Composable () -> Unit = {},
|
||||
content: @Composable (PaddingValues) -> Unit,
|
||||
)
|
||||
{
|
||||
Scaffold(
|
||||
snackbarHost = snackbarHost,
|
||||
topBar = {
|
||||
TopAppBar(
|
||||
title = { Text(title, maxLines = 1, overflow = TextOverflow.Ellipsis, fontWeight = FontWeight.SemiBold) },
|
||||
|
||||
@@ -506,4 +506,7 @@
|
||||
|
||||
<string name="profile_detail_placeholder">Profil auswählen oder hinzufügen</string>
|
||||
<string name="update_downloading_title">Update %1$s wird heruntergeladen</string>
|
||||
<string name="log_filter_all">Alle</string>
|
||||
<string name="log_filter_warn">Warnungen</string>
|
||||
<string name="log_filter_error">Fehler</string>
|
||||
</resources>
|
||||
@@ -503,4 +503,7 @@
|
||||
<string name="widget_description">Статус защиты и подключение одним касанием</string>
|
||||
<string name="profile_detail_placeholder">Выберите или добавьте профиль</string>
|
||||
<string name="update_downloading_title">Загрузка обновления %1$s</string>
|
||||
<string name="log_filter_all">Все</string>
|
||||
<string name="log_filter_warn">Предупреждения</string>
|
||||
<string name="log_filter_error">Ошибки</string>
|
||||
</resources>
|
||||
@@ -508,4 +508,7 @@
|
||||
<string name="widget_description">Protection status and one-tap connect</string>
|
||||
<string name="profile_detail_placeholder">Select or add a profile</string>
|
||||
<string name="update_downloading_title">Downloading update %1$s</string>
|
||||
<string name="log_filter_all">All</string>
|
||||
<string name="log_filter_warn">Warnings</string>
|
||||
<string name="log_filter_error">Errors</string>
|
||||
</resources>
|
||||
Reference in new issue
Block a user