From f080f36a1ac37d4b9093db27cf131e308e3bc07d Mon Sep 17 00:00:00 2001 From: tateisu Date: Sat, 21 Apr 2018 08:16:44 +0900 Subject: [PATCH] =?UTF-8?q?=E3=82=AB=E3=83=A9=E3=83=A0=E3=81=AE=E5=87=A6?= =?UTF-8?q?=E7=90=86=E7=8A=B6=E6=B3=81=E8=A1=A8=E7=A4=BA=E3=81=AE=E6=94=B9?= =?UTF-8?q?=E5=96=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/jp/juggler/subwaytooter/Column.kt | 141 +++++++++--------- .../juggler/subwaytooter/ColumnViewHolder.kt | 65 ++++---- .../java/jp/juggler/subwaytooter/Styler.kt | 16 ++ .../subwaytooter/span/EmojiImageSpan.kt | 27 +++- .../jp/juggler/subwaytooter/util/Utils.kt | 4 + app/src/main/res/drawable-hdpi/ic_pulse.png | Bin 0 -> 896 bytes app/src/main/res/drawable-mdpi/ic_pulse.png | Bin 0 -> 651 bytes app/src/main/res/drawable-xhdpi/ic_pulse.png | Bin 0 -> 1127 bytes app/src/main/res/drawable-xxhdpi/ic_pulse.png | Bin 0 -> 1639 bytes .../main/res/drawable-xxxhdpi/ic_pulse.png | Bin 0 -> 2130 bytes resizeLauncherIcon.pl | 4 +- 11 files changed, 149 insertions(+), 108 deletions(-) create mode 100644 app/src/main/res/drawable-hdpi/ic_pulse.png create mode 100644 app/src/main/res/drawable-mdpi/ic_pulse.png create mode 100644 app/src/main/res/drawable-xhdpi/ic_pulse.png create mode 100644 app/src/main/res/drawable-xxhdpi/ic_pulse.png create mode 100644 app/src/main/res/drawable-xxxhdpi/ic_pulse.png diff --git a/app/src/main/java/jp/juggler/subwaytooter/Column.kt b/app/src/main/java/jp/juggler/subwaytooter/Column.kt index 0a8b1777..e36e746e 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/Column.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/Column.kt @@ -22,19 +22,26 @@ import jp.juggler.subwaytooter.api.entity.* import jp.juggler.subwaytooter.table.* import jp.juggler.subwaytooter.util.* -enum class TaskIndicatorState { - NO_TASK, - SCHEDULED, - BG_START, - BG_END, -} - enum class StreamingIndicatorState { NONE, REGISTERED, // registered, but not listening LISTENING, } +enum class ColumnTaskType{ + LOADING, + REFRESH_TOP, + REFRESH_BOTTOM, + GAP +} +abstract class ColumnTask( + val ctType: ColumnTaskType +) : AsyncTask() { + val ctStarted = AtomicBoolean(false) + val ctClosed = AtomicBoolean(false) +} + + class Column( val app_state : AppState, val context : Context, @@ -352,7 +359,8 @@ class Column( ////////////////////////////////////////////////////////////////////////////////////// - private var last_task : AsyncTask? = null + + internal var lastTask : ColumnTask? = null internal var bInitialLoading : Boolean = false internal var bRefreshLoading : Boolean = false @@ -1086,17 +1094,13 @@ class Column( } private fun cancelLastTask() { - if(last_task != null) { - last_task?.cancel(true) - last_task = null + if(lastTask != null) { + lastTask?.cancel(true) + lastTask = null // bInitialLoading = false bRefreshLoading = false mInitialLoadingError = context.getString(R.string.cancelled) - // - indicatorLoading = TaskIndicatorState.NO_TASK - indicatorRefresh = TaskIndicatorState.NO_TASK - indicatorGap = TaskIndicatorState.NO_TASK } } @@ -1396,10 +1400,6 @@ class Column( env.update(client) } - var indicatorLoading = TaskIndicatorState.NO_TASK - var indicatorRefresh = TaskIndicatorState.NO_TASK - var indicatorGap = TaskIndicatorState.NO_TASK - internal fun startLoading() { cancelLastTask() @@ -1414,14 +1414,13 @@ class Column( bRefreshLoading = false max_id = "" since_id = "" - indicatorLoading = TaskIndicatorState.SCHEDULED duplicate_map.clear() list_data.clear() fireShowContent(reason = "loading start", reset = true) val task = @SuppressLint("StaticFieldLeak") - object : AsyncTask() { + object : ColumnTask(ColumnTaskType.LOADING){ internal var parser = TootParser(context, access_info, highlightTrie = highlight_trie) internal var instance_tmp : TootInstance? = null @@ -1637,7 +1636,7 @@ class Column( } override fun doInBackground(vararg params : Void) : TootApiResult? { - indicatorLoading = TaskIndicatorState.BG_START + ctStarted.set(true) val client = TootApiClient(context, callback = object : TootApiCallback { override val isApiCancelled : Boolean @@ -1909,8 +1908,10 @@ class Column( } catch(ex : Throwable) { log.trace(ex) } - indicatorLoading = TaskIndicatorState.BG_END - client.publishApiProgress("") + ctClosed.set(true) + runOnMainLooperDelayed(333L){ + if( !isCancelled ) fireShowColumnStatus() + } } } @@ -1925,39 +1926,34 @@ class Column( return } - try { - bInitialLoading = false - last_task = null - - if(result.error != null) { - this@Column.mInitialLoadingError = result.error ?: "" - } else { - duplicate_map.clear() - list_data.clear() - val list_tmp = this.list_tmp - if(list_tmp != null) { - val list_pinned = this.list_pinned - if(list_pinned?.isNotEmpty() == true) { - val list_new = duplicate_map.filterDuplicate(list_pinned) - list_data.addAll(list_new) - } - val list_new = duplicate_map.filterDuplicate(list_tmp) + bInitialLoading = false + lastTask = null + + if(result.error != null) { + this@Column.mInitialLoadingError = result.error ?: "" + } else { + duplicate_map.clear() + list_data.clear() + val list_tmp = this.list_tmp + if(list_tmp != null) { + val list_pinned = this.list_pinned + if(list_pinned?.isNotEmpty() == true) { + val list_new = duplicate_map.filterDuplicate(list_pinned) list_data.addAll(list_new) } - - resumeStreaming(false) + val list_new = duplicate_map.filterDuplicate(list_tmp) + list_data.addAll(list_new) } - fireShowContent(reason = "loading updated", reset = true) - // 初期ロードの直後は先頭に移動する - viewHolder?.scrollToTop() - } finally { - indicatorLoading = TaskIndicatorState.NO_TASK - fireShowColumnStatus() + resumeStreaming(false) } + fireShowContent(reason = "loading updated", reset = true) + + // 初期ロードの直後は先頭に移動する + viewHolder?.scrollToTop() } } - this.last_task = task + this.lastTask = task task.executeOnExecutor(App1.task_executor) } // int scroll_hack; @@ -2044,7 +2040,7 @@ class Column( refresh_after_toot : Int ) { - if(last_task != null) { + if(lastTask != null) { if(! bSilent) { showToast(context, true, R.string.column_is_busy) val holder = viewHolder @@ -2079,11 +2075,12 @@ class Column( bRefreshLoading = true mRefreshLoadingError = "" - indicatorRefresh = TaskIndicatorState.SCHEDULED - fireShowColumnStatus() val task = @SuppressLint("StaticFieldLeak") - object : AsyncTask() { + object : ColumnTask( when{ + bBottom -> ColumnTaskType.REFRESH_BOTTOM + else->ColumnTaskType.REFRESH_TOP + }){ internal var parser = TootParser(context, access_info, highlightTrie = highlight_trie) internal var list_tmp : ArrayList? = null @@ -2580,7 +2577,7 @@ class Column( } override fun doInBackground(vararg params : Void) : TootApiResult? { - indicatorRefresh = TaskIndicatorState.BG_START + ctStarted.set(true) val client = TootApiClient(context, callback = object : TootApiCallback { override val isApiCancelled : Boolean @@ -2739,8 +2736,10 @@ class Column( } catch(ex : Throwable) { log.trace(ex) } - indicatorRefresh = TaskIndicatorState.BG_END - client.publishApiProgress("") + ctClosed.set(true) + runOnMainLooperDelayed(333L){ + if( !isCancelled ) fireShowColumnStatus() + } } } @@ -2755,7 +2754,7 @@ class Column( return } try { - last_task = null + lastTask = null bRefreshLoading = false val error = result.error @@ -2854,7 +2853,6 @@ class Column( } } } finally { - indicatorRefresh = TaskIndicatorState.NO_TASK fireShowColumnStatus() if(! bBottom) { @@ -2864,8 +2862,9 @@ class Column( } } } - this.last_task = task + this.lastTask = task task.executeOnExecutor(App1.task_executor) + fireShowColumnStatus() } internal fun startGap(gap : TootGap?) { @@ -2873,7 +2872,7 @@ class Column( showToast(context, true, "gap is null") return } - if(last_task != null) { + if(lastTask != null) { showToast(context, true, R.string.column_is_busy) return } @@ -2882,11 +2881,9 @@ class Column( bRefreshLoading = true mRefreshLoadingError = "" - indicatorGap = TaskIndicatorState.SCHEDULED - fireShowColumnStatus() val task = @SuppressLint("StaticFieldLeak") - object : AsyncTask() { + object : ColumnTask(ColumnTaskType.GAP){ internal var max_id = gap.max_id internal val since_id = gap.since_id internal var list_tmp : ArrayList? = null @@ -3109,7 +3106,7 @@ class Column( } override fun doInBackground(vararg params : Void) : TootApiResult? { - indicatorGap = TaskIndicatorState.BG_START + ctStarted.set(true) val client = TootApiClient(context, callback = object : TootApiCallback { override val isApiCancelled : Boolean @@ -3192,8 +3189,11 @@ class Column( } catch(ex : Throwable) { log.trace(ex) } - indicatorGap = TaskIndicatorState.BG_END - client.publishApiProgress("") + + ctClosed.set(true) + runOnMainLooperDelayed(333L){ + if( !isCancelled ) fireShowColumnStatus() + } } } @@ -3210,7 +3210,7 @@ class Column( try { - last_task = null + lastTask = null bRefreshLoading = false val error = result.error @@ -3285,14 +3285,13 @@ class Column( } } } finally { - indicatorGap = TaskIndicatorState.NO_TASK fireShowColumnStatus() } } } - this.last_task = task - + this.lastTask = task task.executeOnExecutor(App1.task_executor) + fireShowColumnStatus() } enum class HeaderType(val viewType : Int) { diff --git a/app/src/main/java/jp/juggler/subwaytooter/ColumnViewHolder.kt b/app/src/main/java/jp/juggler/subwaytooter/ColumnViewHolder.kt index 212c1f8a..213e793d 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/ColumnViewHolder.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/ColumnViewHolder.kt @@ -28,6 +28,9 @@ import jp.juggler.subwaytooter.table.AcctColor import android.support.v7.widget.ListRecyclerView import android.support.v7.widget.LinearLayoutManager import android.support.v7.widget.RecyclerView +import android.text.SpannableStringBuilder +import android.text.Spanned +import jp.juggler.subwaytooter.span.EmojiImageSpan import jp.juggler.subwaytooter.util.* import jp.juggler.subwaytooter.view.ListDivider import java.io.Closeable @@ -515,46 +518,40 @@ class ColumnViewHolder( fun showColumnStatus() { val column = this.column - val sb = StringBuilder() + + val sb = SpannableStringBuilder() + + + try { if(column == null) { sb.append('?') } else { - when(column.indicatorLoading) { - TaskIndicatorState.NO_TASK -> { - } - - TaskIndicatorState.SCHEDULED -> sb.append("L?") - TaskIndicatorState.BG_START -> sb.append("L") - TaskIndicatorState.BG_END -> sb.append("L!") - } - val tb = if(column.bRefreshingTop) { - 'T' - } else { - 'B' - } - when(column.indicatorRefresh) { - TaskIndicatorState.NO_TASK -> { - } - - TaskIndicatorState.SCHEDULED -> sb.append("${tb}?") - TaskIndicatorState.BG_START -> sb.append(tb) - TaskIndicatorState.BG_END -> sb.append("${tb}!") - } - when(column.indicatorGap) { - TaskIndicatorState.NO_TASK -> { - } - - TaskIndicatorState.SCHEDULED -> sb.append("G?") - TaskIndicatorState.BG_START -> sb.append("G") - TaskIndicatorState.BG_END -> sb.append("G!") + val task = column.lastTask + if( task != null){ + sb.append(when(task.ctType){ + ColumnTaskType.LOADING -> 'L' + ColumnTaskType.REFRESH_TOP ->'T' + ColumnTaskType.REFRESH_BOTTOM ->'B' + ColumnTaskType.GAP->'G' + }) + sb.append(when{ + task.isCancelled -> "~" + task.ctClosed.get() -> "!" + task.ctStarted.get() -> "" + else-> "?" + }) } when(column.getStreamingStatus()) { StreamingIndicatorState.NONE -> { } - - StreamingIndicatorState.REGISTERED -> sb.append("S?") - StreamingIndicatorState.LISTENING -> sb.append("S") + StreamingIndicatorState.REGISTERED ->{ + sb.appendColorShadeIcon(activity,R.drawable.ic_pulse,"Streaming") + sb.append("?") + } + StreamingIndicatorState.LISTENING -> { + sb.appendColorShadeIcon(activity,R.drawable.ic_pulse,"Streaming") + } } } @@ -564,6 +561,10 @@ class ColumnViewHolder( } } + + + + fun showColumnColor() { val column = this.column diff --git a/app/src/main/java/jp/juggler/subwaytooter/Styler.kt b/app/src/main/java/jp/juggler/subwaytooter/Styler.kt index 01a7326f..aee62be0 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/Styler.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/Styler.kt @@ -259,3 +259,19 @@ object Styler { Math.min(lp.width, lp.height).toFloat() * round_ratio } + +fun SpannableStringBuilder.appendColorShadeIcon( + context:Context, + drawable_id:Int, + text:String +){ + val start = this.length + this.append(text) + val end = this.length + this.setSpan( + EmojiImageSpan(context, drawable_id,useColorShader=true), + start, + end, + Spanned.SPAN_EXCLUSIVE_EXCLUSIVE + ) +} diff --git a/app/src/main/java/jp/juggler/subwaytooter/span/EmojiImageSpan.kt b/app/src/main/java/jp/juggler/subwaytooter/span/EmojiImageSpan.kt index 02ba51eb..86f18144 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/span/EmojiImageSpan.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/span/EmojiImageSpan.kt @@ -1,8 +1,8 @@ package jp.juggler.subwaytooter.span import android.content.Context -import android.graphics.Canvas -import android.graphics.Paint +import android.graphics.* +import android.graphics.drawable.BitmapDrawable import android.graphics.drawable.Drawable import android.support.annotation.IntRange import android.support.v4.content.ContextCompat @@ -10,7 +10,11 @@ import android.text.style.ReplacementSpan import java.lang.ref.WeakReference -class EmojiImageSpan(context : Context, private val res_id : Int) : ReplacementSpan() { +class EmojiImageSpan( + context : Context, + private val res_id : Int, + private val useColorShader:Boolean = false +) : ReplacementSpan() { companion object { @@ -62,6 +66,9 @@ class EmojiImageSpan(context : Context, private val res_id : Int) : ReplacementS return size } + private var lastColor :Int? = null + private var lastColorFilter: PorterDuffColorFilter? = null + override fun draw( canvas : Canvas, text : CharSequence, @@ -82,7 +89,19 @@ class EmojiImageSpan(context : Context, private val res_id : Int) : ReplacementS canvas.save() canvas.translate(x, transY.toFloat()) d.setBounds(0, 0, size, size) - d.draw(canvas) + + if( useColorShader && d is BitmapDrawable){ + if( paint.color != lastColor || lastColorFilter == null) { + lastColor = paint.color + lastColorFilter = PorterDuffColorFilter(paint.color, PorterDuff.Mode.SRC_ATOP) + } + val saveColorFilter = d.colorFilter + d.colorFilter = lastColorFilter + d.draw(canvas) + d.colorFilter = saveColorFilter + }else { + d.draw(canvas) + } canvas.restore() } diff --git a/app/src/main/java/jp/juggler/subwaytooter/util/Utils.kt b/app/src/main/java/jp/juggler/subwaytooter/util/Utils.kt index e3b2bb75..ec365a4a 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/util/Utils.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/util/Utils.kt @@ -693,6 +693,10 @@ fun runOnMainLooper(proc : () -> Unit) { Handler(looper).post { proc() } } } +fun runOnMainLooperDelayed(delayMs:Long, proc : () -> Unit) { + val looper = Looper.getMainLooper() + Handler(looper).postDelayed({ proc() },delayMs) +} //////////////////////////////////////////////////////////////////// // View diff --git a/app/src/main/res/drawable-hdpi/ic_pulse.png b/app/src/main/res/drawable-hdpi/ic_pulse.png new file mode 100644 index 0000000000000000000000000000000000000000..9a8e9d5181e8578296407796b1b953e92c964e55 GIT binary patch literal 896 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA0wn)(8}a}tmUKs7M+SzC{oH>NS%G|}ByV>Y zhX3vTXZ8bm>?NMQuI!IkL*~dIVJdoim03YjCn?m9WTxv_lU24ng2qSgXiPHb|sD@OF9LUv}QOSk~cN* zl(dYTvEXTed#v4@;`3)_&b0o1ubDmm&%5^B-)ytrzQ31Ud){g``;Pk+D^|K+WxPE{ zJT*6QzrZ(9-uR1Gk|o|~X8(`X<9b*U$r62*>*1t#N!%vh7mHUVY`s^+5$9FE`|EVa z3z_L@_JM4U`*~;X-OJOogy}kqoPP5I*Mc)Va@wmFAA4b<+M+nURr2+F(}QB052f1H zd33%j>|zToJjN>?uz~Be;2xfR2mKS=1)H)CtA8y|5J{hI;dpexhuV*3b_roKzHykX z)_vuE_}z}jXCG;LX&F4US{Cb?zfN8@An;>cVbc=s-iH5w+SXP6U2*(#!gSN499Fj6 zH_H5t*NJ|xNRofY|ex_ZWvMUJu-kE^Wgr&~K7y?3T~y6L)< z&0UQC$%oFLe*M~5QRdXMO4;0!;2qPpP1S_k_KY45O{^~vCEjSczcPa5i^uOb~ zSf|fUwC+9FZh3EKq0#d?7Y_D|kNqD`QxEMi@9;gwe){_&8^=Y9-&T0Z>-}=ISjV|) zM#-VKuKvqvCe1$ob@BBB9a47J(u>0$iH8&!CZB!reDS))?Y{PM; z>Vjw6Ut6kJy*iW|Ki#pwY;&DyB-_C$H?K%#+V5Yha50c${q&7Ce>7U^*XG$SnJaR2 zfA-^ln%xhqKU7*PuHBzea70(s{dQMn!|%S`(+XdjZ*Six3ry0gC9V-ADTyViR>?)F zK#IZ0z|cb1z(m)`FvP&z%Fxux)J)sJ(#pUf=9O?UiiX_$l+3hB+!}PZuRagdz~JfX K=d#Wzp$PykFo`Gt literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-mdpi/ic_pulse.png b/app/src/main/res/drawable-mdpi/ic_pulse.png new file mode 100644 index 0000000000000000000000000000000000000000..1bb544f003a0dd99f5acd098a2eafa6cc887786f GIT binary patch literal 651 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE0wix1Z>k4UEa{HEjtmSN`?>!lvI6-`N#5=* z4FB8v&+G^C*h@TpUD+S8hzjsqY+Edr%D}+5-_yl0#N+tctNXKq6Ge`F^q(|4VwFZ? zncx*S0WZ-d7o3sbzwI@!fuugHEw`%o|m(J&wcp3SqgxH8uh}M-OfXlfXxaYCSHADm zNvnBJ{{OzR_j+&M)n}DE^f}_T$*kW~^PBIK()l$BM|a<7mvheA_o7_kTjrTLmg049 zJpDw}ud}8n-Dr6r(#I($B|gXfn5*Ks(;E&Ke?A*?HG}W$Ei{;uRqVs?Vn;L`;7faWR9{#ZLO3L8t51A8&hi*)@NQGFbd{<+q$8-D54H7&hn0E}JL64!{5l*E!$ ztK_0oAjM#0U}&LhV4`be7-C>V5BZ4-`?ve})Pwf(+lp zJG$5wRQPgUqfope91MZQ{6V1@WVX9WOUE#_kuq6{dBG6alC%~95GZGru^WOQFrQh* zUK9j@MkW}0p1W#bPhd-uJ&oEz6DVhm@5-4DYzZW1065fvEdiYYO0J#hz?Oi{fCoas zp$=>i<}C_^Vjn{&6g`FQiuDYmQ1mgA&u;sH-4?8q42bc89>Adv>^i8Iyja3=mju?y^LQBm*>$j0 z-Wf}1aY;aDK-74$>0pPkcDj3UPh`M)o@uF_(gBO%_TWdOi&@t!lD2Kj9JOjLe5>ARzvHs2PtGsA{JU4{$**e=uDGoGX3 zxkigv&y&hgh?~Cf3dKVv<|eSu@bzOsnhx4!E&aG6Mk z@X91vH07613@{C^AG`xcj@OoO+YCZ=%oZu)MtFWy`1rkDZ)AXu_5i|C%%5r zELU;LfIT^8(?=f>yKeZEjx^s(IeR+L61>d_aK{vF3*;s+%GsnEppo1U%Go4W*8%9` z45JpYyZPniz)!X(eR!s#-*mrZ=rvgcER*yNOQ4&W7Rr!a9D*B`!%rd3t&_Bj6r`s3)brxle0a^#UqzevF_s{XuIZY2k z4AM<2HRBBr=Y_y6v!rD?@2O-Cv#A_^a7grnQ8}N=Zra{vGUC3HntbYx+4WjbwdWNBu3 t05UK!I4v+VEiy4wFgH3eH99pmD=;}aFfc8*tIq%c002ovPDHLkV1nZY=l1{r literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxhdpi/ic_pulse.png b/app/src/main/res/drawable-xxhdpi/ic_pulse.png new file mode 100644 index 0000000000000000000000000000000000000000..cc0dbf9f4e12bb00138cbde801c475944619139a GIT binary patch literal 1639 zcmV-t2AKJYP)s4R9Ye$u^37HK#V(xD~t!i0?rKQ=BQP>m+I zFab4w6e+EW5u$*?v?I0UqhJHE)3H{dopJG|Z_fLeH}mfFz2WKn@1pnIbLZakf4%42 zbMJdlL=i<4QN;flGpA{sa!Q#fG#OQdd50m+vYoO*o1%s{NT$kbg*HV6vl!3`^b;zy zIquQKq)uR*1%6LIBkvJbi!s713?6~C2B|S<1j@PFAU6e#Ks~hvd1=rHw3tSyV^**T zJYxQyw#T86zi}uZ0@4GLqV8)xq)s5rO1++-TWEaEhtvtwi#;I5`$FSu z98xE+N_aqo9wv3k*L+BwfXsrrIL(MIRdLg`Pawp-LfX$5XLZTfbD1iEMck^_M>z`g z3ayWENR>bP>jPCm4RtGcnHR0A-Z zi{gIBw|q!#2}ut)$sYiW8sd;|`H&g`s|P^R5Qlv80u=%w!%29*UXyO@d@fr@2OYHe zMbZpA4*+;vICi(g?>DF1eS*z}A@Hm)^as(NTO10}tM}^5|2A7?3CS$zpr{Xtd}vM{;xK9{wLIZJrje*$$P#>e?dXc9T)L+cGvJ7K>Gv~ZnX z?_=1Y%bpKav)mxpn`9pd825mrL6uEOZ=teB$3D!_ck|Dn16QY*K z<^s+h0Pv(kJ~W@0uDi;x==o-b`EKMr#ti;4P6s!u6WAn%I!uX8k4A?$v_bs(6fX;@ z*Y+!m#r$gH@<~pMJ3*J&Erxl^wnrtGGM>wJieK+y0pq%qu)L~*YLWDr%aF*YT%e3T zhdn^U433FD4+u?|--Tu)@U7z_`Y@yUNs@e}l7=qM>Hz?I#5B)~_7&oricxBS7e&vA zX%VfoefJ6W{&t)xKueT0zB30YVl{_e*PT)t*Wz@xlE`2U*7d^Td zPt~G)uWb26Z4?y(gS_CNwL6!EStHu^>9r(V8B`>2MEgooi$ddU5fR1-C5+I+pN@TG z9BN9@NT=3udPLu2EN3;9ru-&J6Wg*_@_{1p9AJ$@uR3H%_Nd^5V+=Z!;{#Vhqu8>v ze5glE{ZfvjJj~^c=Ev2upmgbYm~=0 zV=+INOR>LE_E==A{R7e{XMw>|u%UDYF zG<+nQBV-)9WVp+o@0SVrp)o@;+?D^(W_a^YIY-R4{WDKy^8m&;ln$ZIUQFc$jQP+M z1~?fM0>*3JDIDN(unFvQJxT!~^C7Lof)YYC%n|vJRt|VGkylG7W;wHc8Y}|lI5f$Z z!6IOG;{hVUCh(0HA=Fy}@_r~_Ayh-9cwaW+-FJ-M{Rc);_iq57o8Bh}1asKIc`mSv zxrH`G9Tij*+JqvCD58iW{>%6m$#C~QK+aX{0000bbVXQnWMOn=I%9HWVRU5xGB7bX zEig1KGBH#zH##vjIyE*cFgZFfFkI>ra{vGUC3HntbYx+4WjbwdWNBu305UK!I4v+V lEiy4wFgH3eH99pmD=;}aFfc8*tIq%c002ovPDHLkV1kEv?U4Wg literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxxhdpi/ic_pulse.png b/app/src/main/res/drawable-xxxhdpi/ic_pulse.png new file mode 100644 index 0000000000000000000000000000000000000000..1abc0b829d4ded9da4bc3ff3e032033c7fd42345 GIT binary patch literal 2130 zcmZ{lc{J2}AIE>QWJa=&oo0|VX@oGya1FAQElFZVCK4G&O6i&*yCh_}jityPniyqw z)hNpiAv>XwWGs`SXsplN=RD6J&mYep-|zc;Ugx~d`JT`Dp7Tz2K4mMsUw%IT0KzBj ztX=s@`RBl){QI&|IhG&L081xJ0I0qvv`yH@pTT|(w${L2;=P_%{0SCu(g_Fq1eFpK zHLfd>Ch|$bNoz~@8x!1ub7=^iY-h~j&gE7$ORx&m)2cz0LH7<1Hjo{%mSBk?59s|B zXqWU0AMK2EOiET)x3(I}v$IYENgYyY(xmP)Uu=p@ff#JA*qCtMm^sA{@( zv+ikg>XW@4rkn4md(6+GiWOhqYkfLxN#d$DX|j5LZAoFik2TpN4%9DnQ#q-Q>buH23!u#8OG5MsT)Es4`ne&7~QkTNd7F#R_qoWkEo- zrnWWS(XYgv%RSX`Q^ROM%@V6>r}U<$1POG`qm&SLJi-(gQS5Wc0*jQZVEd?^IR+I|{1Eul5G((0L{kN1^F=x8#RESX zR360#JOs~po_$~l)B^n*PRf#Zd1I{9??6cdYL^j)X@h*+BRRLDC@no3fz<%DJIdrt z4Q1AN@`c-ZcFjcY#~!#q`0#Qxb{?LGJMGYzo;t~X+|G${l8;;^qzO(+0r3AImKJzS9G@-1QD&KdDKnV`R0_2gb&7i{$x^L-amgqWxeV{2*32Fb!Ppb;eGkFtMGxwI4o211w3Vk@(Mo!Qw z+}{zUq>5K>(V3g%FFAgRrJoYu(4)o*5zerg;jvz%5bu{~p)3nz84eFH@rx|yt7DTs zqW%)Ukf0J-f>yn@X)9lH+??=)xhPIN7u z(K~FSJ)K*cZWNMx_T{%Hq_5=MrnrVLmN4x*v+shXM(Iz+hW`{)B)Q+8?Rh`GGfhLt zcz$(l*61|*3~>eT5fWC<^^zL9qAQIfEu1*rn{Zi&r~J<={o#&&)nUUFx`)6vL~+AR zuc!vk@hGzV;Zd=;(7v4;hfcDg!h?pD%ktyA^}IM1?ix zxCPBgK7-QAhJ(J9fVm9vTQGTs0U>tP3;Rl~aN9B8H7ko|pPkAb2qc79E>TWR;zbSp z0!<&qVLHz1Wc@N)j@W_$r96;D_rMu3EHWh%(h{fwKj%7gHnY zTULU-LLc@cxiZzBCx_fh?Oe0w&)-$HTx8wU&ema0&KqtrX$9VI8442(J;$X329bw< zm}z*z{ZF6^aVkP)Ul+8jj0~!is}?%Z0vVsRh5h!)Yk@TEyk7Uq9z+;oUSKWk!U~oi zZS+D)MGieEHZ%&7iHom*jFedJ?I;-CsY+ganIQ$E&0pC-VTI#7IMZ8=o$x@y=?x9` z9f#HEH1bpt{ri5T00auLNg-K9vl9!OORi=hdcO2iKy;VrsJY z7KvDWkolhEf6I^Gf(LB3MIU-6fBcBQsLwBHwFsPNgg+sIh`AU}