From 09d64a57e601465702450404113d65ec6a89f72e Mon Sep 17 00:00:00 2001 From: WhiredPlanck Date: Fri, 27 Dec 2024 21:40:18 +0800 Subject: [PATCH] feat: new deploy user experience A notification will be created on deploy failure to guide user to view error logs. --- .../com/osfans/trime/daemon/RimeDaemon.kt | 66 +++++++++++++++++++ .../osfans/trime/ui/components/log/LogView.kt | 36 ++++------ .../com/osfans/trime/ui/main/LogActivity.kt | 17 +++++ .../res/drawable/ic_baseline_warning_24.xml | 5 ++ app/src/main/res/layout/activity_log.xml | 9 ++- app/src/main/res/values-zh-rCN/strings.xml | 2 + app/src/main/res/values-zh-rTW/strings.xml | 2 + app/src/main/res/values/strings.xml | 2 + 8 files changed, 115 insertions(+), 24 deletions(-) create mode 100644 app/src/main/res/drawable/ic_baseline_warning_24.xml diff --git a/app/src/main/java/com/osfans/trime/daemon/RimeDaemon.kt b/app/src/main/java/com/osfans/trime/daemon/RimeDaemon.kt index 27cd32973c..f31cf1394a 100644 --- a/app/src/main/java/com/osfans/trime/daemon/RimeDaemon.kt +++ b/app/src/main/java/com/osfans/trime/daemon/RimeDaemon.kt @@ -6,19 +6,29 @@ package com.osfans.trime.daemon import android.app.NotificationChannel import android.app.NotificationManager +import android.app.PendingIntent +import android.content.Intent import android.os.Build import androidx.core.app.NotificationCompat +import androidx.core.content.ContextCompat import com.osfans.trime.R import com.osfans.trime.TrimeApplication import com.osfans.trime.core.Rime import com.osfans.trime.core.RimeApi import com.osfans.trime.core.RimeLifecycle +import com.osfans.trime.core.RimeMessage import com.osfans.trime.core.lifecycleScope import com.osfans.trime.core.whenReady +import com.osfans.trime.ui.main.LogActivity import com.osfans.trime.util.appContext +import com.osfans.trime.util.toast import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.withContext import splitties.systemservices.notificationManager import java.util.concurrent.locks.ReentrantLock import kotlin.concurrent.withLock @@ -120,6 +130,7 @@ object RimeDaemon { } private const val CHANNEL_ID = "rime-daemon" + private const val MESSAGE_ID = 2331 private var restartId = 0 /** @@ -140,6 +151,9 @@ object RimeDaemon { .let { notificationManager.notify(id, it) } realRime.finalize() realRime.startup(fullCheck) + realRime.messageFlow + .onEach(::handleRimeMessage) + .launchIn(TrimeApplication.getInstance().coroutineScope) TrimeApplication.getInstance().coroutineScope.launch { // cancel notification on ready realRime.lifecycle.whenReady { @@ -147,4 +161,56 @@ object RimeDaemon { } } } + + private suspend fun handleRimeMessage(it: RimeMessage<*>) { + if (it is RimeMessage.DeployMessage) { + when (it.data) { + RimeMessage.DeployMessage.State.Start -> { + withContext(Dispatchers.IO) { + Runtime.getRuntime().exec(arrayOf("logcat", "-c")) + } + } + RimeMessage.DeployMessage.State.Success -> { + ContextCompat.getMainExecutor(appContext).execute { + appContext.toast(R.string.deploy_finish) + } + } + RimeMessage.DeployMessage.State.Failure -> { + val intent = + Intent(appContext, LogActivity::class.java).apply { + flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK + val log = + withContext(Dispatchers.IO) { + Runtime + .getRuntime() + .exec( + arrayOf("logcat", "-d", "-v", "brief", "-s", "rime.trime:W"), + ).inputStream + .bufferedReader() + .readText() + } + putExtra(LogActivity.FROM_DEPLOY, true) + putExtra(LogActivity.DEPLOY_FAILURE_TRACE, log) + } + NotificationCompat + .Builder(appContext, CHANNEL_ID) + .setSmallIcon(R.drawable.ic_baseline_warning_24) + .setContentTitle(appContext.getString(R.string.rime_daemon)) + .setContentText(appContext.getString(R.string.view_deploy_failure_log)) + .setContentIntent( + PendingIntent.getActivity( + appContext, + 0, + intent, + PendingIntent.FLAG_UPDATE_CURRENT, + ), + ).setOngoing(false) + .setAutoCancel(true) + .setPriority(NotificationCompat.PRIORITY_DEFAULT) + .build() + .let { notificationManager.notify(MESSAGE_ID, it) } + } + } + } + } } diff --git a/app/src/main/java/com/osfans/trime/ui/components/log/LogView.kt b/app/src/main/java/com/osfans/trime/ui/components/log/LogView.kt index fdd2b49c0b..21e76dc0a7 100644 --- a/app/src/main/java/com/osfans/trime/ui/components/log/LogView.kt +++ b/app/src/main/java/com/osfans/trime/ui/components/log/LogView.kt @@ -6,20 +6,23 @@ package com.osfans.trime.ui.components.log import android.content.Context import android.util.AttributeSet -import android.view.ViewGroup import android.widget.HorizontalScrollView import androidx.core.content.ContextCompat import androidx.core.text.buildSpannedString import androidx.core.text.color import androidx.lifecycle.findViewTreeLifecycleOwner import androidx.lifecycle.lifecycleScope -import androidx.recyclerview.widget.LinearLayoutManager -import androidx.recyclerview.widget.RecyclerView import com.osfans.trime.R import com.osfans.trime.util.Logcat import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import splitties.resources.styledColor +import splitties.views.dsl.core.add +import splitties.views.dsl.core.lParams +import splitties.views.dsl.core.matchParent +import splitties.views.dsl.core.wrapContent +import splitties.views.dsl.recyclerview.recyclerView +import splitties.views.recyclerview.verticalLayoutManager /** * A scroll view to look up the app log. @@ -39,21 +42,15 @@ class LogView private val logAdapter = LogAdapter() private val recyclerView = - RecyclerView(context).apply { + recyclerView { adapter = logAdapter - layoutManager = - LinearLayoutManager(context).apply { - orientation = LinearLayoutManager.VERTICAL - } + layoutManager = verticalLayoutManager() } init { - addView( + add( recyclerView, - LayoutParams( - ViewGroup.LayoutParams.WRAP_CONTENT, - ViewGroup.LayoutParams.MATCH_PARENT, - ), + lParams(wrapContent, matchParent), ) } @@ -62,12 +59,6 @@ class LogView super.onDetachedFromWindow() } - fun fromCustomLogLines(lines: List) { - lines.onEach { - dyeAndAppendString(it) - } - } - fun append(content: String) { logAdapter.append( buildSpannedString { @@ -80,12 +71,11 @@ class LogView this.logcat = logcat logcat.initLogFlow() logcat.logFlow - .onEach { - dyeAndAppendString(it) - }.launchIn(findViewTreeLifecycleOwner()!!.lifecycleScope) + .onEach(::buildColoredString) + .launchIn(findViewTreeLifecycleOwner()!!.lifecycleScope) } - private fun dyeAndAppendString(str: String) { + private fun buildColoredString(str: String) { val color = ContextCompat.getColor( context, diff --git a/app/src/main/java/com/osfans/trime/ui/main/LogActivity.kt b/app/src/main/java/com/osfans/trime/ui/main/LogActivity.kt index 75370920a2..ec2cecfd76 100644 --- a/app/src/main/java/com/osfans/trime/ui/main/LogActivity.kt +++ b/app/src/main/java/com/osfans/trime/ui/main/LogActivity.kt @@ -4,6 +4,7 @@ package com.osfans.trime.ui.main +import android.content.ClipData import android.os.Bundle import android.view.View import android.view.ViewGroup @@ -28,6 +29,7 @@ import com.osfans.trime.util.toast import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.NonCancellable import kotlinx.coroutines.launch +import splitties.systemservices.clipboardManager /** * The activity to show [LogView]. @@ -41,7 +43,9 @@ class LogActivity : AppCompatActivity() { companion object { const val FROM_CRASH = "from_crash" + const val FROM_DEPLOY = "from_deploy" const val CRASH_STACK_TRACE = "crash_stack_trace" + const val DEPLOY_FAILURE_TRACE = "deploy_failure_trace" } private fun registerLauncher() { @@ -88,6 +92,7 @@ class LogActivity : AppCompatActivity() { if (intent.hasExtra(FROM_CRASH)) { supportActionBar!!.setTitle(R.string.crash_logs) clearButton.visibility = View.GONE + copyButton.visibility = View.GONE AlertDialog .Builder(this@LogActivity) .setTitle(R.string.app_crash) @@ -97,11 +102,16 @@ class LogActivity : AppCompatActivity() { logView.append("--------- Crash stacktrace") logView.append(intent.getStringExtra(CRASH_STACK_TRACE) ?: "") logView.setLogcat(Logcat(TrimeApplication.getLastPid())) + } else if (intent.hasExtra(FROM_DEPLOY)) { + supportActionBar!!.setTitle(R.string.deploy_failure) + clearButton.visibility = View.GONE + logView.append(intent.getStringExtra(DEPLOY_FAILURE_TRACE) ?: "") } else { supportActionBar!!.apply { setDisplayHomeAsUpEnabled(true) setTitle(R.string.real_time_logs) } + copyButton.visibility = View.GONE logView.setLogcat(Logcat()) } clearButton.setOnClickListener { @@ -110,6 +120,13 @@ class LogActivity : AppCompatActivity() { exportButton.setOnClickListener { launcher.launch("$packageName-${iso8601UTCDateTime()}.txt") } + copyButton.setOnClickListener { + val data = ClipData.newPlainText("log", logView.currentLog) + clipboardManager.setPrimaryClip(data) + if (clipboardManager.hasPrimaryClip()) { + toast(R.string.copy_done) + } + } jumpToBottomButton.setOnClickListener { logView.scrollToBottom() } diff --git a/app/src/main/res/drawable/ic_baseline_warning_24.xml b/app/src/main/res/drawable/ic_baseline_warning_24.xml new file mode 100644 index 0000000000..10190dffc5 --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_warning_24.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/layout/activity_log.xml b/app/src/main/res/layout/activity_log.xml index 9e127c39e0..44b67b2801 100644 --- a/app/src/main/res/layout/activity_log.xml +++ b/app/src/main/res/layout/activity_log.xml @@ -54,6 +54,13 @@ SPDX-License-Identifier: GPL-3.0-or-later android:layout_height="wrap_content" android:text="@string/export" /> +