Add UI for prompting about crashes.

This commit is contained in:
Greyson Parrelli
2023-09-06 15:05:23 -04:00
committed by Alex Hart
parent 0a6c3baf24
commit f959543c19
23 changed files with 1089 additions and 182 deletions

View File

@@ -13,11 +13,12 @@ import android.view.ViewGroup
import android.widget.Toast
import androidx.core.os.bundleOf
import androidx.fragment.app.FragmentManager
import androidx.lifecycle.ViewModelProvider
import androidx.fragment.app.viewModels
import org.signal.core.util.ResourceUtil
import org.signal.core.util.concurrent.LifecycleDisposable
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.databinding.PromptLogsBottomSheetBinding
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.util.BottomSheetUtil
import org.thoughtcrime.securesms.util.CommunicationActions
@@ -27,14 +28,21 @@ import org.thoughtcrime.securesms.util.SupportEmailUtil
class DebugLogsPromptDialogFragment : FixedRoundedCornerBottomSheetDialogFragment() {
companion object {
private const val KEY_PURPOSE = "purpose"
@JvmStatic
fun show(context: Context, fragmentManager: FragmentManager) {
fun show(context: Context, fragmentManager: FragmentManager, purpose: Purpose) {
if (NetworkUtil.isConnected(context) && fragmentManager.findFragmentByTag(BottomSheetUtil.STANDARD_BOTTOM_SHEET_FRAGMENT_TAG) == null) {
DebugLogsPromptDialogFragment().apply {
arguments = bundleOf()
arguments = bundleOf(
KEY_PURPOSE to purpose.serialized
)
}.show(fragmentManager, BottomSheetUtil.STANDARD_BOTTOM_SHEET_FRAGMENT_TAG)
SignalStore.uiHints().lastNotificationLogsPrompt = System.currentTimeMillis()
when (purpose) {
Purpose.NOTIFICATIONS -> SignalStore.uiHints().lastNotificationLogsPrompt = System.currentTimeMillis()
Purpose.CRASH -> SignalStore.uiHints().lastCrashPrompt = System.currentTimeMillis()
}
}
}
}
@@ -44,7 +52,12 @@ class DebugLogsPromptDialogFragment : FixedRoundedCornerBottomSheetDialogFragmen
private val binding by ViewBinderDelegate(PromptLogsBottomSheetBinding::bind)
private lateinit var viewModel: PromptLogsViewModel
private val viewModel: PromptLogsViewModel by viewModels(
factoryProducer = {
val purpose = Purpose.deserialize(requireArguments().getInt(KEY_PURPOSE))
PromptLogsViewModel.Factory(ApplicationDependencies.getApplication(), purpose)
}
)
private val disposables: LifecycleDisposable = LifecycleDisposable()
@@ -55,11 +68,21 @@ class DebugLogsPromptDialogFragment : FixedRoundedCornerBottomSheetDialogFragmen
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
disposables.bindTo(viewLifecycleOwner)
viewModel = ViewModelProvider(this).get(PromptLogsViewModel::class.java)
val purpose = Purpose.deserialize(requireArguments().getInt(KEY_PURPOSE))
when (purpose) {
Purpose.NOTIFICATIONS -> {
binding.title.setText(R.string.PromptLogsSlowNotificationsDialog__title)
}
Purpose.CRASH -> {
binding.title.setText(R.string.PromptLogsSlowNotificationsDialog__title_crash)
}
}
binding.submit.setOnClickListener {
val progressDialog = SignalProgressDialog.show(requireContext())
disposables += viewModel.submitLogs().subscribe({ result ->
submitLogs(result)
submitLogs(result, purpose)
progressDialog.dismiss()
dismiss()
}, { _ ->
@@ -68,30 +91,40 @@ class DebugLogsPromptDialogFragment : FixedRoundedCornerBottomSheetDialogFragmen
dismiss()
})
}
binding.decline.setOnClickListener {
SignalStore.uiHints().markDeclinedShareNotificationLogs()
if (purpose == Purpose.NOTIFICATIONS) {
SignalStore.uiHints().markDeclinedShareNotificationLogs()
}
dismiss()
}
}
private fun submitLogs(debugLog: String) {
private fun submitLogs(debugLog: String, purpose: Purpose) {
CommunicationActions.openEmail(
requireContext(),
SupportEmailUtil.getSupportEmailAddress(requireContext()),
getString(R.string.DebugLogsPromptDialogFragment__signal_android_support_request),
getEmailBody(debugLog)
getEmailBody(debugLog, purpose)
)
}
private fun getEmailBody(debugLog: String?): String {
private fun getEmailBody(debugLog: String?, purpose: Purpose): String {
val suffix = StringBuilder()
if (debugLog != null) {
suffix.append("\n")
suffix.append(getString(R.string.HelpFragment__debug_log))
suffix.append(" ")
suffix.append(debugLog)
}
val category = ResourceUtil.getEnglishResources(requireContext()).getString(R.string.DebugLogsPromptDialogFragment__slow_notifications_category)
val category = when (purpose) {
Purpose.NOTIFICATIONS -> ResourceUtil.getEnglishResources(requireContext()).getString(R.string.DebugLogsPromptDialogFragment__slow_notifications_category)
Purpose.CRASH -> ResourceUtil.getEnglishResources(requireContext()).getString(R.string.DebugLogsPromptDialogFragment__crash_category)
}
return SupportEmailUtil.generateSupportEmailBody(
requireContext(),
R.string.DebugLogsPromptDialogFragment__signal_android_support_request,
@@ -100,4 +133,21 @@ class DebugLogsPromptDialogFragment : FixedRoundedCornerBottomSheetDialogFragmen
suffix.toString()
)
}
enum class Purpose(val serialized: Int) {
NOTIFICATIONS(1), CRASH(2);
companion object {
fun deserialize(serialized: Int): Purpose {
for (value in values()) {
if (value.serialized == serialized) {
return value
}
}
throw IllegalArgumentException("Invalid value: $serialized")
}
}
}
}

View File

@@ -5,17 +5,37 @@
package org.thoughtcrime.securesms.components
import android.app.Application
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import io.reactivex.rxjava3.core.Single
import io.reactivex.rxjava3.disposables.CompositeDisposable
import io.reactivex.rxjava3.kotlin.plusAssign
import io.reactivex.rxjava3.schedulers.Schedulers
import io.reactivex.rxjava3.subjects.SingleSubject
import org.thoughtcrime.securesms.crash.CrashConfig
import org.thoughtcrime.securesms.database.LogDatabase
import org.thoughtcrime.securesms.logsubmit.SubmitDebugLogRepository
class PromptLogsViewModel : ViewModel() {
class PromptLogsViewModel(private val context: Application, purpose: DebugLogsPromptDialogFragment.Purpose) : AndroidViewModel(context) {
private val submitDebugLogRepository = SubmitDebugLogRepository()
private val disposables = CompositeDisposable()
init {
if (purpose == DebugLogsPromptDialogFragment.Purpose.CRASH) {
disposables += Single
.fromCallable {
LogDatabase.getInstance(context).crashes.markAsPrompted(CrashConfig.patterns, System.currentTimeMillis())
}
.subscribeOn(Schedulers.io())
.subscribe()
}
}
fun submitLogs(): Single<String> {
val singleSubject = SingleSubject.create<String?>()
submitDebugLogRepository.buildAndSubmitLog { result ->
@@ -28,4 +48,14 @@ class PromptLogsViewModel : ViewModel() {
return singleSubject.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread())
}
override fun onCleared() {
disposables.clear()
}
class Factory(private val context: Application, private val purpose: DebugLogsPromptDialogFragment.Purpose) : ViewModelProvider.NewInstanceFactory() {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return modelClass.cast(PromptLogsViewModel(context, purpose))!!
}
}
}

View File

@@ -768,7 +768,7 @@ class InternalSettingsFragment : DSLSettingsFragment(R.string.preferences__inter
private fun clearKeepLongerLogs() {
SimpleTask.run({
LogDatabase.getInstance(requireActivity().application).clearKeepLonger()
LogDatabase.getInstance(requireActivity().application).logs.clearKeepLonger()
}) {
Toast.makeText(requireContext(), "Cleared keep longer logs", Toast.LENGTH_SHORT).show()
}