Migrate DebugInfoActivity to Compose (#509)

* Gave more flexibility

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Migrated to Compose

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Adjusted paddings

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Added missing observer

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* CardWithImagE: add another preview with subtitle, icon and content

* Made buttons uppercase

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Adjusted spacings

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Changed snackbar host state

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Changed nullable expression

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Using shareFile for zip

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Minor changes (comments/formatting)

* Switched to view instead of sharing files

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Adapted image height for landscape

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* CardView: allow to pass image alignment; use card_theme_max_height

* DebugInfoActivity: paddings, images

---------

Signed-off-by: Arnau Mora <arnyminerz@proton.me>
Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>
Co-authored-by: Ricki Hirner <hirner@bitfire.at>
This commit is contained in:
Arnau Mora 2024-01-11 08:31:08 -08:00 committed by GitHub
parent 3c4601f7a1
commit b8f4b9af30
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 373 additions and 603 deletions

View file

@ -3,31 +3,72 @@
**************************************************************************************************/
package at.bitfire.davdroid.ui
import android.accounts.Account
import android.accounts.AccountManager
import android.app.Application
import android.app.usage.UsageStatsManager
import android.content.*
import android.content.ContentProviderClient
import android.content.ContentResolver
import android.content.ContentUris
import android.content.Context
import android.content.Intent
import android.content.pm.ApplicationInfo
import android.content.pm.PackageManager
import android.net.ConnectivityManager
import android.net.Uri
import android.os.*
import android.os.Build
import android.os.Bundle
import android.os.Environment
import android.os.LocaleList
import android.os.PowerManager
import android.os.StatFs
import android.provider.CalendarContract
import android.provider.ContactsContract
import android.text.format.DateUtils
import android.view.View
import androidx.activity.compose.setContent
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material.FloatingActionButton
import androidx.compose.material.Icon
import androidx.compose.material.LinearProgressIndicator
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Scaffold
import androidx.compose.material.SnackbarDuration
import androidx.compose.material.SnackbarHost
import androidx.compose.material.SnackbarHostState
import androidx.compose.material.Text
import androidx.compose.material.TextButton
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.Adb
import androidx.compose.material.icons.rounded.BugReport
import androidx.compose.material.icons.rounded.Info
import androidx.compose.material.icons.rounded.Share
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
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.unit.dp
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.databinding.DataBindingUtil
import androidx.lifecycle.*
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope
import androidx.work.WorkManager
import androidx.work.WorkQuery
import at.bitfire.dav4jvm.exception.DavException
@ -36,7 +77,6 @@ import at.bitfire.davdroid.BuildConfig
import at.bitfire.davdroid.InvalidAccountException
import at.bitfire.davdroid.R
import at.bitfire.davdroid.TextTable
import at.bitfire.davdroid.databinding.ActivityDebugInfoBinding
import at.bitfire.davdroid.db.AppDatabase
import at.bitfire.davdroid.log.Logger
import at.bitfire.davdroid.resource.LocalAddressBook
@ -44,10 +84,11 @@ import at.bitfire.davdroid.settings.AccountSettings
import at.bitfire.davdroid.settings.SettingsManager
import at.bitfire.davdroid.syncadapter.PeriodicSyncWorker
import at.bitfire.davdroid.syncadapter.SyncWorker
import at.bitfire.davdroid.ui.widget.CardWithImage
import at.bitfire.ical4android.TaskProvider
import at.bitfire.ical4android.TaskProvider.ProviderName
import at.techbee.jtx.JtxContract
import com.google.android.material.snackbar.Snackbar
import com.google.accompanist.themeadapter.material.MdcTheme
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
@ -61,7 +102,11 @@ import org.apache.commons.io.IOUtils
import org.apache.commons.lang3.StringUtils
import org.apache.commons.lang3.exception.ExceptionUtils
import org.dmfs.tasks.contract.TaskContract
import java.io.*
import java.io.File
import java.io.IOError
import java.io.IOException
import java.io.StringReader
import java.io.Writer
import java.util.TimeZone
import java.util.logging.Level
import java.util.zip.ZipEntry
@ -120,101 +165,236 @@ class DebugInfoActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding = DataBindingUtil.setContentView<ActivityDebugInfoBinding>(this, R.layout.activity_debug_info)
binding.model = model
binding.lifecycleOwner = this
model.cause.observe(this) { cause ->
if (cause == null)
return@observe
binding.causeCaption.text = when (cause) {
is HttpException -> getString(if (cause.code / 100 == 5) R.string.debug_info_server_error else R.string.debug_info_http_error)
is DavException -> getString(R.string.debug_info_webdav_error)
is IOException, is IOError -> getString(R.string.debug_info_io_error)
else -> cause::class.java.simpleName
}
binding.causeText.text = getString(
if (cause is HttpException)
when {
cause.code == 403 -> R.string.debug_info_http_403_description
cause.code == 404 -> R.string.debug_info_http_404_description
cause.code / 100 == 5 -> R.string.debug_info_http_5xx_description
else -> R.string.debug_info_unexpected_error
}
else
R.string.debug_info_unexpected_error
)
}
model.debugInfo.observe(this) { debugInfo ->
val showDebugInfo = View.OnClickListener {
val uri = FileProvider.getUriForFile(
this,
getString(R.string.authority_debug_provider),
debugInfo
)
val intent = Intent(Intent.ACTION_VIEW)
intent.setDataAndType(uri, "text/plain")
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
startActivity(Intent.createChooser(intent, null))
}
binding.causeView.setOnClickListener(showDebugInfo)
binding.debugInfoView.setOnClickListener(showDebugInfo)
binding.fab.apply {
setOnClickListener { shareArchive() }
isEnabled = true
}
binding.zipShare.setOnClickListener { shareArchive() }
}
model.logFile.observe(this) { logs ->
binding.logsView.setOnClickListener {
val uri = FileProvider.getUriForFile(this, getString(R.string.authority_debug_provider), logs)
val intent = Intent(Intent.ACTION_VIEW)
intent.setDataAndType(uri, "text/plain")
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
startActivity(Intent.createChooser(intent, null))
}
}
model.zipFile.observe(this) { zipFile ->
if (zipFile != null) {
// ZIP file is ready
val builder = ShareCompat.IntentBuilder(this)
.setSubject("${getString(R.string.app_name)} ${BuildConfig.VERSION_NAME} debug info")
.setText(getString(R.string.debug_info_attached))
.setType("*/*") // application/zip won't show all apps that can manage binary files, like ShareViaHttp
.setStream(
FileProvider.getUriForFile(
this,
getString(R.string.authority_debug_provider),
zipFile
)
)
builder.intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_ACTIVITY_NEW_TASK)
builder.startChooser()
if (zipFile == null) return@observe
// Not beautiful, because it changes model data from the view.
// See https://github.com/android/architecture-components-samples/issues/63
model.zipFile.value = null
}
// 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
}
model.error.observe(this) { message ->
if (message != null) {
Snackbar.make(binding.fab, message, Snackbar.LENGTH_LONG).show()
setContent {
MdcTheme {
val debugInfo by model.debugInfo.observeAsState()
val zipProgress by model.zipProgress.observeAsState(false)
// Reset error message so that it won't be shown when activity is re-created
model.error.value = null
val snackbarHostState = remember { SnackbarHostState() }
Scaffold(
floatingActionButton = {
if (debugInfo != null && !zipProgress) {
FloatingActionButton(
onClick = model::generateZip
) {
Icon(Icons.Rounded.Share, stringResource(R.string.share))
}
}
},
snackbarHost = {
SnackbarHost(hostState = snackbarHostState)
}
) { paddingValues ->
val error by model.error.observeAsState()
LaunchedEffect(error) {
error?.let {
snackbarHostState.showSnackbar(
message = it,
duration = SnackbarDuration.Long
)
model.error.value = null
}
}
Content(paddingValues)
}
}
}
}
fun shareArchive() {
model.generateZip()
@Composable
fun Content(paddingValues: PaddingValues) {
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()
LazyColumn(
modifier = Modifier
.fillMaxSize()
.padding(paddingValues)
) {
if (debugInfo == null) item { LinearProgressIndicator() }
if (debugInfo != null) {
if (zipProgress) item { LinearProgressIndicator() }
item {
CardWithImage(
image = painterResource(R.drawable.undraw_server_down),
imageAlignment = BiasAlignment(0f, .7f),
title = stringResource(R.string.debug_info_archive_caption),
subtitle = stringResource(R.string.debug_info_archive_subtitle),
message = stringResource(R.string.debug_info_archive_text),
icon = Icons.Rounded.Share,
modifier = Modifier.padding(horizontal = 8.dp, vertical = 8.dp)
) {
TextButton(
onClick = model::generateZip,
enabled = !zipProgress
) {
Text(
stringResource(R.string.debug_info_archive_share).uppercase()
)
}
}
}
}
modelCause?.let { cause ->
item {
CardWithImage(
title = when (cause) {
is HttpException -> stringResource(if (cause.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 -> cause::class.java.simpleName
},
subtitle = cause.localizedMessage,
message = stringResource(
if (cause is HttpException)
when {
cause.code == 403 -> R.string.debug_info_http_403_description
cause.code == 404 -> R.string.debug_info_http_404_description
cause.code / 100 == 5 -> R.string.debug_info_http_5xx_description
else -> R.string.debug_info_unexpected_error
}
else
R.string.debug_info_unexpected_error
),
icon = Icons.Rounded.Info,
modifier = Modifier.padding(horizontal = 8.dp, vertical = 8.dp)
) {
TextButton(
enabled = debugInfo != null,
onClick = { viewFile(debugInfo!!) }
) {
Text(
stringResource(R.string.debug_info_view_details).uppercase()
)
}
}
}
}
debugInfo?.let { info ->
item {
CardWithImage(
title = stringResource(R.string.debug_info_title),
subtitle = stringResource(R.string.debug_info_subtitle),
icon = Icons.Rounded.BugReport,
modifier = Modifier.padding(horizontal = 8.dp, vertical = 8.dp)
) {
TextButton(
onClick = { viewFile(info) }
) {
Text(
stringResource(R.string.debug_info_view_details).uppercase()
)
}
}
}
}
if (localResource != null || remoteResource != null) item {
CardWithImage(
title = stringResource(R.string.debug_info_involved_caption),
subtitle = stringResource(R.string.debug_info_involved_subtitle),
icon = Icons.Rounded.Adb,
modifier = Modifier.padding(horizontal = 8.dp, vertical = 8.dp)
) {
remoteResource?.let {
Text(
text = stringResource(R.string.debug_info_involved_remote),
style = MaterialTheme.typography.body1
)
Text(
text = it,
fontFamily = FontFamily.Monospace,
modifier = Modifier.padding(bottom = 8.dp)
)
}
localResource?.let {
Text(
text = stringResource(R.string.debug_info_involved_local),
style = MaterialTheme.typography.body1
)
Text(
text = it,
fontFamily = FontFamily.Monospace,
modifier = Modifier.padding(bottom = 8.dp)
)
}
}
}
logFile?.let { logs ->
item {
CardWithImage(
title = stringResource(R.string.debug_info_logs_caption),
subtitle = stringResource(R.string.debug_info_logs_subtitle),
icon = Icons.Rounded.BugReport,
modifier = Modifier.padding(horizontal = 8.dp, vertical = 8.dp)
) {
TextButton(
onClick = { shareFile(logs) }
) {
Text(
stringResource(R.string.debug_info_logs_view).uppercase()
)
}
}
}
}
}
}
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))
}

View file

@ -32,6 +32,7 @@ import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.BiasAlignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
@ -209,6 +210,7 @@ fun TasksCard(
) {
CardWithImage(
image = painterResource(R.drawable.intro_tasks),
imageAlignment = BiasAlignment(0f, .1f),
title = stringResource(R.string.intro_tasks_title),
message = stringResource(R.string.intro_tasks_text1),
modifier = Modifier

View file

@ -4,60 +4,113 @@
package at.bitfire.davdroid.ui.widget
import android.content.res.Configuration.ORIENTATION_PORTRAIT
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material.Card
import androidx.compose.material.Icon
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.TabletAndroid
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.BiasAlignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.dimensionResource
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import at.bitfire.davdroid.R
@Composable
fun CardWithImage(
image: Painter,
title: String,
message: String,
modifier: Modifier = Modifier,
image: Painter? = null,
imageContentDescription: String? = null,
imageAlignment: Alignment = Alignment.Center,
message: String? = null,
subtitle: String? = null,
icon: ImageVector? = null,
iconContentDescription: String? = null,
content: @Composable ColumnScope.() -> Unit = {}
) {
val configuration = LocalConfiguration.current
Card(modifier) {
Column(
modifier = Modifier.fillMaxWidth()
) {
Image(
painter = image,
contentDescription = imageContentDescription,
modifier = Modifier.fillMaxWidth()
)
image?.let {
Image(
painter = it,
contentDescription = imageContentDescription,
modifier = Modifier
.fillMaxWidth()
.heightIn(max = dimensionResource(R.dimen.card_theme_max_height)),
contentScale = ContentScale.Crop,
alignment = imageAlignment
)
}
Column(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp)
) {
Text(
text = title,
Row(
modifier = Modifier
.fillMaxWidth()
.padding(top = 12.dp),
style = MaterialTheme.typography.h6
)
Text(
text = message,
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 12.dp),
style = MaterialTheme.typography.body1
)
verticalAlignment = Alignment.CenterVertically
) {
icon?.let {
Icon(
imageVector = it,
contentDescription = iconContentDescription,
modifier = Modifier
.size(44.dp)
.padding(end = 12.dp)
)
}
Column(Modifier.fillMaxWidth()) {
Text(
text = title,
modifier = Modifier.fillMaxWidth(),
style = MaterialTheme.typography.h6
)
subtitle?.let {
Text(
text = it,
modifier = Modifier.fillMaxWidth(),
style = MaterialTheme.typography.subtitle1
)
}
}
}
message?.let {
Text(
text = it,
modifier = Modifier
.fillMaxWidth()
.padding(top = 12.dp),
style = MaterialTheme.typography.body1
)
}
content()
}
@ -67,10 +120,34 @@ fun CardWithImage(
@Preview
@Composable
fun CardWithImagePreview() {
fun CardWithImage_Preview() {
CardWithImage(
image = painterResource(R.drawable.intro_tasks),
title = "Demo card",
message = "This is the message to be displayed under the title, but before the content."
)
}
@Preview
@Composable
fun CardWithImage_Preview_WithIconAndSubtitleAndContent() {
CardWithImage(
title = "Demo card",
icon = Icons.Default.TabletAndroid,
subtitle = "Subtitle",
message = "This is the message to be displayed under the title, but before the content."
) {
Text("Content")
}
}
@Preview
@Composable
fun CardWithImage_Preview_WithIconAndContentNoMessage() {
CardWithImage(
title = "Demo card",
icon = Icons.Default.TabletAndroid
) {
Text("Content")
}
}

View file

@ -1,489 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
tools:context=".ui.DebugInfoActivity">
<data>
<import type="android.view.View"/>
<variable
name="model"
type="at.bitfire.davdroid.ui.DebugInfoActivity.ReportModel"/>
</data>
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="@dimen/activity_margin"
android:orientation="vertical">
<ProgressBar
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:indeterminate="true"
style="@style/Widget.AppCompat.ProgressBar.Horizontal"
android:visibility="@{model.debugInfo == null ? View.VISIBLE : View.GONE}"
tools:visibility="visible" />
<com.google.android.material.card.MaterialCardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:visibility="@{model.debugInfo != null ? View.VISIBLE : View.GONE}">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/card_padding">
<androidx.constraintlayout.widget.Guideline
android:id="@+id/zipStart"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintGuide_begin="@dimen/card_padding" />
<androidx.constraintlayout.widget.Guideline
android:id="@+id/zipEnd"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintGuide_end="@dimen/card_padding" />
<ProgressBar
android:id="@+id/zipProgress"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
android:indeterminate="true"
style="@style/Widget.AppCompat.ProgressBar.Horizontal"
android:visibility="@{model.zipProgress ? View.VISIBLE : View.GONE}"
tools:visibility="visible" />
<ImageView
android:id="@+id/zipIcon"
android:layout_width="44dp"
android:layout_height="0dp"
android:paddingEnd="12dp"
android:layout_marginTop="@dimen/card_margin_title_text"
app:layout_constraintTop_toBottomOf="@id/zipProgress"
app:layout_constraintLeft_toLeftOf="@id/zipStart"
app:layout_constraintRight_toLeftOf="@id/zipCaption"
app:layout_constraintBottom_toBottomOf="@id/zipSubtitle"
android:src="@drawable/ic_share" />
<TextView
android:id="@+id/zipCaption"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintLeft_toRightOf="@id/zipIcon"
app:layout_constraintTop_toTopOf="@id/zipIcon"
app:layout_constraintRight_toRightOf="@id/zipEnd"
app:layout_constraintBottom_toTopOf="@id/zipSubtitle"
style="@style/TextAppearance.MaterialComponents.Headline6"
android:text="@string/debug_info_archive_caption" />
<TextView
android:id="@+id/zipSubtitle"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintLeft_toRightOf="@id/zipIcon"
app:layout_constraintTop_toBottomOf="@id/zipCaption"
app:layout_constraintRight_toRightOf="@id/zipEnd"
app:layout_constraintBottom_toTopOf="@id/zipText"
style="@style/TextAppearance.MaterialComponents.Subtitle1"
android:text="@string/debug_info_archive_subtitle" />
<TextView
android:id="@+id/zipText"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@id/zipSubtitle"
app:layout_constraintLeft_toLeftOf="@id/zipStart"
app:layout_constraintRight_toRightOf="@id/zipEnd"
android:layout_marginTop="@dimen/card_margin_title_text"
style="@style/TextAppearance.MaterialComponents.Body1"
android:text="@string/debug_info_archive_text"/>
<com.google.android.material.button.MaterialButton
android:id="@+id/zipShare"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/card_margin_title_text"
app:layout_constraintTop_toBottomOf="@id/zipText"
app:layout_constraintLeft_toLeftOf="@id/zipStart"
app:layout_constraintBottom_toBottomOf="parent"
style="@style/Widget.MaterialComponents.Button.TextButton"
android:enabled="@{model.debugInfo != null &amp;&amp; !model.zipProgress}"
android:text="@string/debug_info_archive_share" />
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.card.MaterialCardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:visibility="@{model.cause == null ? View.GONE : View.VISIBLE}">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/card_padding"
android:orientation="vertical">
<androidx.constraintlayout.widget.Guideline
android:id="@+id/causeStart"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintGuide_begin="@dimen/card_padding" />
<androidx.constraintlayout.widget.Guideline
android:id="@+id/causeEnd"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintGuide_end="@dimen/card_padding" />
<at.bitfire.davdroid.ui.widget.CropImageView
android:id="@+id/causeTheme"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:maxHeight="@dimen/card_theme_max_height"
app:layout_constraintVertical_chainStyle="packed"
app:layout_constraintVertical_bias="0"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintBottom_toTopOf="@id/causeIcon"
android:adjustViewBounds="true"
app:verticalOffsetPercent=".85"
app:srcCompat="@drawable/undraw_server_down" />
<ImageView
android:id="@+id/causeIcon"
android:layout_width="44dp"
android:layout_height="0dp"
android:paddingEnd="12dp"
android:layout_marginTop="@dimen/card_margin_title_text"
app:layout_constraintTop_toBottomOf="@id/causeTheme"
app:layout_constraintLeft_toLeftOf="@id/causeStart"
app:layout_constraintRight_toLeftOf="@id/causeCaption"
app:layout_constraintBottom_toBottomOf="@id/causeSubtitle"
android:src="@drawable/ic_info" />
<TextView
android:id="@+id/causeCaption"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintLeft_toRightOf="@id/causeIcon"
app:layout_constraintTop_toTopOf="@id/causeIcon"
app:layout_constraintRight_toRightOf="@id/causeEnd"
app:layout_constraintBottom_toTopOf="@id/causeSubtitle"
style="@style/TextAppearance.MaterialComponents.Headline6"
tools:text="SampleException" />
<TextView
android:id="@+id/causeSubtitle"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintLeft_toRightOf="@id/causeIcon"
app:layout_constraintTop_toBottomOf="@id/causeCaption"
app:layout_constraintRight_toRightOf="@id/causeEnd"
app:layout_constraintBottom_toTopOf="@id/causeText"
style="@style/TextAppearance.MaterialComponents.Subtitle1"
tools:text="Couldn't find …"
android:text="@{model.cause.localizedMessage}" />
<TextView
android:id="@+id/causeText"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@id/causeSubtitle"
app:layout_constraintLeft_toLeftOf="@id/causeStart"
app:layout_constraintRight_toRightOf="@id/causeEnd"
android:layout_marginTop="@dimen/card_margin_title_text"
style="@style/TextAppearance.MaterialComponents.Body1"
tools:text="Error explanation"/>
<com.google.android.material.button.MaterialButton
android:id="@+id/causeView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/card_margin_title_text"
app:layout_constraintTop_toBottomOf="@id/causeText"
app:layout_constraintLeft_toLeftOf="@id/causeStart"
app:layout_constraintBottom_toBottomOf="parent"
style="@style/Widget.MaterialComponents.Button.TextButton"
android:text="@string/debug_info_view_details"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.card.MaterialCardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:visibility="@{(model.debugInfo != null &amp;&amp; model.cause == null) ? View.VISIBLE : View.GONE}">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/card_padding"
android:orientation="vertical">
<androidx.constraintlayout.widget.Guideline
android:id="@+id/debugInfoStart"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintGuide_begin="@dimen/card_padding" />
<androidx.constraintlayout.widget.Guideline
android:id="@+id/debugInfoEnd"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintGuide_end="@dimen/card_padding" />
<at.bitfire.davdroid.ui.widget.CropImageView
android:id="@+id/debugInfoTheme"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:maxHeight="@dimen/card_theme_max_height"
app:layout_constraintVertical_chainStyle="packed"
app:layout_constraintVertical_bias="0"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintBottom_toTopOf="@id/debugInfoIcon"
android:adjustViewBounds="true"
app:verticalOffsetPercent=".85"
app:srcCompat="@drawable/undraw_server_down" />
<ImageView
android:id="@+id/debugInfoIcon"
android:layout_width="44dp"
android:layout_height="0dp"
android:paddingEnd="12dp"
android:layout_marginTop="@dimen/card_margin_title_text"
app:layout_constraintTop_toBottomOf="@id/debugInfoTheme"
app:layout_constraintLeft_toLeftOf="@id/debugInfoStart"
app:layout_constraintRight_toLeftOf="@id/debugInfoCaption"
app:layout_constraintBottom_toBottomOf="@id/debugInfoSubtitle"
android:src="@drawable/ic_bug_report"/>
<TextView
android:id="@+id/debugInfoCaption"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintLeft_toRightOf="@id/debugInfoIcon"
app:layout_constraintTop_toTopOf="@id/debugInfoIcon"
app:layout_constraintRight_toRightOf="@id/debugInfoEnd"
app:layout_constraintBottom_toTopOf="@id/debugInfoSubtitle"
android:text="@string/debug_info_title"
style="@style/TextAppearance.MaterialComponents.Headline6"/>
<TextView
android:id="@+id/debugInfoSubtitle"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintLeft_toRightOf="@id/debugInfoIcon"
app:layout_constraintTop_toBottomOf="@id/debugInfoCaption"
app:layout_constraintRight_toRightOf="@id/debugInfoEnd"
app:layout_constraintBottom_toTopOf="@id/debugInfoView"
android:text="@string/debug_info_subtitle"
style="@style/TextAppearance.MaterialComponents.Subtitle1"/>
<com.google.android.material.button.MaterialButton
android:id="@+id/debugInfoView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/card_margin_title_text"
app:layout_constraintTop_toBottomOf="@id/debugInfoSubtitle"
app:layout_constraintLeft_toLeftOf="@id/debugInfoStart"
app:layout_constraintBottom_toBottomOf="parent"
style="@style/Widget.MaterialComponents.Button.TextButton"
android:text="@string/debug_info_view_details"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.card.MaterialCardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:visibility="@{(model.localResource != null || model.remoteResource != null) ? View.VISIBLE : View.GONE}">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="@dimen/card_padding">
<ImageView
android:id="@+id/resIcon"
android:layout_width="44dp"
android:layout_height="0dp"
android:paddingEnd="12dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toLeftOf="@id/resCaption"
app:layout_constraintBottom_toBottomOf="@id/resSubtitle"
android:src="@drawable/ic_adb"/>
<TextView
android:id="@+id/resCaption"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintLeft_toRightOf="@id/resIcon"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintBottom_toTopOf="@id/resSubtitle"
android:text="@string/debug_info_involved_caption"
style="@style/TextAppearance.MaterialComponents.Headline6"/>
<TextView
android:id="@+id/resSubtitle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/card_margin_title_text"
app:layout_constraintLeft_toRightOf="@id/resIcon"
app:layout_constraintTop_toBottomOf="@id/resCaption"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintBottom_toTopOf="@id/resRemoteTitle"
android:text="@string/debug_info_involved_subtitle"
style="@style/TextAppearance.MaterialComponents.Subtitle1"/>
<TextView
android:id="@+id/resRemoteTitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="@{model.remoteResource != null ? View.VISIBLE : View.GONE}"
style="@style/TextAppearance.MaterialComponents.Body1"
app:layout_constraintTop_toBottomOf="@id/resSubtitle"
app:layout_constraintBottom_toTopOf="@id/resRemote"
android:text="@string/debug_info_involved_remote"/>
<TextView
android:id="@+id/resRemote"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
app:layout_constraintTop_toBottomOf="@id/resRemoteTitle"
app:layout_constraintBottom_toTopOf="@id/resLocalTitle"
android:visibility="@{model.remoteResource != null ? View.VISIBLE : View.GONE}"
android:fontFamily="monospace"
android:textIsSelectable="true"
android:text="@{model.remoteResource}"
tools:text="https://example.com"/>
<TextView
android:id="@+id/resLocalTitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="@{model.localResource != null ? View.VISIBLE : View.GONE}"
style="@style/TextAppearance.MaterialComponents.Body1"
app:layout_constraintTop_toBottomOf="@id/resRemote"
app:layout_constraintBottom_toTopOf="@id/resLocal"
android:text="@string/debug_info_involved_local"/>
<TextView
android:id="@+id/resLocal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@id/resLocalTitle"
app:layout_constraintBottom_toBottomOf="parent"
android:visibility="@{model.localResource != null ? View.VISIBLE : View.GONE}"
android:fontFamily="monospace"
android:textIsSelectable="true"
android:text="@{model.localResource}"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.card.MaterialCardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:visibility="@{model.logFile == null ? View.GONE : View.VISIBLE}">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="@dimen/card_padding">
<ImageView
android:id="@+id/logsIcon"
android:layout_width="44dp"
android:layout_height="0dp"
android:paddingEnd="12dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toLeftOf="@id/logsCaption"
app:layout_constraintBottom_toBottomOf="@id/logsSubtitle"
android:src="@drawable/ic_adb"/>
<TextView
android:id="@+id/logsCaption"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintLeft_toRightOf="@id/logsIcon"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintBottom_toTopOf="@id/logsSubtitle"
android:text="@string/debug_info_logs_caption"
style="@style/TextAppearance.MaterialComponents.Headline6"/>
<TextView
android:id="@+id/logsSubtitle"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintLeft_toRightOf="@id/logsIcon"
app:layout_constraintTop_toBottomOf="@id/logsCaption"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintBottom_toTopOf="@id/logsView"
android:text="@string/debug_info_logs_subtitle"
style="@style/TextAppearance.MaterialComponents.Subtitle1"/>
<com.google.android.material.button.MaterialButton
android:id="@+id/logsView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/card_margin_title_text"
app:layout_constraintTop_toBottomOf="@id/logsSubtitle"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
style="@style/Widget.MaterialComponents.Button.TextButton"
android:text="@string/debug_info_logs_view"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.card.MaterialCardView>
</LinearLayout>
</ScrollView>
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|right"
android:layout_margin="@dimen/fab_margin"
android:contentDescription="@string/share"
android:enabled="@{model.debugInfo != null &amp;&amp; !model.zipProgress}"
android:src="@drawable/ic_share"/>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
</layout>