Show additional bug report info in AboutActivity (#3802)

Make it easier for people to find information we need for a bug report,
and show it on AboutActivity.

New info is:

- Device manufacturer (e.g., "Google") and model (e.g., "Pixel 4a (5G)")
- Android version (e.g., "13")
- SDK version (e.g., "33")
- Active account (e.g., "@Tusky@mastodon.social")
- Server's version (e.g., "4.1.2+nightly-20230627")

All info is copyable to make it easy to include in a bug report. A
button to copy the information is also shown.
This commit is contained in:
Nik Clayton 2023-08-03 12:20:35 +02:00 committed by GitHub
parent 561af5f105
commit 9cda091d03
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 183 additions and 46 deletions

View File

@ -1,6 +1,10 @@
package com.keylesspalace.tusky package com.keylesspalace.tusky
import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
import android.content.Intent import android.content.Intent
import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.text.SpannableString import android.text.SpannableString
import android.text.SpannableStringBuilder import android.text.SpannableStringBuilder
@ -8,13 +12,21 @@ import android.text.method.LinkMovementMethod
import android.text.style.URLSpan import android.text.style.URLSpan
import android.text.util.Linkify import android.text.util.Linkify
import android.widget.TextView import android.widget.TextView
import android.widget.Toast
import androidx.annotation.StringRes import androidx.annotation.StringRes
import androidx.lifecycle.lifecycleScope
import com.keylesspalace.tusky.components.instanceinfo.InstanceInfoRepository
import com.keylesspalace.tusky.databinding.ActivityAboutBinding import com.keylesspalace.tusky.databinding.ActivityAboutBinding
import com.keylesspalace.tusky.di.Injectable import com.keylesspalace.tusky.di.Injectable
import com.keylesspalace.tusky.util.NoUnderlineURLSpan import com.keylesspalace.tusky.util.NoUnderlineURLSpan
import com.keylesspalace.tusky.util.hide import com.keylesspalace.tusky.util.hide
import com.keylesspalace.tusky.util.show
import kotlinx.coroutines.launch
import javax.inject.Inject
class AboutActivity : BottomSheetActivity(), Injectable { class AboutActivity : BottomSheetActivity(), Injectable {
@Inject
lateinit var instanceInfoRepository: InstanceInfoRepository
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@ -32,6 +44,28 @@ class AboutActivity : BottomSheetActivity(), Injectable {
binding.versionTextView.text = getString(R.string.about_app_version, getString(R.string.app_name), BuildConfig.VERSION_NAME) binding.versionTextView.text = getString(R.string.about_app_version, getString(R.string.app_name), BuildConfig.VERSION_NAME)
binding.deviceInfo.text = getString(
R.string.about_device_info,
Build.MANUFACTURER,
Build.MODEL,
Build.VERSION.RELEASE,
Build.VERSION.SDK_INT
)
lifecycleScope.launch {
accountManager.activeAccount?.let { account ->
val instanceInfo = instanceInfoRepository.getInstanceInfo()
binding.accountInfo.text = getString(
R.string.about_account_info,
account.username,
account.domain,
instanceInfo.version
)
binding.accountInfoTitle.show()
binding.accountInfo.show()
}
}
if (BuildConfig.CUSTOM_INSTANCE.isBlank()) { if (BuildConfig.CUSTOM_INSTANCE.isBlank()) {
binding.aboutPoweredByTusky.hide() binding.aboutPoweredByTusky.hide()
} }
@ -47,6 +81,16 @@ class AboutActivity : BottomSheetActivity(), Injectable {
binding.aboutLicensesButton.setOnClickListener { binding.aboutLicensesButton.setOnClickListener {
startActivityWithSlideInAnimation(Intent(this, LicenseActivity::class.java)) startActivityWithSlideInAnimation(Intent(this, LicenseActivity::class.java))
} }
binding.copyDeviceInfo.setOnClickListener {
val text = "${binding.versionTextView.text}\n\nDevice:\n\n${binding.deviceInfo.text}\n\nAccount:\n\n${binding.accountInfo.text}"
val clipboard = getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
val clip = ClipData.newPlainText("Tusky version information", text)
clipboard.setPrimaryClip(clip)
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.S_V2) {
Toast.makeText(this, getString(R.string.about_copied), Toast.LENGTH_SHORT).show()
}
}
} }
} }

View File

@ -28,5 +28,6 @@ data class InstanceInfo(
val maxMediaAttachments: Int, val maxMediaAttachments: Int,
val maxFields: Int, val maxFields: Int,
val maxFieldNameLength: Int?, val maxFieldNameLength: Int?,
val maxFieldValueLength: Int? val maxFieldValueLength: Int?,
val version: String?
) )

View File

@ -99,7 +99,8 @@ class InstanceInfoRepository @Inject constructor(
maxMediaAttachments = instanceInfo?.maxMediaAttachments ?: DEFAULT_MAX_MEDIA_ATTACHMENTS, maxMediaAttachments = instanceInfo?.maxMediaAttachments ?: DEFAULT_MAX_MEDIA_ATTACHMENTS,
maxFields = instanceInfo?.maxFields ?: DEFAULT_MAX_ACCOUNT_FIELDS, maxFields = instanceInfo?.maxFields ?: DEFAULT_MAX_ACCOUNT_FIELDS,
maxFieldNameLength = instanceInfo?.maxFieldNameLength, maxFieldNameLength = instanceInfo?.maxFieldNameLength,
maxFieldValueLength = instanceInfo?.maxFieldValueLength maxFieldValueLength = instanceInfo?.maxFieldValueLength,
version = instanceInfo?.version
) )
} }
} }

View File

@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="#000000"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M16,1L4,1c-1.1,0 -2,0.9 -2,2v14h2L4,3h12L16,1zM19,5L8,5c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h11c1.1,0 2,-0.9 2,-2L21,7c0,-1.1 -0.9,-2 -2,-2zM19,21L8,21L8,7h11v14z"/>
</vector>

View File

@ -21,104 +21,190 @@
android:layout_gravity="center" android:layout_gravity="center"
android:textDirection="anyRtl"> android:textDirection="anyRtl">
<LinearLayout <androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="match_parent"
android:focusableInTouchMode="true" android:focusableInTouchMode="true"
android:gravity="center" android:gravity="center"
android:orientation="vertical" android:orientation="vertical"
android:paddingTop="16dp" android:paddingTop="16dp"
android:paddingBottom="16dp"> android:paddingBottom="16dp">
<ImageView
android:id="@+id/logo"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@mipmap/ic_launcher"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:ignore="ContentDescription" />
<TextView <TextView
android:id="@+id/versionTextView" android:id="@+id/versionTextView"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="center_horizontal" android:layout_marginTop="12dp"
android:drawableStart="@mipmap/ic_launcher" android:textAppearance="@style/TextAppearance.AppCompat.Large"
android:drawablePadding="16dp" android:textIsSelectable="true"
android:gravity="center_vertical" android:textStyle="normal"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/logo"
tools:text="Tusky Test" />
<TextView
android:id="@+id/deviceInfoTitle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:layout_marginStart="@dimen/text_content_margin"
android:layout_marginEnd="@dimen/text_content_margin"
android:lineSpacingMultiplier="1.1"
android:text="@string/about_device_info_title"
android:textColor="?android:attr/textColorPrimary" android:textColor="?android:attr/textColorPrimary"
android:textIsSelectable="true" android:textIsSelectable="true"
android:textSize="24sp" android:textAppearance="@style/TextAppearance.AppCompat.Medium"
android:textStyle="bold" /> app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/versionTextView"
tools:text="Your device" />
<TextView
android:id="@+id/deviceInfo"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:lineSpacingMultiplier="1.1"
android:textColor="?android:attr/textColorPrimary"
android:textIsSelectable="true"
app:layout_constraintEnd_toEndOf="@+id/deviceInfoTitle"
app:layout_constraintStart_toStartOf="@+id/deviceInfoTitle"
app:layout_constraintTop_toBottomOf="@id/deviceInfoTitle" />
<TextView
android:id="@+id/accountInfoTitle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:lineSpacingMultiplier="1.1"
android:text="@string/about_account_info_title"
android:textAppearance="@style/TextAppearance.AppCompat.Medium"
android:textColor="?android:attr/textColorPrimary"
android:textIsSelectable="true"
android:visibility="gone"
app:layout_constraintEnd_toStartOf="@+id/copyDeviceInfo"
app:layout_constraintStart_toStartOf="@+id/deviceInfo"
app:layout_constraintTop_toBottomOf="@id/deviceInfo"
tools:visibility="visible" />
<TextView
android:id="@+id/accountInfo"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:lineSpacingMultiplier="1.1"
android:textColor="?android:attr/textColorPrimary"
android:textIsSelectable="true"
android:visibility="gone"
app:layout_constraintEnd_toEndOf="@+id/accountInfoTitle"
app:layout_constraintStart_toStartOf="@+id/accountInfoTitle"
app:layout_constraintTop_toBottomOf="@id/accountInfoTitle"
tools:text="\@Tusky@mastodon.social\nVersion: xxx"
tools:visibility="visible" />
<ImageButton
android:id="@+id/copyDeviceInfo"
style="@style/TuskyImageButton"
android:layout_width="48dp"
android:layout_height="48dp"
android:contentDescription="@string/about_copy"
android:layout_marginEnd="@dimen/text_content_margin"
app:layout_constraintBottom_toBottomOf="@+id/accountInfo"
app:layout_constraintEnd_toEndOf="parent"
app:srcCompat="@drawable/ic_content_copy_24" />
<TextView <TextView
android:id="@+id/aboutPoweredByTusky" android:id="@+id/aboutPoweredByTusky"
android:layout_width="wrap_content" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="center_horizontal" android:layout_marginTop="16dp"
android:drawablePadding="16dp"
android:gravity="center_vertical"
android:textSize="18sp"
android:text="@string/about_powered_by_tusky" android:text="@string/about_powered_by_tusky"
android:textStyle="bold" /> android:textAppearance="@style/TextAppearance.AppCompat.Subhead"
app:layout_constraintEnd_toEndOf="@+id/copyDeviceInfo"
app:layout_constraintStart_toStartOf="@+id/deviceInfo"
app:layout_constraintTop_toBottomOf="@+id/accountInfo" />
<com.keylesspalace.tusky.view.ClickableSpanTextView <com.keylesspalace.tusky.view.ClickableSpanTextView
android:id="@+id/aboutLicenseInfoTextView" android:id="@+id/aboutLicenseInfoTextView"
android:layout_width="match_parent" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="24dp" android:layout_marginTop="24dp"
android:hyphenationFrequency="full" android:hyphenationFrequency="full"
android:lineSpacingMultiplier="1.2" android:lineSpacingMultiplier="1.2"
android:paddingStart="@dimen/text_content_margin"
android:paddingEnd="@dimen/text_content_margin"
android:textAlignment="center"
android:textIsSelectable="true" android:textIsSelectable="true"
android:textSize="16sp" app:layout_constraintEnd_toEndOf="@+id/copyDeviceInfo"
app:layout_constraintStart_toStartOf="@+id/accountInfo"
app:layout_constraintTop_toBottomOf="@id/aboutPoweredByTusky"
tools:text="@string/about_tusky_license" /> tools:text="@string/about_tusky_license" />
<com.keylesspalace.tusky.view.ClickableSpanTextView <com.keylesspalace.tusky.view.ClickableSpanTextView
android:id="@+id/aboutWebsiteInfoTextView" android:id="@+id/aboutWebsiteInfoTextView"
android:layout_width="wrap_content" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="24dp" android:layout_marginTop="24dp"
android:lineSpacingMultiplier="1.2" android:lineSpacingMultiplier="1.2"
android:paddingStart="@dimen/text_content_margin"
android:paddingEnd="@dimen/text_content_margin"
android:textAlignment="center"
android:textIsSelectable="true" android:textIsSelectable="true"
android:textSize="16sp" app:layout_constraintEnd_toEndOf="@+id/aboutLicenseInfoTextView"
app:layout_constraintStart_toStartOf="@+id/aboutLicenseInfoTextView"
app:layout_constraintTop_toBottomOf="@id/aboutLicenseInfoTextView"
tools:text="@string/about_project_site" /> tools:text="@string/about_project_site" />
<com.keylesspalace.tusky.view.ClickableSpanTextView <com.keylesspalace.tusky.view.ClickableSpanTextView
android:id="@+id/aboutBugsFeaturesInfoTextView" android:id="@+id/aboutBugsFeaturesInfoTextView"
android:layout_width="wrap_content" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="24dp" android:layout_marginTop="24dp"
android:lineSpacingMultiplier="1.2" android:lineSpacingMultiplier="1.2"
android:paddingStart="@dimen/text_content_margin"
android:paddingEnd="@dimen/text_content_margin"
android:text="@string/about_bug_feature_request_site" android:text="@string/about_bug_feature_request_site"
android:textAlignment="center"
android:textIsSelectable="true" android:textIsSelectable="true"
android:textSize="16sp" /> app:layout_constraintEnd_toEndOf="@+id/aboutWebsiteInfoTextView"
app:layout_constraintStart_toStartOf="@+id/aboutWebsiteInfoTextView"
app:layout_constraintTop_toBottomOf="@id/aboutWebsiteInfoTextView" />
<Button <Button
android:id="@+id/tuskyProfileButton" android:id="@+id/tuskyProfileButton"
style="@style/TuskyButton" style="@style/TuskyButton"
android:layout_width="wrap_content" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="24dp" android:layout_marginTop="24dp"
android:lineSpacingMultiplier="1.2" android:lineSpacingMultiplier="1.2"
android:maxWidth="320dp"
android:text="@string/about_tusky_account" android:text="@string/about_tusky_account"
android:textAllCaps="false" android:textAllCaps="false"
android:textSize="16sp" /> android:textSize="16sp"
android:layout_marginEnd="@dimen/text_content_margin"
app:layout_constraintEnd_toStartOf="@+id/aboutLicensesButton"
app:layout_constraintStart_toStartOf="@+id/aboutBugsFeaturesInfoTextView"
app:layout_constraintTop_toBottomOf="@id/aboutBugsFeaturesInfoTextView" />
<Button <Button
android:id="@+id/aboutLicensesButton" android:id="@+id/aboutLicensesButton"
style="@style/TuskyButton.Outlined" style="@style/TuskyButton.Outlined"
android:layout_width="wrap_content" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:lineSpacingMultiplier="1.2" android:lineSpacingMultiplier="1.2"
android:maxWidth="320dp"
android:text="@string/title_licenses" android:text="@string/title_licenses"
android:textAlignment="center" android:textAlignment="center"
android:textAllCaps="false" android:textAllCaps="false"
android:textSize="16sp" /> android:textSize="16sp"
android:layout_marginEnd="@dimen/text_content_margin"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/tuskyProfileButton"
app:layout_constraintTop_toTopOf="@+id/tuskyProfileButton" />
</LinearLayout> </androidx.constraintlayout.widget.ConstraintLayout>
</androidx.core.widget.NestedScrollView> </androidx.core.widget.NestedScrollView>
</FrameLayout> </FrameLayout>

View File

@ -391,6 +391,10 @@
<string name="about_title_activity">About</string> <string name="about_title_activity">About</string>
<string name="about_tusky_version">Tusky %s</string> <string name="about_tusky_version">Tusky %s</string>
<string name="about_device_info_title">Your device</string>
<string name="about_device_info">%s %s\nAndroid version: %s\nSDK version: %d</string>
<string name="about_account_info_title">Your account</string>
<string name="about_account_info">\@%s\@%s\nVersion: %s</string>
<string name="about_powered_by_tusky">Powered by Tusky</string> <string name="about_powered_by_tusky">Powered by Tusky</string>
<string name="about_tusky_license">Tusky is free and open-source software. <string name="about_tusky_license">Tusky is free and open-source software.
It is licensed under the GNU General Public License Version 3. It is licensed under the GNU General Public License Version 3.
@ -401,14 +405,8 @@
to show we do not mean the software is gratis. Source: https://www.gnu.org/philosophy/free-sw.html to show we do not mean the software is gratis. Source: https://www.gnu.org/philosophy/free-sw.html
* the url can be changed to link to the localized version of the license. * the url can be changed to link to the localized version of the license.
--> -->
<string name="about_project_site"> <string name="about_project_site">Project website: https://tusky.app</string>
Project website:\n <string name="about_bug_feature_request_site">Bug reports &amp; feature requests:\nhttps://github.com/tuskyapp/Tusky/issues</string>
https://tusky.app
</string>
<string name="about_bug_feature_request_site">
Bug reports &amp; feature requests:\n
https://github.com/tuskyapp/Tusky/issues
</string>
<string name="about_tusky_account">Tusky\'s Profile</string> <string name="about_tusky_account">Tusky\'s Profile</string>
<string name="post_share_content">Share content of post</string> <string name="post_share_content">Share content of post</string>
@ -817,4 +815,6 @@
For example the local timeline of your instance [iconics gmd_group]. Or you can search them For example the local timeline of your instance [iconics gmd_group]. Or you can search them
by name [iconics gmd_search]; for example search for Tusky to find our Mastodon account.</string> by name [iconics gmd_search]; for example search for Tusky to find our Mastodon account.</string>
<string name="load_newest_notifications">Load newest notifications</string> <string name="load_newest_notifications">Load newest notifications</string>
<string name="about_copy">Copy version and device information</string>
<string name="about_copied">Copied version and device information</string>
</resources> </resources>