fix(ui): social section

- fix logger line separator
This commit is contained in:
rhunk 2023-08-31 03:24:33 +02:00
parent 61da95f41b
commit c791fbbd00
6 changed files with 175 additions and 27 deletions

View File

@ -2,6 +2,7 @@ package me.rhunk.snapenhance
import android.content.SharedPreferences import android.content.SharedPreferences
import android.util.Log import android.util.Log
import com.google.gson.GsonBuilder
import java.io.File import java.io.File
import java.io.OutputStream import java.io.OutputStream
import java.io.RandomAccessFile import java.io.RandomAccessFile
@ -42,6 +43,21 @@ class LogReader(
private var startLineIndexes = mutableListOf<Long>() private var startLineIndexes = mutableListOf<Long>()
var lineCount = queryLineCount() var lineCount = queryLineCount()
private fun readLogLine(): LogLine? {
val lines = mutableListOf<String>()
while (true) {
val lastPointer = randomAccessFile.filePointer
val line = randomAccessFile.readLine() ?: return null
if (lines.size > 0 && line.startsWith("|")) {
randomAccessFile.seek(lastPointer)
break
}
lines.add(line)
}
val line = lines.joinToString("\n").replaceFirst("|", "")
return LogLine.fromString(line)
}
fun incrementLineCount() { fun incrementLineCount() {
randomAccessFile.seek(randomAccessFile.length()) randomAccessFile.seek(randomAccessFile.length())
startLineIndexes.add(randomAccessFile.filePointer) startLineIndexes.add(randomAccessFile.filePointer)
@ -54,7 +70,7 @@ class LogReader(
var lastIndex: Long var lastIndex: Long
while (true) { while (true) {
lastIndex = randomAccessFile.filePointer lastIndex = randomAccessFile.filePointer
randomAccessFile.readLine() ?: break readLogLine() ?: break
startLineIndexes.add(lastIndex) startLineIndexes.add(lastIndex)
lines++ lines++
} }
@ -64,7 +80,7 @@ class LogReader(
private fun getLine(index: Int): String? { private fun getLine(index: Int): String? {
if (index <= 0 || index > lineCount) return null if (index <= 0 || index > lineCount) return null
randomAccessFile.seek(startLineIndexes[index]) randomAccessFile.seek(startLineIndexes[index])
return randomAccessFile.readLine() return readLogLine()?.toString()
} }
fun getLogLine(index: Int): LogLine? { fun getLogLine(index: Int): LogLine? {
@ -74,7 +90,7 @@ class LogReader(
class LogManager( class LogManager(
remoteSideContext: RemoteSideContext private val remoteSideContext: RemoteSideContext
) { ) {
companion object { companion object {
private const val TAG = "SnapEnhanceManager" private const val TAG = "SnapEnhanceManager"
@ -118,12 +134,10 @@ class LogManager(
} }
fun clearLogs() { fun clearLogs() {
logFile.delete() logFolder.listFiles()?.forEach { it.delete() }
newLogFile() newLogFile()
} }
fun getLogFile() = logFile
fun exportLogsToZip(outputStream: OutputStream) { fun exportLogsToZip(outputStream: OutputStream) {
val zipOutputStream = ZipOutputStream(outputStream) val zipOutputStream = ZipOutputStream(outputStream)
//add logFolder to zip //add logFolder to zip
@ -136,8 +150,10 @@ class LogManager(
} }
//add device info to zip //add device info to zip
zipOutputStream.putNextEntry(ZipEntry("device_info.txt")) zipOutputStream.putNextEntry(ZipEntry("device_info.json"))
val gson = GsonBuilder().setPrettyPrinting().create()
zipOutputStream.write(gson.toJson(remoteSideContext.installationSummary).toByteArray())
zipOutputStream.closeEntry()
zipOutputStream.close() zipOutputStream.close()
} }
@ -178,7 +194,7 @@ class LogManager(
fun internalLog(tag: String, logLevel: LogLevel, message: Any?) { fun internalLog(tag: String, logLevel: LogLevel, message: Any?) {
runCatching { runCatching {
val line = LogLine(logLevel, getCurrentDateTime(), tag, message.toString()) val line = LogLine(logLevel, getCurrentDateTime(), tag, message.toString())
logFile.appendText("$line\n", Charsets.UTF_8) logFile.appendText("|$line\n", Charsets.UTF_8)
lineAddListener(line) lineAddListener(line)
Log.println(logLevel.priority, tag, message.toString()) Log.println(logLevel.priority, tag, message.toString())
}.onFailure { }.onFailure {

View File

@ -237,11 +237,15 @@ class HomeSection : Section() {
}) })
DropdownMenuItem(onClick = { DropdownMenuItem(onClick = {
val logFile = context.log.getLogFile() activityLauncherHelper.saveFile("snapenhance-logs-${System.currentTimeMillis()}.zip", "application/zip") { uri ->
activityLauncherHelper.saveFile(logFile.name, "text/plain") { uri ->
context.androidContext.contentResolver.openOutputStream(Uri.parse(uri))?.use { context.androidContext.contentResolver.openOutputStream(Uri.parse(uri))?.use {
logFile.inputStream().copyTo(it) runCatching {
context.longToast("Saved logs to $uri") context.log.exportLogsToZip(it)
context.longToast("Saved logs to $uri")
}.onFailure {
context.longToast("Failed to save logs to $uri!")
context.log.error("Failed to save logs to $uri!", it)
}
} }
} }
showDropDown = false showDropDown = false

View File

@ -3,10 +3,13 @@ package me.rhunk.snapenhance.ui.manager.sections.home
import androidx.compose.foundation.ScrollState import androidx.compose.foundation.ScrollState
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.foundation.horizontalScroll
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.defaultMinSize
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
@ -19,9 +22,12 @@ import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.KeyboardDoubleArrowDown import androidx.compose.material.icons.filled.KeyboardDoubleArrowDown
import androidx.compose.material.icons.filled.KeyboardDoubleArrowUp import androidx.compose.material.icons.filled.KeyboardDoubleArrowUp
import androidx.compose.material.icons.filled.OpenInNew import androidx.compose.material.icons.filled.OpenInNew
import androidx.compose.material.icons.outlined.BugReport
import androidx.compose.material.icons.outlined.Info
import androidx.compose.material.icons.outlined.Report
import androidx.compose.material.icons.outlined.Warning
import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.FilledIconButton import androidx.compose.material3.FilledIconButton
import androidx.compose.material3.FloatingActionButton
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
@ -36,13 +42,19 @@ import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.platform.LocalClipboardManager
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import androidx.compose.ui.window.Dialog import androidx.compose.ui.window.Dialog
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import me.rhunk.snapenhance.Constants import me.rhunk.snapenhance.Constants
import me.rhunk.snapenhance.LogChannels
import me.rhunk.snapenhance.LogLevel
import me.rhunk.snapenhance.LogReader import me.rhunk.snapenhance.LogReader
import me.rhunk.snapenhance.RemoteSideContext import me.rhunk.snapenhance.RemoteSideContext
import me.rhunk.snapenhance.action.EnumAction import me.rhunk.snapenhance.action.EnumAction
@ -106,6 +118,7 @@ class HomeSubSection(
@Composable @Composable
fun LogsSection() { fun LogsSection() {
val coroutineScope = rememberCoroutineScope() val coroutineScope = rememberCoroutineScope()
val clipboardManager = LocalClipboardManager.current
var lineCount by remember { mutableIntStateOf(0) } var lineCount by remember { mutableIntStateOf(0) }
var logReader by remember { mutableStateOf<LogReader?>(null) } var logReader by remember { mutableStateOf<LogReader?>(null) }
logListState = remember { LazyListState(0) } logListState = remember { LazyListState(0) }
@ -120,12 +133,65 @@ class HomeSubSection(
) { ) {
items(lineCount) { index -> items(lineCount) { index ->
val line = logReader?.getLogLine(index) ?: return@items val line = logReader?.getLogLine(index) ?: return@items
var expand by remember { mutableStateOf(false) }
Box(modifier = Modifier Box(modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.background( .background(
if (index % 2 == 0) MaterialTheme.colorScheme.surface else MaterialTheme.colorScheme.surfaceVariant if (index % 2 == 0) MaterialTheme.colorScheme.surface else MaterialTheme.colorScheme.surfaceVariant
)) { )
Text(text = line.message, modifier = Modifier.padding(9.dp), fontSize = 10.sp) .pointerInput(Unit) {
detectTapGestures(
onLongPress = {
coroutineScope.launch {
clipboardManager.setText(AnnotatedString(line.message))
}
},
onTap = {
expand = !expand
}
)
}) {
Row(
modifier = Modifier
.horizontalScroll(ScrollState(0))
.padding(4.dp)
.defaultMinSize(minHeight = 30.dp),
verticalAlignment = Alignment.CenterVertically
) {
if (!expand) {
Icon(
imageVector = when (line.logLevel) {
LogLevel.DEBUG -> Icons.Outlined.BugReport
LogLevel.ERROR, LogLevel.ASSERT -> Icons.Outlined.Report
LogLevel.INFO, LogLevel.VERBOSE -> Icons.Outlined.Info
LogLevel.WARN -> Icons.Outlined.Warning
},
contentDescription = null,
)
Text(
text = LogChannels.fromChannel(line.tag)?.shortName ?: line.tag,
modifier = Modifier.padding(start = 4.dp),
fontWeight = FontWeight.Light,
fontSize = 10.sp,
)
Text(
text = line.dateTime,
modifier = Modifier.padding(start = 4.dp, end = 4.dp),
fontSize = 10.sp
)
}
Text(
text = line.message.trimIndent(),
fontSize = 10.sp,
maxLines = if (expand) Int.MAX_VALUE else 6,
overflow = if (expand) TextOverflow.Visible else TextOverflow.Ellipsis,
softWrap = !expand,
)
}
} }
} }
} }

View File

@ -241,10 +241,26 @@ class ScopeContent(
return return
} }
Column {
Text(text = group.name, maxLines = 1) Column(
Text(text = "participantsCount: ${group.participantsCount}", maxLines = 1) modifier = Modifier
Spacer(modifier = Modifier.height(16.dp)) .padding(10.dp)
.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
text = group.name,
maxLines = 1,
fontSize = 20.sp,
fontWeight = FontWeight.Bold
)
Spacer(modifier = Modifier.height(5.dp))
Text(
text = "Participants: ${group.participantsCount}",
maxLines = 1,
fontSize = 12.sp,
fontWeight = FontWeight.Light
)
} }
} }
} }

View File

@ -2,6 +2,7 @@ package me.rhunk.snapenhance.ui.manager.sections.social
import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
@ -39,6 +40,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.vectorResource import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
@ -147,8 +149,7 @@ class SocialSection : Section() {
if (listSize == 0) { if (listSize == 0) {
item { item {
//TODO: i18n Text(text = "(empty)", modifier = Modifier.fillMaxWidth().padding(10.dp), textAlign = TextAlign.Center)
Text(text = "No ${scope.key.lowercase()}s found")
} }
} }
@ -172,9 +173,13 @@ class SocialSection : Section() {
when (scope) { when (scope) {
SocialScope.GROUP -> { SocialScope.GROUP -> {
val group = groupList[index] val group = groupList[index]
Column { Column(
Text(text = group.name, maxLines = 1) modifier = Modifier
Text(text = "participantsCount: ${group.participantsCount}", maxLines = 1) .padding(10.dp)
.fillMaxSize(),
verticalArrangement = Arrangement.Center
) {
Text(text = group.name, maxLines = 1, fontWeight = FontWeight.Bold)
} }
} }
SocialScope.FRIEND -> { SocialScope.FRIEND -> {

View File

@ -1,8 +1,11 @@
package me.rhunk.snapenhance package me.rhunk.snapenhance
import android.annotation.SuppressLint
import android.util.Log import android.util.Log
import de.robv.android.xposed.XposedBridge import de.robv.android.xposed.XposedBridge
import me.rhunk.snapenhance.core.bridge.BridgeClient import me.rhunk.snapenhance.core.bridge.BridgeClient
import me.rhunk.snapenhance.hook.HookStage
import me.rhunk.snapenhance.hook.hook
enum class LogLevel( enum class LogLevel(
val letter: String, val letter: String,
@ -24,10 +27,28 @@ enum class LogLevel(
fun fromShortName(shortName: String): LogLevel? { fun fromShortName(shortName: String): LogLevel? {
return values().find { it.shortName == shortName } return values().find { it.shortName == shortName }
} }
fun fromPriority(priority: Int): LogLevel? {
return values().find { it.priority == priority }
}
}
}
enum class LogChannels(val channel: String, val shortName: String) {
CORE("SnapEnhanceCore", "core"),
NATIVE("SnapEnhanceNative", "native"),
MANAGER("SnapEnhanceManager", "manager"),
XPOSED("LSPosed-Bridge", "xposed");
companion object {
fun fromChannel(channel: String): LogChannels? {
return values().find { it.channel == channel }
}
} }
} }
@SuppressLint("PrivateApi")
class Logger( class Logger(
private val bridgeClient: BridgeClient private val bridgeClient: BridgeClient
) { ) {
@ -55,11 +76,31 @@ class Logger(
} }
} }
private var invokeOriginalPrintLog: (Int, String, String) -> Unit
init {
val printLnMethod = Log::class.java.getDeclaredMethod("println", Int::class.java, String::class.java, String::class.java)
printLnMethod.hook(HookStage.BEFORE) { param ->
val priority = param.arg(0) as Int
val tag = param.arg(1) as String
val message = param.arg(2) as String
internalLog(tag, LogLevel.fromPriority(priority) ?: LogLevel.INFO, message)
}
invokeOriginalPrintLog = { priority, tag, message ->
XposedBridge.invokeOriginalMethod(
printLnMethod,
null,
arrayOf(priority, tag, message)
)
}
}
private fun internalLog(tag: String, logLevel: LogLevel, message: Any?) { private fun internalLog(tag: String, logLevel: LogLevel, message: Any?) {
runCatching { runCatching {
bridgeClient.broadcastLog(tag, logLevel.shortName, message.toString()) bridgeClient.broadcastLog(tag, logLevel.shortName, message.toString())
}.onFailure { }.onFailure {
Log.println(logLevel.priority, tag, message.toString()) invokeOriginalPrintLog(logLevel.priority, tag, message.toString())
} }
} }
@ -69,7 +110,7 @@ class Logger(
fun error(message: Any?, throwable: Throwable, tag: String = TAG) { fun error(message: Any?, throwable: Throwable, tag: String = TAG) {
internalLog(tag, LogLevel.ERROR, message) internalLog(tag, LogLevel.ERROR, message)
internalLog(tag, LogLevel.ERROR, throwable) internalLog(tag, LogLevel.ERROR, throwable.stackTraceToString())
} }
fun info(message: Any?, tag: String = TAG) = internalLog(tag, LogLevel.INFO, message) fun info(message: Any?, tag: String = TAG) = internalLog(tag, LogLevel.INFO, message)