11package com.tapadoo.debugmenu.logs
22
3+ import android.content.Context
4+ import android.content.Intent
35import androidx.compose.foundation.ExperimentalFoundationApi
46import androidx.compose.foundation.background
57import androidx.compose.foundation.basicMarquee
8+ import androidx.compose.foundation.combinedClickable
69import androidx.compose.foundation.layout.*
710import androidx.compose.foundation.lazy.LazyColumn
811import androidx.compose.foundation.lazy.items
912import androidx.compose.foundation.shape.RoundedCornerShape
1013import androidx.compose.material.icons.Icons
1114import androidx.compose.material.icons.automirrored.filled.List
15+ import androidx.compose.material.icons.filled.Share
1216import androidx.compose.material.icons.outlined.Delete
17+ import androidx.compose.material.icons.outlined.Clear
1318import androidx.compose.material3.Icon
1419import androidx.compose.material3.IconButton
1520import androidx.compose.material3.MaterialTheme
1621import androidx.compose.material3.Text
1722import androidx.compose.runtime.*
23+ import androidx.compose.ui.Alignment
1824import androidx.compose.ui.Modifier
1925import androidx.compose.ui.draw.clip
26+ import androidx.compose.ui.platform.LocalContext
2027import androidx.compose.ui.text.font.FontFamily
28+ import androidx.compose.ui.text.style.TextOverflow
2129import androidx.compose.ui.unit.dp
30+ import androidx.compose.ui.unit.sp
2231import com.tapadoo.debugmenu.module.DebugMenuModule
2332
2433class LoggingModule : DebugMenuModule {
@@ -28,6 +37,9 @@ class LoggingModule: DebugMenuModule {
2837 @Composable
2938 override fun Content () {
3039 var invertSort by remember { mutableStateOf(false ) }
40+ var selectedLogs by remember { mutableStateOf(setOf<LogItem >()) }
41+ var expandedLogs by remember { mutableStateOf(setOf<LogItem >()) }
42+ val context = LocalContext .current
3143
3244 val logs by remember {
3345 derivedStateOf {
@@ -52,27 +64,85 @@ class LoggingModule: DebugMenuModule {
5264 .background(MaterialTheme .colorScheme.surface)
5365 .padding(4 .dp)
5466 .fillMaxWidth(),
55- horizontalArrangement = Arrangement .End
67+ horizontalArrangement = Arrangement .spacedBy( 8 .dp, Alignment . End )
5668 ) {
69+ if (selectedLogs.isNotEmpty()) {
70+ IconButton ({ shareSelectedLogs(context, selectedLogs.toList()) }) {
71+ Row (
72+ horizontalArrangement = Arrangement .spacedBy(6 .dp),
73+ modifier = Modifier .padding(horizontal = 8 .dp)
74+ ) {
75+ Icon (
76+ Icons .Filled .Share ,
77+ contentDescription = " Share"
78+ )
79+ Text (
80+ text = " ${selectedLogs.size} " ,
81+ style = MaterialTheme .typography.labelLarge
82+ )
83+ }
84+ }
85+
86+ IconButton ({ selectedLogs = setOf () }) {
87+ Icon (
88+ Icons .Outlined .Clear ,
89+ contentDescription = " Deselect All"
90+ )
91+ }
92+ }
5793 IconButton ({ DebugLogs .clear() }) {
5894 Icon (
59- Icons .Outlined .Delete , null
95+ Icons .Outlined .Delete ,
96+ contentDescription = " Clear All"
6097 )
6198 }
6299 IconButton ({ invertSort = ! invertSort }) {
63100 Icon (
64- Icons .AutoMirrored .Filled .List , null
101+ Icons .AutoMirrored .Filled .List ,
102+ contentDescription = " Toggle Sort"
65103 )
66104 }
67105 }
68106 }
69107 items(logs) { item ->
108+ val isExpanded = expandedLogs.contains(item)
109+ val isSelected = selectedLogs.contains(item)
110+
70111 Column (
71112 modifier = Modifier
72113 .padding(horizontal = 12 .dp)
73114 .fillMaxWidth()
74115 .clip(RoundedCornerShape (8 .dp))
75- .background(MaterialTheme .colorScheme.surfaceContainerLowest)
116+ .background(
117+ if (isSelected) MaterialTheme .colorScheme.primaryContainer
118+ else MaterialTheme .colorScheme.surfaceContainerLowest
119+ )
120+ .combinedClickable(
121+ onClick = {
122+ if (selectedLogs.isNotEmpty()) {
123+ // If in selection mode, toggle selection on click
124+ selectedLogs = if (isSelected) {
125+ selectedLogs - item
126+ } else {
127+ selectedLogs + item
128+ }
129+ } else {
130+ // Otherwise toggle expansion
131+ expandedLogs = if (isExpanded) {
132+ expandedLogs - item
133+ } else {
134+ expandedLogs + item
135+ }
136+ }
137+ },
138+ onLongClick = {
139+ selectedLogs = if (isSelected) {
140+ selectedLogs - item
141+ } else {
142+ selectedLogs + item
143+ }
144+ }
145+ )
76146 .padding(12 .dp)
77147 ) {
78148 Row {
@@ -81,26 +151,42 @@ class LoggingModule: DebugMenuModule {
81151 text = " [${item.tag} ] " ,
82152 maxLines = 1 ,
83153 modifier = Modifier .basicMarquee(),
84- style = MaterialTheme .typography.titleMedium.copy(fontFamily = FontFamily .Monospace ),
154+ style = MaterialTheme .typography.titleSmall.copy(
155+ fontFamily = FontFamily .Monospace ,
156+ fontSize = 11 .sp
157+ ),
85158 color = MaterialTheme .colorScheme.primary
86159 )
87160 }
88161 Text (
89162 text = getPriorityLabel(item.priority),
90163 maxLines = 1 ,
91- style = MaterialTheme .typography.titleMedium.copy(fontFamily = FontFamily .Monospace ),
164+ style = MaterialTheme .typography.titleSmall.copy(
165+ fontFamily = FontFamily .Monospace ,
166+ fontSize = 11 .sp
167+ ),
92168 color = getPriorityColor(item.priority)
93169 )
94170 }
95171 Text (
96172 text = item.message,
97- style = MaterialTheme .typography.bodyMedium.copy(fontFamily = FontFamily .Monospace ),
173+ maxLines = if (isExpanded) Int .MAX_VALUE else 3 ,
174+ overflow = TextOverflow .Ellipsis ,
175+ style = MaterialTheme .typography.bodySmall.copy(
176+ fontFamily = FontFamily .Monospace ,
177+ fontSize = 10 .sp
178+ ),
98179 color = MaterialTheme .colorScheme.onSurface
99180 )
100181 if (item.throwable != null ) {
101182 Text (
102183 text = item.throwable.stackTraceToString(),
103- style = MaterialTheme .typography.bodySmall.copy(fontFamily = FontFamily .Monospace ),
184+ maxLines = if (isExpanded) Int .MAX_VALUE else 5 ,
185+ overflow = TextOverflow .Ellipsis ,
186+ style = MaterialTheme .typography.bodySmall.copy(
187+ fontFamily = FontFamily .Monospace ,
188+ fontSize = 9 .sp
189+ ),
104190 color = MaterialTheme .colorScheme.error
105191 )
106192 }
@@ -129,4 +215,33 @@ class LoggingModule: DebugMenuModule {
129215 7 -> " ASSERT"
130216 else -> " UNKNOWN"
131217 }
218+
219+ private fun shareSelectedLogs (context : Context , selectedLogs : List <LogItem >) {
220+ if (selectedLogs.isEmpty()) return
221+
222+ val logsText = selectedLogs.joinToString(" \n\n " ) { logItem ->
223+ buildString {
224+ append(" [${getPriorityLabel(logItem.priority)} ]" )
225+ if (logItem.tag != null ) {
226+ append(" [${logItem.tag} ]" )
227+ }
228+ append(" \n " )
229+ append(logItem.message)
230+ if (logItem.throwable != null ) {
231+ append(" \n " )
232+ append(logItem.throwable.stackTraceToString())
233+ }
234+ append(" \n Timestamp: ${logItem.timestampMs} " )
235+ }
236+ }
237+
238+ val shareIntent = Intent ().apply {
239+ action = Intent .ACTION_SEND
240+ type = " text/plain"
241+ putExtra(Intent .EXTRA_TEXT , logsText)
242+ putExtra(Intent .EXTRA_SUBJECT , " Debug Logs (${selectedLogs.size} items)" )
243+ }
244+
245+ context.startActivity(Intent .createChooser(shareIntent, " Share Logs" ))
246+ }
132247}
0 commit comments