mirror of
https://github.com/JustinZeus/Wholphin.git
synced 2026-07-08 15:50:16 +02:00
Add a debug page (#32)
Click the version in settings 3 times to open this new debug page which displays internal information such as settings, server info, app info, and database info. It will also show recently logcat messages.
This commit is contained in:
parent
f57fd25f8e
commit
405e170750
6 changed files with 325 additions and 2 deletions
|
|
@ -22,4 +22,7 @@ interface ItemPlaybackDao {
|
|||
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
fun saveItem(item: ItemPlayback): Long
|
||||
|
||||
@Query("SELECT * from ItemPlayback WHERE userId=:userId")
|
||||
fun getItems(userId: Int): List<ItemPlayback>
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import androidx.room.Query
|
|||
import androidx.room.Relation
|
||||
import androidx.room.Transaction
|
||||
import androidx.room.Update
|
||||
import com.github.damontecres.wholphin.ui.isNotNullOrBlank
|
||||
import java.util.UUID
|
||||
|
||||
@Entity(tableName = "servers")
|
||||
|
|
@ -43,7 +44,10 @@ data class JellyfinUser(
|
|||
@ColumnInfo(index = true)
|
||||
val serverId: UUID,
|
||||
val accessToken: String?,
|
||||
)
|
||||
) {
|
||||
override fun toString(): String =
|
||||
"JellyfinUser(rowId=$rowId, id=$id, name=$name, serverId=$serverId, accessToken=${accessToken.isNotNullOrBlank()})"
|
||||
}
|
||||
|
||||
data class JellyfinServerUsers(
|
||||
@Embedded val server: JellyfinServer,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,310 @@
|
|||
package com.github.damontecres.wholphin.ui.detail
|
||||
|
||||
import android.util.Log
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.focusable
|
||||
import androidx.compose.foundation.gestures.scrollBy
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.livedata.observeAsState
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
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.onKeyEvent
|
||||
import androidx.compose.ui.input.key.type
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import androidx.tv.material3.MaterialTheme
|
||||
import androidx.tv.material3.Text
|
||||
import com.github.damontecres.wholphin.BuildConfig
|
||||
import com.github.damontecres.wholphin.data.ItemPlaybackDao
|
||||
import com.github.damontecres.wholphin.data.ServerRepository
|
||||
import com.github.damontecres.wholphin.data.model.ItemPlayback
|
||||
import com.github.damontecres.wholphin.preferences.UserPreferences
|
||||
import com.github.damontecres.wholphin.ui.launchIO
|
||||
import com.github.damontecres.wholphin.util.ExceptionHandler
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.io.BufferedReader
|
||||
import java.io.InputStreamReader
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
class DebugViewModel
|
||||
@Inject
|
||||
constructor(
|
||||
val serverRepository: ServerRepository,
|
||||
val itemPlaybackDao: ItemPlaybackDao,
|
||||
) : ViewModel() {
|
||||
val itemPlaybacks = MutableLiveData<List<ItemPlayback>>(listOf())
|
||||
val logcat = MutableLiveData<List<LogcatLine>>(listOf())
|
||||
|
||||
init {
|
||||
viewModelScope.launchIO {
|
||||
serverRepository.currentUser?.rowId?.let {
|
||||
val results = itemPlaybackDao.getItems(it)
|
||||
withContext(Dispatchers.Main) {
|
||||
itemPlaybacks.value = results
|
||||
}
|
||||
val logcat = getLogCatLines()
|
||||
withContext(Dispatchers.Main) {
|
||||
this@DebugViewModel.logcat.value = logcat
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun getLogCatLines(): List<LogcatLine> {
|
||||
val lineCount = 500
|
||||
val args =
|
||||
buildList {
|
||||
add("logcat")
|
||||
add("-d")
|
||||
add("-t")
|
||||
add(lineCount.toString())
|
||||
addAll(THIRD_PARTY_TAGS)
|
||||
add("*:V")
|
||||
}
|
||||
val process = ProcessBuilder().command(args).redirectErrorStream(true).start()
|
||||
val logLines = mutableListOf<LogcatLine>()
|
||||
try {
|
||||
val reader = BufferedReader(InputStreamReader(process.inputStream))
|
||||
var count = 0
|
||||
|
||||
while (count < lineCount) {
|
||||
val line = reader.readLine()
|
||||
if (line != null) {
|
||||
val level = line.split(" ").getOrNull(4)
|
||||
val logLevel =
|
||||
when (level?.uppercase()) {
|
||||
"V" -> Log.VERBOSE
|
||||
"D" -> Log.DEBUG
|
||||
"I" -> Log.INFO
|
||||
"W" -> Log.WARN
|
||||
"E" -> Log.ERROR
|
||||
else -> Log.VERBOSE
|
||||
}
|
||||
logLines.add(LogcatLine(logLevel, line))
|
||||
} else {
|
||||
break
|
||||
}
|
||||
count++
|
||||
}
|
||||
} finally {
|
||||
process.destroy()
|
||||
}
|
||||
return logLines
|
||||
}
|
||||
}
|
||||
|
||||
data class LogcatLine(
|
||||
val level: Int,
|
||||
val text: String,
|
||||
)
|
||||
|
||||
@Composable
|
||||
fun DebugPage(
|
||||
preferences: UserPreferences,
|
||||
modifier: Modifier = Modifier,
|
||||
viewModel: DebugViewModel = hiltViewModel(),
|
||||
) {
|
||||
val scrollAmount = 100f
|
||||
val columnState = rememberLazyListState()
|
||||
val scope = rememberCoroutineScope()
|
||||
|
||||
fun scroll(reverse: Boolean = false) {
|
||||
scope.launch(ExceptionHandler()) {
|
||||
columnState.scrollBy(if (reverse) -scrollAmount else scrollAmount)
|
||||
}
|
||||
}
|
||||
|
||||
val itemPlaybacks by viewModel.itemPlaybacks.observeAsState(listOf())
|
||||
val logcat by viewModel.logcat.observeAsState(listOf())
|
||||
|
||||
LazyColumn(
|
||||
state = columnState,
|
||||
contentPadding = PaddingValues(16.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(32.dp),
|
||||
modifier =
|
||||
modifier
|
||||
.focusable()
|
||||
.background(
|
||||
MaterialTheme.colorScheme.surface,
|
||||
).onKeyEvent {
|
||||
if (it.type == KeyEventType.KeyUp) {
|
||||
return@onKeyEvent false
|
||||
}
|
||||
if (it.key == Key.DirectionDown) {
|
||||
scroll(false)
|
||||
return@onKeyEvent true
|
||||
}
|
||||
if (it.key == Key.DirectionUp) {
|
||||
scroll(true)
|
||||
return@onKeyEvent true
|
||||
}
|
||||
return@onKeyEvent false
|
||||
},
|
||||
) {
|
||||
item {
|
||||
Column(
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
) {
|
||||
Text(
|
||||
text = "AppPreferences",
|
||||
style = MaterialTheme.typography.displaySmall,
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
)
|
||||
Text(
|
||||
text = preferences.appPreferences.toString(),
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
)
|
||||
}
|
||||
}
|
||||
item {
|
||||
Column(
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
) {
|
||||
Text(
|
||||
text = "App Information",
|
||||
style = MaterialTheme.typography.displaySmall,
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
)
|
||||
Text(
|
||||
text = "Build type: ${BuildConfig.BUILD_TYPE}",
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
)
|
||||
Text(
|
||||
text = "Debug enabled: ${BuildConfig.DEBUG}",
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
)
|
||||
}
|
||||
}
|
||||
item {
|
||||
Column(
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
) {
|
||||
Text(
|
||||
text = "User Information",
|
||||
style = MaterialTheme.typography.displaySmall,
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
)
|
||||
Text(
|
||||
text = "Current server: ${viewModel.serverRepository.currentServer}",
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
)
|
||||
Text(
|
||||
text = "Current user: ${viewModel.serverRepository.currentUser}",
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
)
|
||||
Text(
|
||||
text = "User server settings: ${preferences.userConfig}",
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
)
|
||||
}
|
||||
}
|
||||
item {
|
||||
Column(
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
) {
|
||||
Text(
|
||||
text = "Database",
|
||||
style = MaterialTheme.typography.displaySmall,
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
)
|
||||
Text(
|
||||
text = "ItemPlayback",
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
)
|
||||
itemPlaybacks.forEach {
|
||||
Text(
|
||||
text = it.toString(),
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
item {
|
||||
Column(
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
) {
|
||||
Text(
|
||||
text = "Logcat",
|
||||
style = MaterialTheme.typography.displaySmall,
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
)
|
||||
logcat.forEach { (level, line) ->
|
||||
val color =
|
||||
when (level) {
|
||||
Log.VERBOSE -> MaterialTheme.colorScheme.onSurface
|
||||
Log.DEBUG -> Color(0xff2bc4cf)
|
||||
Log.INFO -> Color(0xff2bcf8b)
|
||||
Log.WARN -> Color(0xffdde663)
|
||||
Log.ERROR -> Color(0xffe67063)
|
||||
else -> MaterialTheme.colorScheme.onSurface
|
||||
}
|
||||
Text(
|
||||
text = line,
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = color,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val THIRD_PARTY_TAGS =
|
||||
listOf(
|
||||
"libc:F",
|
||||
"ExoPlayerImpl:W",
|
||||
// FireTV
|
||||
"Codec2Client:E",
|
||||
"CCodecBuffers:E",
|
||||
"CCodecConfig:E",
|
||||
"okhttp.Http2:W",
|
||||
"okhttp.TaskRunner:W",
|
||||
"LruBitmapPool:W",
|
||||
"FragmentManager:W",
|
||||
"ConfigStore:W",
|
||||
"GlideRequest:W",
|
||||
"FactoryPools:W",
|
||||
"ViewTarget:W",
|
||||
"Engine:W",
|
||||
"Downsampler:W",
|
||||
"TransformationUtils:W",
|
||||
"DecodeJob:W",
|
||||
"BufferPoolAccessor2.0:W",
|
||||
"ExifInterface:W",
|
||||
"MediaCodec:W",
|
||||
"SurfaceUtils:W",
|
||||
"ByteArrayPool:W",
|
||||
"HardwareConfig:W",
|
||||
"DfltImageHeaderParser:W",
|
||||
)
|
||||
|
|
@ -90,4 +90,7 @@ sealed class Destination(
|
|||
|
||||
@Serializable
|
||||
data object License : Destination(true)
|
||||
|
||||
@Serializable
|
||||
data object Debug : Destination(true)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import com.github.damontecres.wholphin.ui.components.LicenseInfo
|
|||
import com.github.damontecres.wholphin.ui.detail.CollectionFolderGeneric
|
||||
import com.github.damontecres.wholphin.ui.detail.CollectionFolderMovie
|
||||
import com.github.damontecres.wholphin.ui.detail.CollectionFolderTv
|
||||
import com.github.damontecres.wholphin.ui.detail.DebugPage
|
||||
import com.github.damontecres.wholphin.ui.detail.PlaylistDetails
|
||||
import com.github.damontecres.wholphin.ui.detail.SeriesDetails
|
||||
import com.github.damontecres.wholphin.ui.detail.movie.MovieDetails
|
||||
|
|
@ -174,5 +175,7 @@ fun DestinationContent(
|
|||
userPreferences = preferences,
|
||||
modifier = modifier,
|
||||
)
|
||||
|
||||
Destination.Debug -> DebugPage(preferences, modifier)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -192,7 +192,7 @@ fun PreferencesContent(
|
|||
if (movementSounds) playOnClickSound(context)
|
||||
if (clickCount++ >= 2) {
|
||||
clickCount = 0
|
||||
// navigationManager.navigateTo(Destination.Debug)
|
||||
viewModel.navigationManager.navigateTo(Destination.Debug)
|
||||
}
|
||||
},
|
||||
summary = installedVersion.toString(),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue