Skip to content

Commit

Permalink
Switch to compose state; Move remaining methods
Browse files Browse the repository at this point in the history
  • Loading branch information
sunkup committed Apr 25, 2024
1 parent 543c740 commit 577c8a3
Show file tree
Hide file tree
Showing 3 changed files with 109 additions and 107 deletions.
59 changes: 0 additions & 59 deletions app/src/main/kotlin/at/bitfire/davdroid/ui/DebugInfoActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,13 @@

package at.bitfire.davdroid.ui

import android.content.Intent
import android.os.Bundle
import androidx.activity.compose.setContent
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ShareCompat
import androidx.core.content.FileProvider
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import at.bitfire.davdroid.BuildConfig
import at.bitfire.davdroid.R
import dagger.hilt.android.AndroidEntryPoint
import java.io.File
import javax.inject.Inject

/**
Expand Down Expand Up @@ -44,66 +38,13 @@ class DebugInfoActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

model.zipFile.observe(this) { zipFile ->
if (zipFile == null) return@observe

// ZIP file is ready
shareFile(
zipFile,
subject = "${getString(R.string.app_name)} ${BuildConfig.VERSION_NAME} debug info",
text = getString(R.string.debug_info_attached),
type = "*/*", // application/zip won't show all apps that can manage binary files, like ShareViaHttp
)

// only share ZIP file once
model.zipFile.value = null
}

setContent {
M2Theme {
DebugInfoScreen(
model,
onShareFile = { shareFile(it) },
onViewFile = { viewFile(it) },
onNavUp = { onNavigateUp() }
)
}
}
}

private fun shareFile(
file: File,
subject: String? = null,
text: String? = null,
type: String = "text/plain"
) {
val uri = FileProvider.getUriForFile(
this,
getString(R.string.authority_debug_provider),
file
)
ShareCompat.IntentBuilder(this)
.setSubject(subject)
.setText(text)
.setType(type)
.setStream(uri)
.startChooser()
}

private fun viewFile(
file: File,
title: String? = null
) {
val uri = FileProvider.getUriForFile(
this,
getString(R.string.authority_debug_provider),
file
)
val intent = Intent(Intent.ACTION_VIEW).apply {
setDataAndType(uri, "text/plain")
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
}
startActivity(Intent.createChooser(intent, title))
}

}
92 changes: 71 additions & 21 deletions app/src/main/kotlin/at/bitfire/davdroid/ui/DebugInfoModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,16 @@ import android.os.StatFs
import android.provider.CalendarContract
import android.provider.ContactsContract
import android.text.format.DateUtils
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.core.app.NotificationManagerCompat
import androidx.core.app.ShareCompat
import androidx.core.content.ContextCompat
import androidx.core.content.FileProvider
import androidx.core.content.getSystemService
import androidx.core.content.pm.PackageInfoCompat
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.viewModelScope
import androidx.work.WorkManager
import androidx.work.WorkQuery
Expand Down Expand Up @@ -110,16 +114,16 @@ class DebugInfoModel @AssistedInject constructor (
@Inject
lateinit var settings: SettingsManager

val cause = MutableLiveData<Throwable>()
var logFile = MutableLiveData<File>()
val localResource = MutableLiveData<String>()
val remoteResource = MutableLiveData<String>()
val debugInfo = MutableLiveData<File>()
var cause by mutableStateOf<Throwable?>(null)
var logFile by mutableStateOf<File?>(null)
var localResource by mutableStateOf<String?>(null)
var remoteResource by mutableStateOf<String?>(null)
var debugInfo by mutableStateOf<File?>(null)

// feedback for UI
val zipProgress = MutableLiveData(false)
val zipFile = MutableLiveData<File>()
val error = MutableLiveData<String>()
var zipProgress by mutableStateOf(false)
var zipFile by mutableStateOf<File?>(null)
var error by mutableStateOf<String?>(null)

init {
// create debug info directory
Expand All @@ -134,22 +138,22 @@ class DebugInfoModel @AssistedInject constructor (
file.writer().buffered().use { writer ->
IOUtils.copy(StringReader(logsText), writer)
}
logFile.postValue(file)
logFile = file
} else
Logger.log.warning("Can't write logs to $file")
} else Logger.getDebugLogFile()?.let { debugLogFile ->
if (debugLogFile.isFile && debugLogFile.canRead())
logFile.postValue(debugLogFile)
logFile = debugLogFile
}

val throwable = extras?.getSerializable(EXTRA_CAUSE) as? Throwable
cause.postValue(throwable)
cause = throwable

val local = extras?.getString(EXTRA_LOCAL_RESOURCE)
localResource.postValue(local)
localResource = local

val remote = extras?.getString(EXTRA_REMOTE_RESOURCE)
remoteResource.postValue(remote)
remoteResource = remote

generateDebugInfo(
extras?.getParcelable(EXTRA_ACCOUNT),
Expand All @@ -161,6 +165,52 @@ class DebugInfoModel @AssistedInject constructor (
}
}

fun shareZipFile() {
zipFile?.let {
shareFile(
it,
subject = "${context.getString(R.string.app_name)} ${BuildConfig.VERSION_NAME} debug info",
text = context.getString(R.string.debug_info_attached),
type = "*/*", // application/zip won't show all apps that can manage binary files, like ShareViaHttp
)
}
}

fun shareFile(
file: File,
subject: String? = null,
text: String? = null,
type: String = "text/plain"
) {
val uri = FileProvider.getUriForFile(
context,
context.getString(R.string.authority_debug_provider),
file
)
ShareCompat.IntentBuilder(context)
.setSubject(subject)
.setText(text)
.setType(type)
.setStream(uri)
.startChooser()
}

fun viewFile(
file: File,
title: String? = null
) {
val uri = FileProvider.getUriForFile(
context,
context.getString(R.string.authority_debug_provider),
file
)
val intent = Intent(Intent.ACTION_VIEW).apply {
setDataAndType(uri, "text/plain")
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
}
context.startActivity(Intent.createChooser(intent, title))
}

private fun generateDebugInfo(syncAccount: Account?, syncAuthority: String?, cause: Throwable?, localResource: String?, remoteResource: String?) {
val debugInfoFile = File(Logger.debugDir(), FILE_DEBUG_INFO)
debugInfoFile.writer().buffered().use { writer ->
Expand Down Expand Up @@ -413,26 +463,26 @@ class DebugInfoModel @AssistedInject constructor (
writer.append("--- END DEBUG INFO ---\n")
writer.toString()
}
debugInfo.postValue(debugInfoFile)
debugInfo = debugInfoFile
}

fun generateZip() {
try {
zipProgress.postValue(true)
zipProgress = true

val file = File(Logger.debugDir(), "davx5-debug.zip")
Logger.log.fine("Writing debug info to ${file.absolutePath}")
ZipOutputStream(file.outputStream().buffered()).use { zip ->
zip.setLevel(9)
debugInfo.value?.let { debugInfo ->
debugInfo?.let { debugInfo ->
zip.putNextEntry(ZipEntry("debug-info.txt"))
debugInfo.inputStream().use {
IOUtils.copy(it, zip)
}
zip.closeEntry()
}

val logs = logFile.value
val logs = logFile
if (logs != null) {
// verbose logs available
zip.putNextEntry(ZipEntry(logs.name))
Expand All @@ -454,12 +504,12 @@ class DebugInfoModel @AssistedInject constructor (
}

// success, show ZIP file
zipFile.postValue(file)
zipFile = file
} catch (e: Exception) {
Logger.log.log(Level.SEVERE, "Couldn't generate debug info ZIP", e)
error.postValue(e.localizedMessage)
error = e.localizedMessage
} finally {
zipProgress.postValue(false)
zipProgress = false
}
}

Expand Down
65 changes: 38 additions & 27 deletions app/src/main/kotlin/at/bitfire/davdroid/ui/DebugInfoScreen.kt
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,6 @@ import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.runtime.remember
import androidx.compose.ui.BiasAlignment
import androidx.compose.ui.Modifier
Expand All @@ -35,34 +33,31 @@ import at.bitfire.dav4jvm.exception.HttpException
import at.bitfire.davdroid.R
import at.bitfire.davdroid.ui.composable.BasicTopAppBar
import at.bitfire.davdroid.ui.composable.CardWithImage
import java.io.File
import java.io.IOError
import java.io.IOException

@Composable
fun DebugInfoScreen(
model: DebugInfoModel,
onShareFile: (File) -> Unit,
onViewFile: (File) -> Unit,
onNavUp: () -> Unit
) {
val debugInfo by model.debugInfo.observeAsState()
val zipProgress by model.zipProgress.observeAsState(false)
val modelCause by model.cause.observeAsState()
val localResource by model.localResource.observeAsState()
val remoteResource by model.remoteResource.observeAsState()
val logFile by model.logFile.observeAsState()
val error by model.error.observeAsState()
val debugInfo = model.debugInfo
val zipProgress = model.zipProgress
val modelCause = model.cause
val localResource = model.localResource
val remoteResource = model.remoteResource
val logFile = model.logFile
val error = model.error

AppTheme {
DebugInfoScreen(
error,
onResetError = { model.error.value = null },
debugInfo != null,
zipProgress,
modelCause != null,
error = error,
onResetError = { model.error = null },
showDebugInfo = debugInfo != null,
zipProgress = zipProgress,
showModelCause = modelCause != null,
modelCauseTitle = when (modelCause) {
is HttpException -> stringResource(if ((modelCause as HttpException).code / 100 == 5) R.string.debug_info_server_error else R.string.debug_info_http_error)
is HttpException -> stringResource(if (modelCause.code / 100 == 5) R.string.debug_info_server_error else R.string.debug_info_http_error)
is DavException -> stringResource(R.string.debug_info_webdav_error)
is IOException, is IOError -> stringResource(R.string.debug_info_io_error)
else -> modelCause?.let { it::class.java.simpleName }
Expand All @@ -71,21 +66,23 @@ fun DebugInfoScreen(
modelCauseMessage = stringResource(
if (modelCause is HttpException)
when {
(modelCause as HttpException).code == 403 -> R.string.debug_info_http_403_description
(modelCause as HttpException).code == 404 -> R.string.debug_info_http_404_description
(modelCause as HttpException).code / 100 == 5 -> R.string.debug_info_http_5xx_description
modelCause.code == 403 -> R.string.debug_info_http_403_description
modelCause.code == 404 -> R.string.debug_info_http_404_description
modelCause.code / 100 == 5 -> R.string.debug_info_http_5xx_description
else -> R.string.debug_info_unexpected_error
}
else
R.string.debug_info_unexpected_error
),
localResource,
remoteResource,
logFile != null,
localResource = localResource,
remoteResource = remoteResource,
hasLogFile = logFile != null,
onResetZipFile = { model.zipFile = null },
onGenerateZip = { model.generateZip() },
onShareLogsFile = { logFile?.let { onShareFile(it) } },
onViewDebugFile = { debugInfo?.let { onViewFile(it) } },
onNavUp
onShareZipFile = { model.shareZipFile() },
onShareLogsFile = { logFile?.let { model.shareFile(it) } },
onViewDebugFile = { debugInfo?.let { model.viewFile(it) } },
onNavUp = onNavUp
)
}
}
Expand All @@ -103,13 +100,25 @@ fun DebugInfoScreen(
localResource: String?,
remoteResource: String?,
hasLogFile: Boolean,
onResetZipFile: () -> Unit,
onGenerateZip: () -> Unit,
onShareZipFile: () -> Unit,
onShareLogsFile: () -> Unit,
onViewDebugFile: () -> Unit,
onNavUp: () -> Unit
) {
val snackbarHostState = remember { SnackbarHostState() }

LaunchedEffect(zipProgress) {
if (!zipProgress) return@LaunchedEffect

// ZIP file is ready
onShareZipFile()

// only share ZIP file once
onResetZipFile()
}

Scaffold(
floatingActionButton = {
if (showDebugInfo && !zipProgress) {
Expand Down Expand Up @@ -285,7 +294,9 @@ fun DebugInfoScreen_Preview() {
localResource = "local-resource-string",
remoteResource = "remote-resource-string",
hasLogFile = true,
onResetZipFile = {},
onGenerateZip = {},
onShareZipFile = {},
onShareLogsFile = {},
onViewDebugFile = {},
onNavUp = {},
Expand Down

0 comments on commit 577c8a3

Please sign in to comment.