diff --git a/.idea/compiler.xml b/.idea/compiler.xml
index fb7f4a8..fcb19bf 100644
--- a/.idea/compiler.xml
+++ b/.idea/compiler.xml
@@ -1,6 +1,6 @@
-
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
index a8286ae..8859ca8 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -4,7 +4,7 @@
-
+
\ No newline at end of file
diff --git a/src/main/kotlin/com/jerryjeon/logjerry/Main.kt b/src/main/kotlin/com/jerryjeon/logjerry/Main.kt
index 87aeb28..ba8cd4e 100644
--- a/src/main/kotlin/com/jerryjeon/logjerry/Main.kt
+++ b/src/main/kotlin/com/jerryjeon/logjerry/Main.kt
@@ -23,7 +23,10 @@ import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
-import androidx.compose.ui.window.*
+import androidx.compose.ui.window.MenuBar
+import androidx.compose.ui.window.Window
+import androidx.compose.ui.window.WindowState
+import androidx.compose.ui.window.application
import com.jerryjeon.logjerry.log.Log
import com.jerryjeon.logjerry.parse.ParseStatus
import com.jerryjeon.logjerry.preferences.ColorTheme
@@ -106,6 +109,7 @@ private fun GettingStartedView(notStarted: ParseStatus.NotStarted, changeSource:
?.let { changeSource(Source.Text(it.toString())) }
true
}
+
else -> {
false
}
@@ -296,16 +300,31 @@ private fun TabView(tabs: Tabs, activate: (Tab) -> Unit, close: (Tab) -> Unit) {
val scrollState = rememberScrollState()
Row(modifier = Modifier.fillMaxWidth().height(IntrinsicSize.Min).horizontalScroll(scrollState)) {
tabList.forEach { tab ->
- Row(
- modifier = Modifier
- .background(if (tab === activated) MaterialTheme.colors.secondary else Color.Transparent)
- .clickable { activate(tab) }
- .padding(8.dp)
- ) {
- Text(tab.name, modifier = Modifier.align(Alignment.CenterVertically), style = MaterialTheme.typography.body2)
- Spacer(Modifier.width(8.dp))
- IconButton(modifier = Modifier.size(16.dp).align(Alignment.CenterVertically), onClick = { close(tab) }) {
- Icon(Icons.Default.Close, "Close tab")
+ Column(modifier = Modifier.width(IntrinsicSize.Max).clickable { activate(tab) }) {
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(start = 12.dp, end = 12.dp, top = 12.dp)
+ ) {
+ Text(
+ tab.name,
+ modifier = Modifier.align(Alignment.CenterVertically),
+ style = MaterialTheme.typography.body2,
+ maxLines = 1
+ )
+ Spacer(Modifier.width(8.dp))
+ IconButton(
+ modifier = Modifier.size(16.dp).align(Alignment.CenterVertically),
+ onClick = { close(tab) }
+ ) {
+ Icon(Icons.Default.Close, "Close tab")
+ }
+ }
+ if (tab === activated) {
+ Box(modifier = Modifier.fillMaxWidth().height(7.dp))
+ Divider(modifier = Modifier.fillMaxWidth().height(5.dp), color = MaterialTheme.colors.primary)
+ } else {
+ Box(modifier = Modifier.fillMaxWidth().height(12.dp))
}
}
Divider(modifier = Modifier.fillMaxHeight().width(1.dp))
diff --git a/src/main/kotlin/com/jerryjeon/logjerry/log/Log.kt b/src/main/kotlin/com/jerryjeon/logjerry/log/Log.kt
index 33e9897..4e185a1 100644
--- a/src/main/kotlin/com/jerryjeon/logjerry/log/Log.kt
+++ b/src/main/kotlin/com/jerryjeon/logjerry/log/Log.kt
@@ -1,4 +1,11 @@
package com.jerryjeon.logjerry.log
+
+import java.time.Duration
+import java.time.LocalDate
+import java.time.LocalDateTime
+import java.time.LocalTime
+import java.time.format.DateTimeFormatter
+
data class Log(
val number: Int,
val date: String?,
@@ -12,6 +19,32 @@ data class Log(
) {
val index = number - 1
val priority = Priority.find(priorityText)
+
+ val localDateTime: LocalDateTime?
+ get() = try {
+ if (date != null && time != null) {
+ LocalDateTime.parse("$date $time", formatter)
+ } else if (time != null) {
+ // Assume date is today.
+ LocalTime.parse(time, timeFormatter).atDate(LocalDate.now())
+ } else {
+ null
+ }
+ } catch (e: Exception) {
+ e.printStackTrace()
+ null
+ }
+
+ fun durationBetween(other: Log): Duration? {
+ val thisLocalDateTime = localDateTime ?: return null
+ val otherLocalDateTime = other.localDateTime ?: return null
+ return Duration.between(thisLocalDateTime, otherLocalDateTime)
+ }
+
+ companion object {
+ val formatter: DateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS")
+ val timeFormatter: DateTimeFormatter = DateTimeFormatter.ofPattern("HH:mm:ss.SSS")
+ }
}
enum class Priority(val text: String, val fullText: String, val level: Int) {
diff --git a/src/main/kotlin/com/jerryjeon/logjerry/log/ParseCompleted.kt b/src/main/kotlin/com/jerryjeon/logjerry/log/ParseCompleted.kt
index 9581e37..25ad9b2 100644
--- a/src/main/kotlin/com/jerryjeon/logjerry/log/ParseCompleted.kt
+++ b/src/main/kotlin/com/jerryjeon/logjerry/log/ParseCompleted.kt
@@ -58,6 +58,7 @@ class ParseCompleted(
detectionFinishedFlow
) { filteredLogs, detectionFinished ->
val allDetections = mutableMapOf>()
+ var lastRefinedLog: RefinedLog? = null
val refinedLogs = filteredLogs.map { log ->
val detections = detectionFinished.detectionsByLog[log] ?: emptyMap()
detections.forEach { (key, newValue) ->
@@ -69,7 +70,10 @@ class ParseCompleted(
// TODO don't want to repeat all annotate if just one log has changed. How can I achieve it
val logContents =
LogAnnotation.separateAnnotationStrings(log, detections.values.flatten())
- RefinedLog(log, detections, LogAnnotation.annotate(log, logContents, detectionFinished.detectors))
+ val timeGap = lastRefinedLog?.log?.durationBetween(log)?.takeIf { it.toSeconds() >= 3 }
+ RefinedLog(log, detections, LogAnnotation.annotate(log, logContents, detectionFinished.detectors), timeGap).also {
+ lastRefinedLog = it
+ }
}
RefineResult(refinedLogs, allDetections)
}
diff --git a/src/main/kotlin/com/jerryjeon/logjerry/logview/MarkInfo.kt b/src/main/kotlin/com/jerryjeon/logjerry/logview/MarkInfo.kt
new file mode 100644
index 0000000..727b190
--- /dev/null
+++ b/src/main/kotlin/com/jerryjeon/logjerry/logview/MarkInfo.kt
@@ -0,0 +1,12 @@
+package com.jerryjeon.logjerry.logview
+
+sealed class MarkInfo {
+ class Marked(
+ val markedLog: RefinedLog,
+ ) : MarkInfo()
+
+ class StatBetweenMarks(
+ val logCount: Int,
+ val duration: String?, // ex) 1h 2m 3s, and null if it's not able to calculate
+ ) : MarkInfo()
+}
diff --git a/src/main/kotlin/com/jerryjeon/logjerry/logview/RefineResult.kt b/src/main/kotlin/com/jerryjeon/logjerry/logview/RefineResult.kt
index 5f1a3ad..0a68969 100644
--- a/src/main/kotlin/com/jerryjeon/logjerry/logview/RefineResult.kt
+++ b/src/main/kotlin/com/jerryjeon/logjerry/logview/RefineResult.kt
@@ -3,7 +3,6 @@ package com.jerryjeon.logjerry.logview
import com.jerryjeon.logjerry.detector.Detection
import com.jerryjeon.logjerry.detector.DetectionStatus
import com.jerryjeon.logjerry.detector.DetectorKey
-import com.jerryjeon.logjerry.detector.MarkDetection
import com.jerryjeon.logjerry.ui.focus.DetectionFocus
import com.jerryjeon.logjerry.ui.focus.LogFocus
import kotlinx.coroutines.flow.MutableStateFlow
@@ -13,8 +12,6 @@ data class RefineResult(
val refinedLogs: List,
val allDetections: Map>
) {
- val markedRows = refinedLogs.filter { it.marked }
-
val currentFocus = MutableStateFlow(null)
val statusByKey = MutableStateFlow(
@@ -28,6 +25,41 @@ data class RefineResult(
}
)
+ val markInfos: List
+
+ init {
+ val markedLogs = refinedLogs.filter { it.marked }
+ markInfos = if (markedLogs.isEmpty()) {
+ emptyList()
+ } else {
+ val markInfos = mutableListOf()
+ markedLogs
+ .scan(refinedLogs.first()) { prevRefinedLog, refinedLog ->
+ val duration = prevRefinedLog.durationBetween(refinedLog)
+ markInfos.add(
+ MarkInfo.StatBetweenMarks(
+ logCount = refinedLogs.indexOf(refinedLog) - refinedLogs.indexOf(prevRefinedLog),
+ duration = duration?.toHumanReadable()
+ )
+ )
+ markInfos.add(MarkInfo.Marked(refinedLog))
+ refinedLog
+ }
+
+ val lastLog = refinedLogs.last()
+ val lastMarkedLogs = markedLogs.last()
+ val duration = lastMarkedLogs.durationBetween(lastLog)
+
+ markInfos.add(
+ MarkInfo.StatBetweenMarks(
+ logCount = refinedLogs.indexOf(lastLog) - refinedLogs.indexOf(lastMarkedLogs),
+ duration = duration?.toHumanReadable()
+ )
+ )
+ markInfos
+ }
+ }
+
fun selectPreviousDetection(status: DetectionStatus) {
val previousIndex = if (status.currentIndex <= 0) {
status.allDetections.size - 1
@@ -68,7 +100,7 @@ data class RefineResult(
statusByKey.value[key]?.let { selectNextDetection(it) }
}
- fun selectDetection(detection: MarkDetection) {
+ fun selectDetection(detection: Detection) {
statusByKey.update {
val status = it[detection.key] ?: return@update it
val index = status.allDetections.indexOf(detection)
diff --git a/src/main/kotlin/com/jerryjeon/logjerry/logview/RefinedLog.kt b/src/main/kotlin/com/jerryjeon/logjerry/logview/RefinedLog.kt
index dd27488..1beb613 100644
--- a/src/main/kotlin/com/jerryjeon/logjerry/logview/RefinedLog.kt
+++ b/src/main/kotlin/com/jerryjeon/logjerry/logview/RefinedLog.kt
@@ -5,12 +5,36 @@ import com.jerryjeon.logjerry.detector.DetectorKey
import com.jerryjeon.logjerry.detector.MarkDetection
import com.jerryjeon.logjerry.log.Log
import com.jerryjeon.logjerry.log.LogContentView
+import java.time.Duration
class RefinedLog(
val log: Log,
val detections: Map>,
- val logContentViews: List
+ val logContentViews: List,
+ val timeGap: Duration?
) {
val mark = detections[DetectorKey.Mark]?.firstOrNull() as? MarkDetection
val marked = mark != null
+
+ fun durationBetween(other: RefinedLog): Duration? {
+ return this.log.durationBetween(other.log)
+ }
+}
+
+fun Duration.toHumanReadable(): String {
+ val hours: Long = toHours()
+ val minutes: Long = toMinutes() % 60
+ val seconds: Long = seconds % 60
+
+ return when {
+ hours > 0 -> {
+ "${hours}h ${minutes}m ${seconds}s"
+ }
+ minutes > 0 -> {
+ "${minutes}m ${seconds}s"
+ }
+ else -> {
+ "${toMillis()}ms"
+ }
+ }
}
diff --git a/src/main/kotlin/com/jerryjeon/logjerry/ui/AppliedTextFilter.kt b/src/main/kotlin/com/jerryjeon/logjerry/ui/AppliedTextFilter.kt
new file mode 100644
index 0000000..039ee50
--- /dev/null
+++ b/src/main/kotlin/com/jerryjeon/logjerry/ui/AppliedTextFilter.kt
@@ -0,0 +1,53 @@
+package com.jerryjeon.logjerry.ui
+
+import androidx.compose.foundation.border
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.*
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material.ButtonDefaults
+import androidx.compose.material.Divider
+import androidx.compose.material.Icon
+import androidx.compose.material.Text
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Close
+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.unit.dp
+import com.jerryjeon.logjerry.filter.TextFilter
+
+@Composable
+fun AppliedTextFilter(textFilter: TextFilter, removeFilter: (TextFilter) -> Unit) {
+ Box(
+ Modifier
+ .padding(horizontal = 4.dp, vertical = 8.dp)
+ .border(1.dp, Color.LightGray, RoundedCornerShape(8.dp))
+ ) {
+ Row(modifier = Modifier.height(30.dp)) {
+ Spacer(Modifier.width(8.dp))
+ Text(
+ textFilter.columnType.text,
+ modifier = Modifier.align(Alignment.CenterVertically),
+ )
+ Spacer(Modifier.width(8.dp))
+ Divider(Modifier.width(1.dp).fillMaxHeight())
+ Spacer(Modifier.width(8.dp))
+ Text(textFilter.text, modifier = Modifier.align(Alignment.CenterVertically))
+ Spacer(Modifier.width(8.dp))
+ Box(
+ Modifier
+ .clickable { removeFilter(textFilter) }
+ .align(Alignment.CenterVertically)
+ .fillMaxHeight()
+ .aspectRatio(1f)
+ ) {
+ Icon(
+ Icons.Default.Close,
+ contentDescription = "Remove a filter",
+ modifier = Modifier.size(ButtonDefaults.IconSize).align(Alignment.Center)
+ )
+ }
+ }
+ }
+}
diff --git a/src/main/kotlin/com/jerryjeon/logjerry/ui/DetectionView.kt b/src/main/kotlin/com/jerryjeon/logjerry/ui/DetectionView.kt
deleted file mode 100644
index 1336ec9..0000000
--- a/src/main/kotlin/com/jerryjeon/logjerry/ui/DetectionView.kt
+++ /dev/null
@@ -1,58 +0,0 @@
-package com.jerryjeon.logjerry.ui
-
-import androidx.compose.foundation.border
-import androidx.compose.foundation.layout.*
-import androidx.compose.foundation.shape.RoundedCornerShape
-import androidx.compose.material.Divider
-import androidx.compose.material.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.unit.dp
-import com.jerryjeon.logjerry.detector.DetectionStatus
-import com.jerryjeon.logjerry.detector.DetectorKey
-import com.jerryjeon.logjerry.detector.DetectorManager
-import com.jerryjeon.logjerry.log.Log
-import com.jerryjeon.logjerry.preferences.Preferences
-import kotlinx.coroutines.flow.StateFlow
-
-@Composable
-fun RowScope.DetectionView(
- preferences: Preferences,
- detectorManager: DetectorManager,
- statusByKey: Map,
- openNewTab: (StateFlow>) -> Unit,
- selectPreviousDetection: (status: DetectionStatus) -> Unit,
- selectNextDetection: (status: DetectionStatus) -> Unit,
-) {
- Box(modifier = Modifier.weight(0.5f).border(1.dp, Color.LightGray, RoundedCornerShape(4.dp))) {
- Column {
- Text("Auto-detection", modifier = Modifier.padding(8.dp))
- Divider()
- Row {
- JsonDetectionView(
- Modifier.width(200.dp).wrapContentHeight(),
- statusByKey[DetectorKey.Json],
- selectPreviousDetection,
- selectNextDetection
- )
-
- Spacer(Modifier.width(8.dp))
- Divider(Modifier.width(1.dp).height(70.dp).align(Alignment.CenterVertically))
- Spacer(Modifier.width(8.dp))
-
- MarkDetectionView(
- Modifier.width(200.dp).wrapContentHeight(),
- statusByKey[DetectorKey.Mark],
- selectPreviousDetection,
- selectNextDetection,
- openMarkedRowsTab = { openNewTab(detectorManager.markedRowsFlow) }
- )
-
- Spacer(Modifier.width(8.dp))
- Divider(Modifier.width(1.dp).height(70.dp).align(Alignment.CenterVertically))
- }
- }
- }
-}
diff --git a/src/main/kotlin/com/jerryjeon/logjerry/ui/FilterView.kt b/src/main/kotlin/com/jerryjeon/logjerry/ui/FilterView.kt
deleted file mode 100644
index b0116e4..0000000
--- a/src/main/kotlin/com/jerryjeon/logjerry/ui/FilterView.kt
+++ /dev/null
@@ -1,23 +0,0 @@
-package com.jerryjeon.logjerry.ui
-
-import androidx.compose.foundation.layout.Spacer
-import androidx.compose.foundation.layout.width
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.collectAsState
-import androidx.compose.runtime.getValue
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.unit.dp
-import com.jerryjeon.logjerry.filter.FilterManager
-
-@Composable
-fun FilterView(
- filterManager: FilterManager,
-) {
- val textFilters by filterManager.textFiltersFlow.collectAsState()
- val priorityFilters by filterManager.priorityFilterFlow.collectAsState()
-
- TextFilterView(textFilters, filterManager::addTextFilter, filterManager::removeTextFilter)
- Spacer(Modifier.width(16.dp))
- PriorityFilterView(priorityFilters, filterManager::setPriorityFilter)
- Spacer(Modifier.width(16.dp))
-}
diff --git a/src/main/kotlin/com/jerryjeon/logjerry/ui/JsonDetectionView.kt b/src/main/kotlin/com/jerryjeon/logjerry/ui/JsonDetectionView.kt
index ee1f140..d50b768 100644
--- a/src/main/kotlin/com/jerryjeon/logjerry/ui/JsonDetectionView.kt
+++ b/src/main/kotlin/com/jerryjeon/logjerry/ui/JsonDetectionView.kt
@@ -1,10 +1,8 @@
package com.jerryjeon.logjerry.ui
+import androidx.compose.foundation.border
import androidx.compose.foundation.layout.*
-import androidx.compose.material.Icon
-import androidx.compose.material.IconButton
-import androidx.compose.material.LocalTextStyle
-import androidx.compose.material.Text
+import androidx.compose.material.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.KeyboardArrowDown
import androidx.compose.material.icons.filled.KeyboardArrowUp
@@ -12,37 +10,31 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
-import androidx.compose.ui.text.buildAnnotatedString
-import androidx.compose.ui.text.style.TextAlign
-import androidx.compose.ui.text.withStyle
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.jerryjeon.logjerry.detector.DetectionStatus
-import com.jerryjeon.logjerry.detector.JsonDetection
@Composable
fun JsonDetectionView(
- modifier: Modifier,
- detectionStatus: DetectionStatus?,
+ modifier: Modifier = Modifier,
+ detectionStatus: DetectionStatus,
moveToPreviousOccurrence: (DetectionStatus) -> Unit,
moveToNextOccurrence: (DetectionStatus) -> Unit,
) {
CompositionLocalProvider(
LocalTextStyle provides LocalTextStyle.current.copy(fontSize = 12.sp),
) {
- Box(modifier = modifier) {
- Column(Modifier.height(IntrinsicSize.Min).padding(8.dp)) {
- val title = buildAnnotatedString {
- append("Json")
- }
- Text(title)
- if (detectionStatus == null) {
- Spacer(Modifier.height(16.dp))
- Text("No results", textAlign = TextAlign.Center)
- } else {
- JsonDetectionSelectionExist(detectionStatus, moveToPreviousOccurrence, moveToNextOccurrence)
- }
- }
+ Row(
+ modifier
+ .wrapContentWidth()
+ .border(ButtonDefaults.outlinedBorder, MaterialTheme.shapes.small)
+ .padding(start = 12.dp),
+ horizontalArrangement = Arrangement.SpaceBetween,
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ Text("Json")
+ Spacer(modifier = Modifier.width(8.dp))
+ JsonDetectionSelectionExist(detectionStatus, moveToPreviousOccurrence, moveToNextOccurrence)
}
}
}
@@ -53,34 +45,25 @@ fun JsonDetectionSelectionExist(
moveToPreviousOccurrence: (DetectionStatus) -> Unit,
moveToNextOccurrence: (DetectionStatus) -> Unit
) {
- Column {
- Row {
- Row(modifier = Modifier.weight(1f).fillMaxHeight()) {
- if (selection.selected == null) {
- Text(
- "${selection.allDetections.size} results",
- modifier = Modifier.align(Alignment.CenterVertically)
- )
- } else {
- Text(
- "${selection.currentIndexInView} / ${selection.totalCount}",
- modifier = Modifier.align(Alignment.CenterVertically)
- )
- }
- }
- IconButton(onClick = { moveToPreviousOccurrence(selection) }) {
- Icon(Icons.Default.KeyboardArrowUp, "Previous Occurrence")
- }
- IconButton(onClick = { moveToNextOccurrence(selection) }) {
- Icon(Icons.Default.KeyboardArrowDown, "Next Occurrence")
+ Row(verticalAlignment = Alignment.CenterVertically) {
+ Row(modifier = Modifier) {
+ if (selection.selected == null) {
+ Text(
+ " ${selection.allDetections.size}",
+ modifier = Modifier.align(Alignment.CenterVertically)
+ )
+ } else {
+ Text(
+ "${selection.currentIndexInView} / ${selection.totalCount}",
+ modifier = Modifier.align(Alignment.CenterVertically)
+ )
}
}
-
- // TODO cleanup ; don't cast
- /*
- (it.focusing as? JsonDetectionResult)?.let {
- Text(it.jsonList) // TODO show summary of json
- }
- */
+ IconButton(onClick = { moveToPreviousOccurrence(selection) }) {
+ Icon(Icons.Default.KeyboardArrowUp, "Previous Occurrence")
+ }
+ IconButton(onClick = { moveToNextOccurrence(selection) }) {
+ Icon(Icons.Default.KeyboardArrowDown, "Next Occurrence")
+ }
}
}
diff --git a/src/main/kotlin/com/jerryjeon/logjerry/ui/LogRow.kt b/src/main/kotlin/com/jerryjeon/logjerry/ui/LogRow.kt
index 46439bc..47e369a 100644
--- a/src/main/kotlin/com/jerryjeon/logjerry/ui/LogRow.kt
+++ b/src/main/kotlin/com/jerryjeon/logjerry/ui/LogRow.kt
@@ -7,8 +7,6 @@ import androidx.compose.foundation.PointerMatcher
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.onClick
-import androidx.compose.foundation.text.BasicTextField
-import androidx.compose.foundation.text.selection.SelectionContainer
import androidx.compose.material.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ContentCopy
@@ -17,28 +15,19 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.input.key.Key
-import androidx.compose.ui.input.key.KeyEventType
-import androidx.compose.ui.input.key.key
-import androidx.compose.ui.input.key.type
import androidx.compose.ui.input.pointer.PointerButton
import androidx.compose.ui.unit.dp
-import androidx.compose.ui.unit.sp
-import androidx.compose.ui.window.Dialog
-import androidx.compose.ui.window.DialogState
-import com.jerryjeon.logjerry.detector.JsonDetection
import com.jerryjeon.logjerry.log.Log
import com.jerryjeon.logjerry.log.LogContentView
import com.jerryjeon.logjerry.logview.RefinedLog
+import com.jerryjeon.logjerry.logview.toHumanReadable
import com.jerryjeon.logjerry.mark.LogMark
import com.jerryjeon.logjerry.preferences.Preferences
import com.jerryjeon.logjerry.table.ColumnInfo
import com.jerryjeon.logjerry.table.ColumnType
import com.jerryjeon.logjerry.table.Header
import com.jerryjeon.logjerry.util.copyToClipboard
-import com.jerryjeon.logjerry.util.isCtrlOrMetaPressed
import kotlinx.serialization.json.Json
-import kotlinx.serialization.json.JsonObject
val json = Json { prettyPrint = true }
@@ -80,34 +69,52 @@ fun LogRow(
MarkDialog(showMarkDialog, setMark)
- Row(
- Modifier
- .background(
- when {
- selected -> Color(0x20CCCCCC)
- else -> Color.Transparent
- }
- )
- .onClick { selectLog(refinedLog) }
- .onClick(
- matcher = PointerMatcher.mouse(PointerButton.Secondary),
- onClick = {
- showContextMenu = refinedLog
- }
- )
+ Column {
+ // This should be separated as a different item of LazyColumn
+ if (refinedLog.timeGap != null) {
+ Box(
+ Modifier
+ .fillMaxWidth()
+ .height(40.dp)
+ .background(Color(0x20CCCCCC))
+ ) {
+ Text(
+ text = "Large time gap: ${refinedLog.timeGap.toHumanReadable()}",
+ style = MaterialTheme.typography.body2,
+ modifier = Modifier.align(Alignment.Center)
+ )
+ }
+ }
- ) {
- Spacer(Modifier.width(8.dp))
- header.asColumnList.forEach { columnInfo ->
- if (columnInfo.visible) {
- CellByColumnType(preferences, columnInfo, refinedLog)
- if (columnInfo.columnType.showDivider) {
- divider()
+ Row(
+ Modifier
+ .background(
+ when {
+ selected -> Color(0x20CCCCCC)
+ else -> Color.Transparent
+ }
+ )
+ .onClick { selectLog(refinedLog) }
+ .onClick(
+ matcher = PointerMatcher.mouse(PointerButton.Secondary),
+ onClick = {
+ showContextMenu = refinedLog
+ }
+ )
+
+ ) {
+ Spacer(Modifier.width(8.dp))
+ header.asColumnList.forEach { columnInfo ->
+ if (columnInfo.visible) {
+ CellByColumnType(preferences, columnInfo, refinedLog)
+ if (columnInfo.columnType.showDivider) {
+ divider()
+ }
}
}
- }
- Spacer(Modifier.width(8.dp))
+ Spacer(Modifier.width(8.dp))
+ }
}
}
diff --git a/src/main/kotlin/com/jerryjeon/logjerry/ui/LogsView.kt b/src/main/kotlin/com/jerryjeon/logjerry/ui/LogsView.kt
index 068fc57..3ba7c8f 100644
--- a/src/main/kotlin/com/jerryjeon/logjerry/ui/LogsView.kt
+++ b/src/main/kotlin/com/jerryjeon/logjerry/ui/LogsView.kt
@@ -2,18 +2,12 @@
package com.jerryjeon.logjerry.ui
-import androidx.compose.animation.AnimatedVisibility
-import androidx.compose.animation.core.tween
-import androidx.compose.animation.fadeIn
-import androidx.compose.animation.fadeOut
import androidx.compose.foundation.*
import androidx.compose.foundation.gestures.scrollBy
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyListState
-import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.rememberLazyListState
-import androidx.compose.material.Divider
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.runtime.*
@@ -23,16 +17,19 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.PathEffect
+import androidx.compose.ui.graphics.drawscope.Stroke
import androidx.compose.ui.input.key.*
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
-import androidx.compose.ui.unit.sp
import com.jerryjeon.logjerry.ColumnDivider
import com.jerryjeon.logjerry.HeaderDivider
+import com.jerryjeon.logjerry.detector.Detection
import com.jerryjeon.logjerry.detector.DetectorManager
import com.jerryjeon.logjerry.log.ParseCompleted
import com.jerryjeon.logjerry.logview.LogSelection
+import com.jerryjeon.logjerry.logview.MarkInfo
import com.jerryjeon.logjerry.logview.RefineResult
import com.jerryjeon.logjerry.logview.RefinedLog
import com.jerryjeon.logjerry.mark.LogMark
@@ -41,10 +38,7 @@ import com.jerryjeon.logjerry.table.Header
import com.jerryjeon.logjerry.ui.focus.DetectionFocus
import com.jerryjeon.logjerry.ui.focus.KeyboardFocus
import com.jerryjeon.logjerry.ui.focus.LogFocus
-import com.jerryjeon.logjerry.ui.focus.MarkFocus
import com.jerryjeon.logjerry.util.isCtrlOrMetaPressed
-import kotlinx.coroutines.GlobalScope
-import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
@@ -102,34 +96,15 @@ fun LogsView(
detectorManager = detectorManager,
header = header,
listState = listState,
- markedRows = refineResult.markedRows,
+ markInfos = refineResult.markInfos,
setMark = detectorManager::setMark,
deleteMark = detectorManager::deleteMark,
hide = hide,
changeFocus = { refineResult.currentFocus.value = it },
moveToPreviousMark = moveToPreviousMark,
moveToNextMark = moveToNextMark,
+ selectDetection = refineResult::selectDetection,
)
- LazyColumn(modifier = Modifier.width(120.dp).fillMaxHeight().align(Alignment.CenterEnd)) {
- items(refineResult.markedRows) {
- val mark = it.mark!!
- Box(
- modifier = Modifier.fillMaxWidth().height(60.dp).background(mark.color)
- .clickable {
- refineResult.selectDetection(mark)
- },
- ) {
- Text(
- mark.note,
- modifier = Modifier.align(Alignment.Center),
- textAlign = TextAlign.Center,
- color = Color.Black,
- )
- }
- Divider()
- }
- }
-
}
}
@@ -142,13 +117,14 @@ fun LogsView(
preferences: Preferences,
header: Header,
listState: LazyListState,
- markedRows: List,
+ markInfos: List,
setMark: (logMark: LogMark) -> Unit,
deleteMark: (logIndex: Int) -> Unit,
hide: (logIndex: Int) -> Unit,
changeFocus: (LogFocus?) -> Unit,
moveToPreviousMark: () -> Unit,
moveToNextMark: () -> Unit,
+ selectDetection: (Detection) -> Unit,
) {
val scope = rememberCoroutineScope()
val focusRequester = remember { FocusRequester() }
@@ -295,7 +271,6 @@ fun LogsView(
} else {
logRow()
}
- Divider()
}
}
}
@@ -307,48 +282,147 @@ fun LogsView(
adapter = adapter,
)
- var isScrolling by remember { mutableStateOf(false) }
- LaunchedEffect(listState.firstVisibleItemScrollOffset) {
- isScrolling = true
- delay(1000)
- isScrolling = false
+ Box(
+ modifier = Modifier
+ .width(120.dp)
+ .fillMaxHeight()
+ .padding(end = LocalScrollbarStyle.current.thickness)
+ .align(Alignment.TopEnd)
+ ) {
+ MarkView(markInfos, listState.layoutInfo.viewportSize.height, refinedLogs.size, selectDetection)
}
+ }
+ }
- // TODO why this should be annotated
- this@Column.AnimatedVisibility(
- visible = isScrolling,
- enter = fadeIn(animationSpec = tween(500)),
- exit = fadeOut(animationSpec = tween(500))
- ) {
- val width = listState.layoutInfo.viewportSize.width
- Box(
- modifier = Modifier
- .fillMaxSize()
- .padding(start = (width - 120).dp)
- ) {
- markedRows.forEach {
- val y =
- it.log.index.toFloat() / listState.layoutInfo.totalItemsCount.toFloat() * listState.layoutInfo.viewportSize.height
- Box(
- modifier = Modifier.fillMaxWidth().height(30.dp)
- .offset(y = y.toInt().dp)
- .background(it.mark!!.color),
- contentAlignment = Alignment.Center,
- ) {
- Text(
- text = it.mark.note,
- color = Color.Black,
- fontSize = 12.sp,
- maxLines = 1
- )
- }
+ LaunchedEffect(focusRequester) {
+ focusRequester.requestFocus()
+ }
+}
+
+@Composable
+private fun MarkView(
+ markInfos: List,
+ viewportHeight: Int,
+ refinedLogsSize: Int,
+ selectDetection: (Detection) -> Unit,
+) {
+ val minHeight = 40
+ val minRatio = minHeight.toFloat() / viewportHeight.toFloat()
+
+ Column(modifier = Modifier.fillMaxSize()) {
+ markInfos.forEach {
+ when (it) {
+ is MarkInfo.Marked -> {
+ val mark = it.markedLog.mark!!
+ Box(
+ modifier = Modifier.fillMaxWidth().height(60.dp).background(mark.color)
+ .clickable { selectDetection(mark) },
+ ) {
+ Text(
+ mark.note,
+ modifier = Modifier.align(Alignment.Center),
+ textAlign = TextAlign.Center,
+ color = Color.Black,
+ )
+ }
+ }
+
+ is MarkInfo.StatBetweenMarks -> {
+ val ratio = it.logCount.toFloat() / refinedLogsSize.toFloat() / markInfos.size
+ val baseModifier = if (ratio < minRatio) {
+ Modifier.height(minHeight.dp)
+ } else {
+ Modifier.weight(ratio)
+ }
+ Box(
+ modifier = baseModifier.fillMaxWidth()
+ ) {
+ DashedDivider(
+ modifier = Modifier.fillMaxHeight().align(Alignment.Center),
+ thickness = 4.dp,
+ color = Color(0x44888888)
+ )
+ Text(
+ text = "${it.logCount} logs, ${it.duration}",
+ modifier = Modifier.padding(vertical = 12.dp).align(Alignment.Center).background(MaterialTheme.colors.background),
+ textAlign = TextAlign.Center,
+ style = MaterialTheme.typography.body2
+ )
}
}
}
}
}
- LaunchedEffect(focusRequester) {
- focusRequester.requestFocus()
+ /* TODO it seems not useful..
+ var isScrolling by remember { mutableStateOf(false) }
+ LaunchedEffect(listState.firstVisibleItemScrollOffset) {
+ isScrolling = true
+ delay(1000)
+ isScrolling = false
+ }
+
+ Column(modifier = Modifier.fillMaxSize()) {
+ AnimatedVisibility(
+ visible = isScrolling,
+ enter = fadeIn(animationSpec = tween(500)),
+ exit = fadeOut(animationSpec = tween(500))
+ ) {
+ Box(modifier = Modifier.fillMaxSize().background(MaterialTheme.colors.background))
+ }
+ }
+
+ Column(modifier = Modifier.fillMaxSize()) {
+ AnimatedVisibility(
+ visible = isScrolling,
+ enter = fadeIn(animationSpec = tween(500)),
+ exit = fadeOut(animationSpec = tween(500))
+ ) {
+ Column(modifier = Modifier.fillMaxSize()) {
+ markedRows.forEach {
+ val y =
+ it.log.index.toFloat() / listState.layoutInfo.totalItemsCount.toFloat() * listState.layoutInfo.viewportSize.height
+ Box(
+ modifier = Modifier.fillMaxWidth().height(30.dp)
+ .offset(y = y.toInt().dp)
+ .background(it.mark!!.color),
+ contentAlignment = Alignment.Center,
+ ) {
+ Text(
+ text = it.mark.note,
+ color = Color.Black,
+ fontSize = 12.sp,
+ maxLines = 1
+ )
+ }
+ }
+ }
+ }
+ }
+ */
+}
+
+@Composable
+fun DashedDivider(
+ thickness: Dp,
+ color: Color = MaterialTheme.colors.onSurface,
+ phase: Float = 10f,
+ intervals: FloatArray = floatArrayOf(20f, 25f),
+ modifier: Modifier = Modifier
+) {
+ Canvas(
+ modifier = modifier
+ ) {
+ val dividerHeight = thickness.toPx()
+ drawRoundRect(
+ color = color,
+ style = Stroke(
+ width = dividerHeight,
+ pathEffect = PathEffect.dashPathEffect(
+ intervals = intervals,
+ phase = phase
+ )
+ )
+ )
}
}
diff --git a/src/main/kotlin/com/jerryjeon/logjerry/ui/ParseCompletedView.kt b/src/main/kotlin/com/jerryjeon/logjerry/ui/ParseCompletedView.kt
index fbc6068..ef7f3e2 100644
--- a/src/main/kotlin/com/jerryjeon/logjerry/ui/ParseCompletedView.kt
+++ b/src/main/kotlin/com/jerryjeon/logjerry/ui/ParseCompletedView.kt
@@ -3,15 +3,21 @@
package com.jerryjeon.logjerry.ui
import androidx.compose.foundation.layout.*
+import androidx.compose.material.OutlinedButton
+import androidx.compose.material.Text
import androidx.compose.runtime.*
-import androidx.compose.ui.Alignment
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
-import androidx.compose.ui.focus.FocusRequester
-import androidx.compose.ui.focus.focusRequester
-import androidx.compose.ui.unit.dp
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.layout.onGloballyPositioned
+import androidx.compose.ui.layout.positionInRoot
+import androidx.compose.ui.unit.*
+import androidx.compose.ui.window.Popup
+import androidx.compose.ui.window.PopupPositionProvider
import com.jerryjeon.logjerry.detector.DetectorKey
import com.jerryjeon.logjerry.detector.KeywordDetectionView
+import com.jerryjeon.logjerry.filter.FilterManager
+import com.jerryjeon.logjerry.filter.PriorityFilter
import com.jerryjeon.logjerry.log.Log
import com.jerryjeon.logjerry.log.ParseCompleted
import com.jerryjeon.logjerry.preferences.Preferences
@@ -31,31 +37,47 @@ fun ParseCompletedView(
Column(
modifier = Modifier
) {
- Column {
- val keywordDetectionRequest by detectorManager.keywordDetectionRequestFlow.collectAsState()
+ Row(modifier = Modifier.height(IntrinsicSize.Min)) {
val statusByKey by refineResult.statusByKey.collectAsState()
- Row(modifier = Modifier.padding(16.dp)) {
+ Row(
+ modifier = Modifier
+ .padding(12.dp)
+ .height(IntrinsicSize.Min)
+ ) {
FilterView(filterManager)
- DetectionView(
- preferences,
- detectorManager,
- statusByKey,
- openNewTab,
- refineResult::selectPreviousDetection,
- refineResult::selectNextDetection,
- )
+ Spacer(Modifier.width(8.dp))
+ statusByKey[DetectorKey.Json]?.let {
+ JsonDetectionView(
+ modifier = Modifier.fillMaxHeight(),
+ detectionStatus = it,
+ moveToPreviousOccurrence = refineResult::selectPreviousDetection,
+ moveToNextOccurrence = refineResult::selectNextDetection,
+ )
+ }
}
- Box(modifier = Modifier.fillMaxWidth()) {
- KeywordDetectionView(
- Modifier.align(Alignment.BottomEnd),
- keywordDetectionRequest,
- statusByKey[DetectorKey.Keyword],
- detectorManager::findKeyword,
- detectorManager::setKeywordDetectionEnabled,
- refineResult::selectPreviousDetection,
- refineResult::selectNextDetection,
- )
+ Spacer(modifier = Modifier.weight(1f))
+
+ val keywordDetectionRequest by detectorManager.keywordDetectionRequestFlow.collectAsState()
+ KeywordDetectionView(
+ keywordDetectionRequest = keywordDetectionRequest,
+ detectionStatus = statusByKey[DetectorKey.Keyword],
+ find = detectorManager::findKeyword,
+ setFindEnabled = detectorManager::setKeywordDetectionEnabled,
+ moveToPreviousOccurrence = refineResult::selectPreviousDetection,
+ moveToNextOccurrence = refineResult::selectNextDetection,
+ )
+ }
+
+ val textFilters by filterManager.textFiltersFlow.collectAsState()
+ Column {
+ textFilters.chunked(2).forEach {
+ Row {
+ it.forEach { filter ->
+ AppliedTextFilter(filter, filterManager::removeTextFilter)
+ Spacer(Modifier.width(8.dp))
+ }
+ }
}
}
@@ -72,3 +94,96 @@ fun ParseCompletedView(
}
}
+@Composable
+private fun FilterView(filterManager: FilterManager) {
+ var showTextFilterPopup by remember { mutableStateOf(false) }
+ var textFilterAnchor by remember { mutableStateOf(Offset.Zero) }
+ var showLogLevelPopup by remember { mutableStateOf(false) }
+ var logLevelAnchor by remember { mutableStateOf(Offset.Zero) }
+ val priorityFilter by filterManager.priorityFilterFlow.collectAsState()
+
+ OutlinedButton(
+ onClick = {
+ showTextFilterPopup = true
+ },
+ modifier = Modifier
+ .height(48.dp)
+ .onGloballyPositioned { coordinates ->
+ textFilterAnchor = coordinates.positionInRoot()
+ },
+ ) {
+ Text("Add Filter")
+ }
+
+ Spacer(Modifier.width(8.dp))
+
+ OutlinedButton(
+ onClick = {
+ showLogLevelPopup = true
+ },
+ modifier = Modifier
+ .height(48.dp)
+ .onGloballyPositioned { coordinates ->
+ logLevelAnchor = coordinates.positionInRoot()
+ },
+ ) {
+ Text("Log Level | ${priorityFilter.priority.name}")
+ }
+
+ if (showTextFilterPopup) {
+ Popup(
+ onDismissRequest = { showTextFilterPopup = false },
+ focusable = true,
+ popupPositionProvider = object : PopupPositionProvider {
+ override fun calculatePosition(
+ anchorBounds: IntRect,
+ windowSize: IntSize,
+ layoutDirection: LayoutDirection,
+ popupContentSize: IntSize
+ ): IntOffset {
+ return IntOffset(textFilterAnchor.x.toInt(), (textFilterAnchor.y + anchorBounds.height).toInt())
+ }
+ }
+ ) {
+ TextFilterView(
+ filterManager::addTextFilter
+ ) { showTextFilterPopup = false }
+ }
+ }
+
+ PriorityFilterPopup(
+ priorityFilter,
+ logLevelAnchor,
+ showLogLevelPopup,
+ dismissPopup = { showLogLevelPopup = false },
+ setPriorityFilter = filterManager::setPriorityFilter
+ )
+}
+
+@Composable
+private fun PriorityFilterPopup(
+ priorityFilter: PriorityFilter,
+ anchor: Offset,
+ showPopup: Boolean,
+ dismissPopup: () -> Unit,
+ setPriorityFilter: (PriorityFilter) -> Unit
+) {
+ if (showPopup) {
+ Popup(
+ onDismissRequest = dismissPopup,
+ focusable = true,
+ popupPositionProvider = object : PopupPositionProvider {
+ override fun calculatePosition(
+ anchorBounds: IntRect,
+ windowSize: IntSize,
+ layoutDirection: LayoutDirection,
+ popupContentSize: IntSize
+ ): IntOffset {
+ return IntOffset(anchor.x.toInt(), (anchor.y + anchorBounds.height).toInt())
+ }
+ }
+ ) {
+ PriorityFilterView(priorityFilter, setPriorityFilter)
+ }
+ }
+}
diff --git a/src/main/kotlin/com/jerryjeon/logjerry/ui/PriorityFilterView.kt b/src/main/kotlin/com/jerryjeon/logjerry/ui/PriorityFilterView.kt
index 5e39989..cae887f 100644
--- a/src/main/kotlin/com/jerryjeon/logjerry/ui/PriorityFilterView.kt
+++ b/src/main/kotlin/com/jerryjeon/logjerry/ui/PriorityFilterView.kt
@@ -1,24 +1,14 @@
package com.jerryjeon.logjerry.ui
+import androidx.compose.foundation.background
import androidx.compose.foundation.border
-import androidx.compose.foundation.layout.Arrangement
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.IntrinsicSize
-import androidx.compose.foundation.layout.Row
-import androidx.compose.foundation.layout.Spacer
-import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.height
-import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.LocalTextStyle
+import androidx.compose.material.MaterialTheme
import androidx.compose.material.Slider
import androidx.compose.material.Text
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.setValue
+import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.style.TextAlign
@@ -33,7 +23,12 @@ fun PriorityFilterView(
changePriorityFilter: (PriorityFilter) -> Unit
) {
var value by remember(priorityFilter) { mutableStateOf(priorityFilter.priority.ordinal.toFloat()) }
- Column(Modifier.width(IntrinsicSize.Min).border(1.dp, Color.LightGray, RoundedCornerShape(4.dp)).padding(8.dp)) {
+ Column(
+ Modifier.width(IntrinsicSize.Min)
+ .border(1.dp, Color.LightGray, RoundedCornerShape(4.dp))
+ .background(MaterialTheme.colors.background)
+ .padding(8.dp)
+ ) {
Text("Log level")
Spacer(Modifier.height(8.dp))
val priorities = Priority.values()
diff --git a/src/main/kotlin/com/jerryjeon/logjerry/ui/TextFilterView.kt b/src/main/kotlin/com/jerryjeon/logjerry/ui/TextFilterView.kt
index 843ced9..dd6d1e6 100644
--- a/src/main/kotlin/com/jerryjeon/logjerry/ui/TextFilterView.kt
+++ b/src/main/kotlin/com/jerryjeon/logjerry/ui/TextFilterView.kt
@@ -2,51 +2,20 @@
package com.jerryjeon.logjerry.ui
+import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.IntrinsicSize
-import androidx.compose.foundation.layout.Row
-import androidx.compose.foundation.layout.Spacer
-import androidx.compose.foundation.layout.aspectRatio
-import androidx.compose.foundation.layout.fillMaxHeight
-import androidx.compose.foundation.layout.height
-import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.layout.size
-import androidx.compose.foundation.layout.width
-import androidx.compose.foundation.layout.wrapContentWidth
+import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
-import androidx.compose.material.ButtonDefaults
-import androidx.compose.material.Divider
-import androidx.compose.material.DropdownMenu
-import androidx.compose.material.DropdownMenuItem
-import androidx.compose.material.Icon
-import androidx.compose.material.IconButton
-import androidx.compose.material.LocalTextStyle
-import androidx.compose.material.Text
-import androidx.compose.material.TextField
-import androidx.compose.material.TextFieldDefaults
+import androidx.compose.material.*
import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.Add
import androidx.compose.material.icons.filled.ArrowDropDown
-import androidx.compose.material.icons.filled.Close
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.CompositionLocalProvider
-import androidx.compose.runtime.MutableState
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.setValue
+import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.input.key.Key
-import androidx.compose.ui.input.key.KeyEventType
-import androidx.compose.ui.input.key.key
-import androidx.compose.ui.input.key.onPreviewKeyEvent
-import androidx.compose.ui.input.key.type
+import androidx.compose.ui.input.key.*
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
@@ -55,43 +24,29 @@ import com.jerryjeon.logjerry.table.ColumnType
@Composable
fun TextFilterView(
- textFilters: List,
addFilter: (TextFilter) -> Unit,
- removeFilter: (TextFilter) -> Unit
+ dismiss: () -> Unit
) {
- Box(Modifier.border(1.dp, Color.LightGray, RoundedCornerShape(4.dp)).padding(8.dp)) {
- Column {
- Text("Filter")
- CompositionLocalProvider(
- LocalTextStyle provides LocalTextStyle.current.copy(fontSize = 12.sp)
- ) {
- Spacer(Modifier.height(8.dp))
- AddTextFilterView(addFilter)
-
- Spacer(Modifier.height(8.dp))
- // FlowRow must be better, but it works strangely..
- Column {
- textFilters.chunked(2).forEach {
- Row {
- it.forEach { filter ->
- AppliedTextFilter(filter, removeFilter)
- Spacer(Modifier.width(8.dp))
- }
- }
- }
- }
- }
+ Column(
+ Modifier.border(1.dp, Color.LightGray, RoundedCornerShape(4.dp)).background(MaterialTheme.colors.background)
+ .padding(8.dp)
+ ) {
+ CompositionLocalProvider(
+ LocalTextStyle provides LocalTextStyle.current.copy(fontSize = 12.sp)
+ ) {
+ AddTextFilterView(addFilter, dismiss)
}
}
}
@Composable
private fun AddTextFilterView(
- addFilter: (TextFilter) -> Unit
+ addFilter: (TextFilter) -> Unit,
+ dismiss: () -> Unit,
) {
var text by remember { mutableStateOf("") }
val columnTypeState = remember { mutableStateOf(ColumnType.Log) }
- Row(
+ Column(
modifier = Modifier.height(IntrinsicSize.Min).onPreviewKeyEvent {
when {
it.key == Key.Enter && it.type == KeyEventType.KeyDown -> {
@@ -115,18 +70,30 @@ private fun AddTextFilterView(
leadingIcon = {
SelectColumnTypeView(columnTypeState)
},
- trailingIcon = {
- IconButton(onClick = {
- addFilter(TextFilter(columnTypeState.value, text))
- text = ""
- }) {
- Icon(Icons.Default.Add, "Add a filter")
- }
- },
colors = TextFieldDefaults.textFieldColors(
backgroundColor = Color.Transparent
)
)
+
+ Row(modifier = Modifier.align(Alignment.End)) {
+ TextButton(
+ onClick = {
+ dismiss()
+ text = ""
+ }
+ ) {
+ Text("Cancel")
+ }
+ TextButton(
+ onClick = {
+ addFilter(TextFilter(columnTypeState.value, text))
+ dismiss()
+ text = ""
+ }
+ ) {
+ Text("Ok")
+ }
+ }
}
}
@@ -163,38 +130,3 @@ private fun SelectColumnTypeView(
}
}
}
-
-@Composable
-private fun AppliedTextFilter(textFilter: TextFilter, removeFilter: (TextFilter) -> Unit) {
- Box(
- Modifier
- .padding(horizontal = 4.dp, vertical = 8.dp)
- .border(1.dp, Color.LightGray, RoundedCornerShape(8.dp))
- ) {
- Row(modifier = Modifier.height(30.dp)) {
- Spacer(Modifier.width(8.dp))
- Text(
- textFilter.columnType.text,
- modifier = Modifier.align(Alignment.CenterVertically),
- )
- Spacer(Modifier.width(8.dp))
- Divider(Modifier.width(1.dp).fillMaxHeight())
- Spacer(Modifier.width(8.dp))
- Text(textFilter.text, modifier = Modifier.align(Alignment.CenterVertically))
- Spacer(Modifier.width(8.dp))
- Box(
- Modifier
- .clickable { removeFilter(textFilter) }
- .align(Alignment.CenterVertically)
- .fillMaxHeight()
- .aspectRatio(1f)
- ) {
- Icon(
- Icons.Default.Close,
- contentDescription = "Remove a filter",
- modifier = Modifier.size(ButtonDefaults.IconSize).align(Alignment.Center)
- )
- }
- }
- }
-}