diff --git a/jami-android/app/src/main/java/cx/ring/client/LogsActivity.kt b/jami-android/app/src/main/java/cx/ring/client/LogsActivity.kt index 6b2a650e61e2a71e63f16ed119dcd0baa2c2d188..1c9cbf4717d793367954bbf263b0dd3ce32a5273 100644 --- a/jami-android/app/src/main/java/cx/ring/client/LogsActivity.kt +++ b/jami-android/app/src/main/java/cx/ring/client/LogsActivity.kt @@ -16,9 +16,11 @@ */ package cx.ring.client +import android.animation.ValueAnimator import android.app.ActivityManager import android.app.ApplicationExitInfo import android.content.Intent +import android.graphics.Color import android.net.Uri import android.os.Build import android.os.Bundle @@ -31,11 +33,12 @@ import android.view.ViewGroup import android.widget.TextView import androidx.activity.result.ActivityResultLauncher import androidx.activity.result.contract.ActivityResultContracts +import androidx.annotation.ColorInt import androidx.appcompat.app.AppCompatActivity import androidx.core.content.ContextCompat import androidx.core.content.getSystemService -import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView +import androidx.recyclerview.widget.SimpleItemAnimator import com.google.android.material.bottomsheet.BottomSheetDialogFragment import com.google.android.material.snackbar.Snackbar import cx.ring.R @@ -91,8 +94,16 @@ class LogsActivity : AppCompatActivity() { } binding.fab.setOnClickListener { if (disposable == null) startLogging() else stopLogging() } - binding.logRecyclerView.adapter = LogAdapter() - binding.logRecyclerView.layoutManager = LinearLayoutManager(this) + val highlightColor = getColor(R.color.colorSecondaryTranslucent) + val bgColor = getColor(R.color.transparent) + + binding.logRecyclerView.apply { + setHasFixedSize(true) + itemAnimator = FadeInItemAnimator(highlightColor, bgColor) + adapter = LogAdapter(highlightColor, bgColor).apply { + stateRestorationPolicy = RecyclerView.Adapter.StateRestorationPolicy.PREVENT_WHEN_EMPTY + } + } // Check for previous crash reasons, if any. if (savedInstanceState == null) @@ -260,7 +271,7 @@ class LogsActivity : AppCompatActivity() { .subscribe({ messages: List<String> -> val adapter = binding.logRecyclerView.adapter as LogAdapter adapter.addLogs(messages.map { LogMessage(it) }) - binding.logRecyclerView.smoothScrollToPosition(adapter.itemCount - 1) + binding.logRecyclerView.scrollToPosition(adapter.itemCount - 1) }) { e -> Log.w(TAG, "Error in logger", e) } .apply { disposable = this }) setButtonState(true) @@ -291,42 +302,102 @@ class LogsActivity : AppCompatActivity() { super.onDestroy() } - companion object { - private val TAG = LogsActivity::class.simpleName!! - } -} + data class LogMessage(val message: String) -data class LogMessage(val message: String) + class FadeInItemAnimator(@ColorInt val highlightColor: Int, @ColorInt val bgColor: Int): SimpleItemAnimator() { + init { + supportsChangeAnimations = false + addDuration = 1000 + } -class LogAdapter : - RecyclerView.Adapter<LogAdapter.LogViewHolder>() { + override fun animateAdd(holder: RecyclerView.ViewHolder): Boolean { + val logHolder = holder as LogAdapter.LogViewHolder + val pos = holder.adapterPosition + Log.w(TAG, "animateAdd ${pos}") + val view = holder.itemView + view.setBackgroundColor(highlightColor) + logHolder.animation.setDuration(addDuration) + logHolder.animation.addUpdateListener { animation -> + view.setBackgroundColor(animation.getAnimatedValue() as Int) + } + logHolder.animation.start() + return false + } - private val logList = mutableListOf<LogMessage>() + override fun animateRemove(holder: RecyclerView.ViewHolder): Boolean { + //dispatchRemoveFinished(holder) + return false; + } - fun getLogs(): String = logList.joinToString(separator = "\n") { it.message } + override fun animateMove(holder: RecyclerView.ViewHolder, fromX: Int, fromY: Int, toX: Int, toY: Int): Boolean { + // Implement if you need move animations + //dispatchMoveFinished(holder) + return false; + } - class LogViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { - val messageTextView: TextView = itemView.findViewById(R.id.log_item_text) - } + override fun animateChange(oldHolder: RecyclerView.ViewHolder, newHolder: RecyclerView.ViewHolder, + fromLeft: Int, fromTop: Int, toLeft: Int, toTop: Int): Boolean { + // Implement if you need change animations + return false; + } - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): LogViewHolder = - LogViewHolder( - LayoutInflater.from(parent.context).inflate(R.layout.item_log, parent, false) - ) + override fun runPendingAnimations() { + // No-op for this example + } - override fun onBindViewHolder(holder: LogViewHolder, position: Int) { - holder.messageTextView.text = logList[position].message - } + override fun endAnimation(item: RecyclerView.ViewHolder) { + Log.w(TAG, "endAnimation ${item.adapterPosition}") + val logHolder = item as LogAdapter.LogViewHolder + logHolder.animation.cancel() + item.itemView.setBackgroundColor(bgColor) + dispatchAnimationFinished(item) + } - override fun getItemCount(): Int = logList.size + override fun endAnimations() { + // No-op for this example + } - fun addLogs(logs: List<LogMessage>) { - logList.addAll(logs) - notifyItemRangeInserted(logList.size - logs.size, logs.size) + override fun isRunning(): Boolean { + return false; // Return true if animations are running + } } - fun clearLogs() { - logList.clear() - notifyDataSetChanged() + class LogAdapter(@ColorInt val highlightColor: Int, @ColorInt val bgColor: Int) : + RecyclerView.Adapter<LogAdapter.LogViewHolder>() { + + private val logList = mutableListOf<LogMessage>() + + fun getLogs(): String = logList.joinToString(separator = "\n") { it.message } + + class LogViewHolder(itemView: View, @ColorInt highlightColor: Int, @ColorInt bgColor: Int) : RecyclerView.ViewHolder(itemView) { + val messageTextView: TextView = itemView.findViewById(R.id.log_item_text) + val animation: ValueAnimator = ValueAnimator.ofArgb(highlightColor, bgColor) + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): LogViewHolder = + LogViewHolder( + LayoutInflater.from(parent.context).inflate(R.layout.item_log, parent, false), + highlightColor, + bgColor + ) + + override fun onBindViewHolder(holder: LogViewHolder, position: Int) { + holder.messageTextView.text = logList[position].message + } + + override fun getItemCount(): Int = logList.size + + fun addLogs(logs: List<LogMessage>) { + logList.addAll(logs) + notifyItemRangeInserted(logList.size - logs.size, logs.size) + } + + fun clearLogs() { + logList.clear() + notifyDataSetChanged() + } } -} \ No newline at end of file + companion object { + private val TAG = LogsActivity::class.simpleName!! + } +} diff --git a/jami-android/app/src/main/java/cx/ring/client/PushNotificationLogsActivity.kt b/jami-android/app/src/main/java/cx/ring/client/PushNotificationLogsActivity.kt index acbfe5077e36a9c4755ce5d596c59609a280cabe..2be4d0ed1a87d8a73a2f792ea619ee5fa164b768 100644 --- a/jami-android/app/src/main/java/cx/ring/client/PushNotificationLogsActivity.kt +++ b/jami-android/app/src/main/java/cx/ring/client/PushNotificationLogsActivity.kt @@ -30,6 +30,9 @@ import androidx.core.content.ContextCompat import androidx.recyclerview.widget.LinearLayoutManager import com.google.android.material.snackbar.Snackbar import cx.ring.R +import cx.ring.client.LogsActivity.FadeInItemAnimator +import cx.ring.client.LogsActivity.LogAdapter +import cx.ring.client.LogsActivity.LogMessage import cx.ring.databinding.ActivityPushNotificationLogsBinding import cx.ring.utils.AndroidFileUtils import cx.ring.utils.ContentUri @@ -52,8 +55,13 @@ class PushNotificationLogsActivity : AppCompatActivity() { private val compositeDisposable = CompositeDisposable() private var disposable: Disposable? = null private lateinit var logAdapter: LogAdapter - private lateinit var fileSaver: ActivityResultLauncher<String> private lateinit var logFile: File + private var fileSaver: ActivityResultLauncher<String> = registerForActivityResult(ActivityResultContracts + .CreateDocument("text/plain")) { result: Uri? -> + if (result != null) { + copyFileToUri(logFile, result) + } + } @Inject @Singleton @@ -65,7 +73,10 @@ class PushNotificationLogsActivity : AppCompatActivity() { setContentView(binding.root) setSupportActionBar(binding.toolbar) supportActionBar?.setDisplayHomeAsUpEnabled(true) - logAdapter = LogAdapter() + val highlightColor = getColor(R.color.colorSecondaryTranslucent) + val bgColor = getColor(R.color.transparent) + logAdapter = LogAdapter(highlightColor, bgColor) + binding.logRecyclerView.itemAnimator = FadeInItemAnimator(highlightColor, bgColor) binding.logRecyclerView.adapter = logAdapter binding.logRecyclerView.layoutManager = LinearLayoutManager(this) val pushSummaryTextView = findViewById<TextView>(R.id.pushSummaryTextView) @@ -81,15 +92,7 @@ class PushNotificationLogsActivity : AppCompatActivity() { if (disposable == null) startLogging() else stopLogging() } - fileSaver = registerForActivityResult(ActivityResultContracts - .CreateDocument("text/plain")) { result: Uri? -> - if (result != null) { - copyFileToUri(logFile, result) - } - } - if (mHardwareService.loggingStatus) startLogging() - } override fun onCreateOptionsMenu(menu: Menu): Boolean { diff --git a/jami-android/app/src/main/res/layout/activity_logs.xml b/jami-android/app/src/main/res/layout/activity_logs.xml index 319325a31571cc12a62d797b7edbd19a41cce84e..ec45f74f7b5d704d6a019068a8318a3d21dc619e 100644 --- a/jami-android/app/src/main/res/layout/activity_logs.xml +++ b/jami-android/app/src/main/res/layout/activity_logs.xml @@ -15,10 +15,8 @@ <com.google.android.material.appbar.MaterialToolbar android:id="@+id/toolbar" - style="@style/Widget.Material3.Toolbar.Surface" android:layout_width="match_parent" android:layout_height="?attr/actionBarSize" - android:background="@color/background" tools:menu="@menu/logs_menu" tools:title="Logs" /> @@ -34,14 +32,19 @@ android:id="@+id/log_recycler_view" android:layout_width="match_parent" android:layout_height="0dp" - android:layout_weight="1" /> + android:layout_weight="1" + android:orientation="vertical" + app:stackFromEnd="true" + app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" + tools:listitem="@layout/item_log"/> <com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton android:id="@+id/fab" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="bottom|center_horizontal" - android:layout_margin="@dimen/fab_margin" + android:layout_marginTop="8dp" + android:layout_marginBottom="@dimen/fab_margin" android:gravity="center" android:text="@string/pref_logs_start" app:icon="@drawable/baseline_article_24" /> diff --git a/jami-android/app/src/main/res/layout/item_log.xml b/jami-android/app/src/main/res/layout/item_log.xml index c08ce604ec318d4512fad26d4bb045df74804c53..7156c1b0c212218704886152a3165d69eae8572b 100644 --- a/jami-android/app/src/main/res/layout/item_log.xml +++ b/jami-android/app/src/main/res/layout/item_log.xml @@ -13,7 +13,6 @@ GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. --> - <TextView xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/log_item_text" @@ -22,6 +21,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. android:breakStrategy="simple" android:hyphenationFrequency="none" android:paddingHorizontal="@dimen/padding_medium" - android:paddingTop="@dimen/padding_small" + android:paddingVertical="@dimen/padding_xsmall" android:textIsSelectable="true" - tools:text="@tools:sample/lorem/random" /> \ No newline at end of file + tools:text="@tools:sample/first_names"/> \ No newline at end of file diff --git a/jami-android/app/src/main/res/values-night/colors.xml b/jami-android/app/src/main/res/values-night/colors.xml index 3a7545996c6f422289edfc3b46319047e1befb95..1a8ecd4784de9825fd798edcca990274b6c0044f 100644 --- a/jami-android/app/src/main/res/values-night/colors.xml +++ b/jami-android/app/src/main/res/values-night/colors.xml @@ -9,6 +9,9 @@ <color name="colorPrimary">@color/white</color> <color name="colorPrimaryTranslucent">#C0FFFFFF</color> <color name="colorOnPrimary">@color/color_primary_dark</color> + <color name="colorSecondaryTranslucent">@color/color_primary_dark_translucent</color> + + <color name="transparent">#00000000</color> <!-- Text color --> <color name="colorOnSurface">@color/abc_primary_text_material_dark</color> diff --git a/jami-android/app/src/main/res/values/colors.xml b/jami-android/app/src/main/res/values/colors.xml index 0347945b57ce68d41c6ac630c49084a7501cc3c7..8ee0f2918c7e385fbbb46e1635bd3b3e8b065107 100644 --- a/jami-android/app/src/main/res/values/colors.xml +++ b/jami-android/app/src/main/res/values/colors.xml @@ -9,6 +9,7 @@ <color name="colorPrimary">@color/color_primary_dark</color> <color name="colorPrimaryTranslucent">@color/color_primary_dark_translucent</color> <color name="colorOnPrimary">@color/white</color> + <color name="colorSecondaryTranslucent">@color/color_primary_light_translucent</color> <color name="colorSecondary">@color/color_primary_light</color> <color name="colorSecondaryContainer">@color/color_primary_light_container</color> @@ -32,7 +33,7 @@ <color name="darker_gray">#c4c4c4</color> <color name="lighter_gray">#d3d3d3</color> <color name="light">#eee</color> - <color name="transparent">#00000000</color> + <color name="transparent">#00FFFFFF</color> <color name="white">#FFF</color> <color name="black">#000000</color> diff --git a/jami-android/libjamiclient/src/main/kotlin/net/jami/services/HardwareService.kt b/jami-android/libjamiclient/src/main/kotlin/net/jami/services/HardwareService.kt index ba58ccaf084f28eb3ce82e09b32f5bc7717bd6d7..0c328be0ade7745c82c77fca86e2936b10ba422f 100644 --- a/jami-android/libjamiclient/src/main/kotlin/net/jami/services/HardwareService.kt +++ b/jami-android/libjamiclient/src/main/kotlin/net/jami/services/HardwareService.kt @@ -166,7 +166,6 @@ abstract class HardwareService( var unknownPriorityPushCount = 0 var startTime: String? = null - @get:Synchronized val isLogging: Boolean get() = logs != null @@ -186,6 +185,7 @@ abstract class HardwareService( } } .buffer(500, TimeUnit.MILLISECONDS) + .filter { it.isNotEmpty() } .replay() .autoConnect() .apply { logs = this }