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 00000000..9a8e9d51 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_pulse.png differ 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 00000000..1bb544f0 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_pulse.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_pulse.png b/app/src/main/res/drawable-xhdpi/ic_pulse.png new file mode 100644 index 00000000..bb5e5b96 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_pulse.png differ 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 00000000..cc0dbf9f Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_pulse.png differ 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 00000000..1abc0b82 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_pulse.png differ diff --git a/resizeLauncherIcon.pl b/resizeLauncherIcon.pl index fd48f6a8..c5a711c4 100644 --- a/resizeLauncherIcon.pl +++ b/resizeLauncherIcon.pl @@ -111,7 +111,9 @@ my $res_dir = "app/src/main/res"; #resize_scales( "_ArtWork/ic_list_tl_dark.png" ,$res_dir,"drawable","ic_list_tl_dark",0,32); #resize_scales( "_ArtWork/ic_list_member_dark.png" ,$res_dir,"drawable","ic_list_member_dark",0,32); -resize_scales( "_ArtWork/media_bg_dark.png" ,$res_dir,"drawable","media_bg_dark",0,24); +#resize_scales( "_ArtWork/media_bg_dark.png" ,$res_dir,"drawable","media_bg_dark",0,24); #resize_scales( "_ArtWork/v0.5.1/ic_launcher_foreground.png" ,$res_dir,"mipmap","ic_launcher_foreground",0,108); #resize_scales( "_ArtWork/v0.5.1/ic_launcher_background.png" ,$res_dir,"mipmap","ic_launcher_background",0,108); + +resize_scales( "_ArtWork/ic_pulse.png" ,$res_dir,"drawable","ic_pulse",0,32);