mirror of
https://github.com/tateisu/SubwayTooter
synced 2025-01-28 01:29:23 +01:00
TL更新時のちらつきをなくした
This commit is contained in:
parent
67a6d87777
commit
4ab6f78121
@ -12,8 +12,8 @@ android {
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 27
|
||||
|
||||
versionCode 209
|
||||
versionName "2.0.9"
|
||||
versionCode 210
|
||||
versionName "2.1.0"
|
||||
|
||||
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
||||
|
||||
|
@ -27,6 +27,7 @@ import android.support.design.widget.NavigationView
|
||||
import android.support.v4.view.GravityCompat
|
||||
import android.support.v4.widget.DrawerLayout
|
||||
import android.support.v7.app.AppCompatActivity
|
||||
import android.support.v7.widget.DefaultItemAnimator
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import android.view.Window
|
||||
@ -297,10 +298,10 @@ class ActMain : AppCompatActivity()
|
||||
override fun run() {
|
||||
handler.removeCallbacks(this)
|
||||
if(! bStart) return
|
||||
for(c in app_state.column_list) {
|
||||
c.fireShowContent()
|
||||
}
|
||||
if(Pref.bpRelativeTimestamp(pref)) {
|
||||
for(c in app_state.column_list) {
|
||||
c.fireRelativeTime()
|
||||
}
|
||||
handler.postDelayed(this, 10000L)
|
||||
}
|
||||
}
|
||||
@ -453,21 +454,14 @@ class ActMain : AppCompatActivity()
|
||||
// アカウント設定から戻ってきたら、カラムを消す必要があるかもしれない
|
||||
run {
|
||||
val new_order = ArrayList<Int>()
|
||||
var i = 0
|
||||
val ie = app_state.column_list.size
|
||||
while(i < ie) {
|
||||
for( i in 0 until app_state.column_list.size){
|
||||
val column = app_state.column_list[i]
|
||||
|
||||
if(! column.access_info.isNA) {
|
||||
val sa = SavedAccount.loadAccount(this@ActMain, column.access_info.db_id)
|
||||
if(sa == null) {
|
||||
++ i
|
||||
continue
|
||||
}
|
||||
if(sa == null) continue
|
||||
}
|
||||
|
||||
new_order.add(i)
|
||||
++ i
|
||||
}
|
||||
|
||||
if(new_order.size != app_state.column_list.size) {
|
||||
@ -489,6 +483,10 @@ class ActMain : AppCompatActivity()
|
||||
// カラムの表示範囲インジケータを更新
|
||||
updateColumnStripSelection(- 1, - 1f)
|
||||
|
||||
for(c in app_state.column_list) {
|
||||
c.fireShowContent(reason="ActMain onStart",reset=true)
|
||||
}
|
||||
|
||||
// 相対時刻表示
|
||||
proc_updateRelativeTime.run()
|
||||
|
||||
@ -740,7 +738,7 @@ class ActMain : AppCompatActivity()
|
||||
val idx = data.getIntExtra(ActColumnCustomize.EXTRA_COLUMN_INDEX, 0)
|
||||
if(idx >= 0 && idx < app_state.column_list.size) {
|
||||
app_state.column_list[idx].fireColumnColor()
|
||||
app_state.column_list[idx].fireShowContent()
|
||||
app_state.column_list[idx].fireShowContent(reason="ActMain column color changed",reset=true)
|
||||
}
|
||||
updateColumnStrip()
|
||||
}
|
||||
@ -1247,6 +1245,12 @@ class ActMain : AppCompatActivity()
|
||||
}
|
||||
})
|
||||
|
||||
env.tablet_pager.itemAnimator = null
|
||||
// val animator = env.tablet_pager.itemAnimator
|
||||
// if( animator is DefaultItemAnimator){
|
||||
// animator.supportsChangeAnimations = false
|
||||
// }
|
||||
|
||||
env.tablet_snap_helper = GravitySnapHelper(Gravity.START)
|
||||
env.tablet_snap_helper.attachToRecyclerView(env.tablet_pager)
|
||||
|
||||
@ -1952,7 +1956,7 @@ class ActMain : AppCompatActivity()
|
||||
fun showColumnMatchAccount(account : SavedAccount) {
|
||||
for(column in app_state.column_list) {
|
||||
if(account.acct == column.access_info.acct) {
|
||||
column.fireShowContent()
|
||||
column.fireRebindAdapterItems()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,9 @@
|
||||
package jp.juggler.subwaytooter
|
||||
|
||||
enum class AdapterChangeType {
|
||||
RangeInsert,
|
||||
RangeRemove,
|
||||
RangeChange,
|
||||
}
|
||||
|
||||
class AdapterChange(val type : AdapterChangeType, val listIndex : Int, val count : Int = 1)
|
@ -343,7 +343,7 @@ class Column(
|
||||
|
||||
internal var task_progress : String? = null
|
||||
|
||||
internal val list_data = BucketList<Any>()
|
||||
internal val list_data = BucketList<TimelineItem>()
|
||||
private val duplicate_map = DuplicateMap()
|
||||
|
||||
private val isFilterEnabled : Boolean
|
||||
@ -369,7 +369,7 @@ class Column(
|
||||
// ListViewの表示更新が追いつかないとスクロール位置が崩れるので
|
||||
// 一定時間より短期間にはデータ更新しないようにする
|
||||
private var last_show_stream_data : Long = 0
|
||||
private val stream_data_queue = LinkedList<Any>()
|
||||
private val stream_data_queue = LinkedList<TimelineItem>()
|
||||
|
||||
private var bPutGap : Boolean = false
|
||||
|
||||
@ -668,7 +668,7 @@ class Column(
|
||||
}
|
||||
}
|
||||
if(bChanged) {
|
||||
fireShowContent()
|
||||
fireShowContent(reason="findStatus",reset=true)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -680,7 +680,7 @@ class Column(
|
||||
|
||||
val INVALID_ACCOUNT = - 1L
|
||||
|
||||
val tmp_list = ArrayList<Any>(list_data.size)
|
||||
val tmp_list = ArrayList<TimelineItem>(list_data.size)
|
||||
for(o in list_data) {
|
||||
if(o is TootStatus) {
|
||||
if(who_id == (o.account.id)) continue
|
||||
@ -698,7 +698,7 @@ class Column(
|
||||
if(tmp_list.size != list_data.size) {
|
||||
list_data.clear()
|
||||
list_data.addAll(tmp_list)
|
||||
fireShowContent()
|
||||
fireShowContent(reason="removeAccountInTimeline")
|
||||
|
||||
}
|
||||
}
|
||||
@ -706,7 +706,7 @@ class Column(
|
||||
// ミュート解除が成功した時に呼ばれる
|
||||
fun removeFromMuteList(target_account : SavedAccount, who_id : Long) {
|
||||
if(column_type == TYPE_MUTES && target_account.acct == access_info.acct) {
|
||||
val tmp_list = ArrayList<Any>(list_data.size)
|
||||
val tmp_list = ArrayList<TimelineItem>(list_data.size)
|
||||
for(o in list_data) {
|
||||
if(o is TootAccount) {
|
||||
if(o.id == who_id) continue
|
||||
@ -716,7 +716,7 @@ class Column(
|
||||
if(tmp_list.size != list_data.size) {
|
||||
list_data.clear()
|
||||
list_data.addAll(tmp_list)
|
||||
fireShowContent()
|
||||
fireShowContent(reason="removeFromMuteList")
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -724,7 +724,7 @@ class Column(
|
||||
// ブロック解除が成功したので、ブロックリストから削除する
|
||||
fun removeFromBlockList(target_account : SavedAccount, who_id : Long) {
|
||||
if(column_type == TYPE_BLOCKS && target_account.acct == access_info.acct) {
|
||||
val tmp_list = ArrayList<Any>(list_data.size)
|
||||
val tmp_list = ArrayList<TimelineItem>(list_data.size)
|
||||
for(o in list_data) {
|
||||
if(o is TootAccount) {
|
||||
if(o.id == who_id) continue
|
||||
@ -734,7 +734,7 @@ class Column(
|
||||
if(tmp_list.size != list_data.size) {
|
||||
list_data.clear()
|
||||
list_data.addAll(tmp_list)
|
||||
fireShowContent()
|
||||
fireShowContent(reason="removeFromBlockList")
|
||||
}
|
||||
|
||||
}
|
||||
@ -744,7 +744,7 @@ class Column(
|
||||
if(target_account.acct != access_info.acct) return
|
||||
|
||||
if(column_type == TYPE_FOLLOW_REQUESTS) {
|
||||
val tmp_list = ArrayList<Any>(list_data.size)
|
||||
val tmp_list = ArrayList<TimelineItem>(list_data.size)
|
||||
for(o in list_data) {
|
||||
if(o is TootAccount) {
|
||||
if(o.id == who_id) continue
|
||||
@ -754,11 +754,11 @@ class Column(
|
||||
if(tmp_list.size != list_data.size) {
|
||||
list_data.clear()
|
||||
list_data.addAll(tmp_list)
|
||||
fireShowContent()
|
||||
fireShowContent(reason="removeFollowRequest 1")
|
||||
}
|
||||
} else {
|
||||
// 他のカラムでもフォロー状態の表示更新が必要
|
||||
fireShowContent()
|
||||
fireShowContent(reason="removeFollowRequest 2",reset=true)
|
||||
}
|
||||
}
|
||||
|
||||
@ -767,7 +767,7 @@ class Column(
|
||||
|
||||
if(target_account.host != access_info.host) return
|
||||
|
||||
val tmp_list = ArrayList<Any>(list_data.size)
|
||||
val tmp_list = ArrayList<TimelineItem>(list_data.size)
|
||||
for(o in list_data) {
|
||||
if(o is TootStatus) {
|
||||
if(status_id == o.id) continue
|
||||
@ -783,22 +783,23 @@ class Column(
|
||||
if(tmp_list.size != list_data.size) {
|
||||
list_data.clear()
|
||||
list_data.addAll(tmp_list)
|
||||
fireShowContent()
|
||||
fireShowContent(reason="removeStatus")
|
||||
}
|
||||
}
|
||||
|
||||
fun removeNotifications() {
|
||||
cancelLastTask()
|
||||
|
||||
list_data.clear()
|
||||
mRefreshLoadingError = ""
|
||||
bRefreshLoading = false
|
||||
mInitialLoadingError = ""
|
||||
bInitialLoading = false
|
||||
max_id = ""
|
||||
since_id = ""
|
||||
|
||||
fireShowContent()
|
||||
|
||||
list_data.clear()
|
||||
duplicate_map.clear()
|
||||
fireShowContent(reason="removeNotifications",reset=true)
|
||||
|
||||
PollingWorker.queueNotificationCleared(context, access_info.db_id)
|
||||
}
|
||||
@ -807,7 +808,7 @@ class Column(
|
||||
if(column_type != TYPE_NOTIFICATIONS) return
|
||||
if(access_info.acct != target_account.acct) return
|
||||
|
||||
val tmp_list = ArrayList<Any>(list_data.size)
|
||||
val tmp_list = ArrayList<TimelineItem>(list_data.size)
|
||||
for(o in list_data) {
|
||||
if(o is TootNotification) {
|
||||
if(o.id == notification.id) continue
|
||||
@ -819,12 +820,12 @@ class Column(
|
||||
if(tmp_list.size != list_data.size) {
|
||||
list_data.clear()
|
||||
list_data.addAll(tmp_list)
|
||||
fireShowContent()
|
||||
fireShowContent(reason="removeNotificationOne")
|
||||
}
|
||||
}
|
||||
|
||||
fun onMuteAppUpdated() {
|
||||
val tmp_list = ArrayList<Any>(list_data.size)
|
||||
val tmp_list = ArrayList<TimelineItem>(list_data.size)
|
||||
|
||||
val muted_app = MutedApp.nameSet
|
||||
val muted_word = MutedWord.nameSet
|
||||
@ -843,7 +844,7 @@ class Column(
|
||||
if(tmp_list.size != list_data.size) {
|
||||
list_data.clear()
|
||||
list_data.addAll(tmp_list)
|
||||
fireShowContent()
|
||||
fireShowContent(reason="onMuteAppUpdated")
|
||||
}
|
||||
}
|
||||
|
||||
@ -863,7 +864,7 @@ class Column(
|
||||
val checker =
|
||||
{ acct : String? -> if(acct == null) false else reDomain.matcher(acct).find() }
|
||||
|
||||
val tmp_list = ArrayList<Any>(list_data.size)
|
||||
val tmp_list = ArrayList<TimelineItem>(list_data.size)
|
||||
|
||||
for(o in list_data) {
|
||||
if(o is TootStatus) {
|
||||
@ -879,7 +880,7 @@ class Column(
|
||||
if(tmp_list.size != list_data.size) {
|
||||
list_data.clear()
|
||||
list_data.addAll(tmp_list)
|
||||
fireShowContent()
|
||||
fireShowContent(reason="onDomainBlockChanged")
|
||||
}
|
||||
|
||||
}
|
||||
@ -943,30 +944,46 @@ class Column(
|
||||
return _holder_list.size > 1
|
||||
}
|
||||
|
||||
internal fun fireShowContent() {
|
||||
internal fun fireShowContent(
|
||||
reason:String,
|
||||
changeList : List<AdapterChange>? = null,
|
||||
reset : Boolean = false
|
||||
) {
|
||||
if(! Utils.isMainThread) {
|
||||
throw RuntimeException("fireShowColumnHeader: not on main thread.")
|
||||
throw RuntimeException("fireShowContent: not on main thread.")
|
||||
}
|
||||
val holder = viewHolder
|
||||
holder?.showContent()
|
||||
viewHolder?.showContent(reason,changeList,reset)
|
||||
}
|
||||
|
||||
internal fun fireShowColumnHeader() {
|
||||
if(! Utils.isMainThread) {
|
||||
throw RuntimeException("fireShowColumnHeader: not on main thread.")
|
||||
}
|
||||
val holder = viewHolder
|
||||
holder?.showColumnHeader()
|
||||
viewHolder?.showColumnHeader()
|
||||
}
|
||||
|
||||
internal fun fireColumnColor() {
|
||||
if(! Utils.isMainThread) {
|
||||
throw RuntimeException("fireShowColumnHeader: not on main thread.")
|
||||
throw RuntimeException("fireColumnColor: not on main thread.")
|
||||
}
|
||||
val holder = viewHolder
|
||||
holder?.showColumnColor()
|
||||
viewHolder?.showColumnColor()
|
||||
}
|
||||
|
||||
fun fireRelativeTime() {
|
||||
if(! Utils.isMainThread) {
|
||||
throw RuntimeException("fireRelativeTime: not on main thread.")
|
||||
}
|
||||
viewHolder?.updateRelativeTime()
|
||||
}
|
||||
|
||||
fun fireRebindAdapterItems() {
|
||||
if(! Utils.isMainThread) {
|
||||
throw RuntimeException("fireRelativeTime: not on main thread.")
|
||||
}
|
||||
viewHolder?.rebindAdapterItems()
|
||||
}
|
||||
|
||||
|
||||
private fun cancelLastTask() {
|
||||
if(last_task != null) {
|
||||
last_task?.cancel(true)
|
||||
@ -1028,27 +1045,30 @@ class Column(
|
||||
|
||||
}
|
||||
|
||||
private inline fun <reified T> addAll(
|
||||
dstArg : ArrayList<Any>?,
|
||||
private inline fun <reified T : TimelineItem> addAll(
|
||||
dstArg : ArrayList<TimelineItem>?,
|
||||
src : ArrayList<T>
|
||||
) : ArrayList<Any> {
|
||||
) : ArrayList<TimelineItem> {
|
||||
val dst = dstArg ?: ArrayList()
|
||||
for(item in src) {
|
||||
dst.add(item as Any)
|
||||
dst.add(item as TimelineItem)
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
private fun addOne(dstArg : ArrayList<Any>?, item : Any) : ArrayList<Any> {
|
||||
private fun addOne(
|
||||
dstArg : ArrayList<TimelineItem>?,
|
||||
item : TimelineItem
|
||||
) : ArrayList<TimelineItem> {
|
||||
val dst = dstArg ?: ArrayList()
|
||||
dst.add(item)
|
||||
return dst
|
||||
}
|
||||
|
||||
private fun addWithFilterStatus(
|
||||
dstArg : ArrayList<Any>?,
|
||||
dstArg : ArrayList<TimelineItem>?,
|
||||
src : ArrayList<TootStatus>
|
||||
) : ArrayList<Any> {
|
||||
) : ArrayList<TimelineItem> {
|
||||
val dst = dstArg ?: ArrayList()
|
||||
for(status in src) {
|
||||
if(! isFiltered(status)) {
|
||||
@ -1059,9 +1079,9 @@ class Column(
|
||||
}
|
||||
|
||||
private fun addWithFilterNotification(
|
||||
dstArg : ArrayList<Any>?,
|
||||
dstArg : ArrayList<TimelineItem>?,
|
||||
src : ArrayList<TootNotification>
|
||||
) : ArrayList<Any> {
|
||||
) : ArrayList<TimelineItem> {
|
||||
val dst = dstArg ?: ArrayList()
|
||||
for(item in src) {
|
||||
if(! isFiltered(item)) dst.add(item)
|
||||
@ -1232,7 +1252,7 @@ class Column(
|
||||
//
|
||||
private fun updateRelation(
|
||||
client : TootApiClient,
|
||||
list : ArrayList<Any>?,
|
||||
list : ArrayList<TimelineItem>?,
|
||||
who : TootAccount?
|
||||
) {
|
||||
if(access_info.isPseudo) return
|
||||
@ -1268,7 +1288,7 @@ class Column(
|
||||
|
||||
duplicate_map.clear()
|
||||
list_data.clear()
|
||||
fireShowContent()
|
||||
fireShowContent(reason="loading start",reset=true)
|
||||
|
||||
val task = @SuppressLint("StaticFieldLeak")
|
||||
object : AsyncTask<Void, Void, TootApiResult?>() {
|
||||
@ -1276,9 +1296,9 @@ class Column(
|
||||
|
||||
internal var instance_tmp : TootInstance? = null
|
||||
|
||||
internal var list_pinned : ArrayList<Any>? = null
|
||||
internal var list_pinned : ArrayList<TimelineItem>? = null
|
||||
|
||||
internal var list_tmp : ArrayList<Any>? = null
|
||||
internal var list_tmp : ArrayList<TimelineItem>? = null
|
||||
|
||||
internal fun getInstanceInformation(
|
||||
client : TootApiClient,
|
||||
@ -1498,7 +1518,7 @@ class Column(
|
||||
Utils.runOnMainThread {
|
||||
if(isCancelled) return@runOnMainThread
|
||||
task_progress = s
|
||||
fireShowContent()
|
||||
fireShowContent(reason="loading progress",changeList = ArrayList())
|
||||
}
|
||||
}
|
||||
})
|
||||
@ -1782,6 +1802,7 @@ class Column(
|
||||
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) {
|
||||
@ -1796,14 +1817,10 @@ class Column(
|
||||
|
||||
resumeStreaming(false)
|
||||
}
|
||||
fireShowContent()
|
||||
fireShowContent(reason="loading updated",reset=true)
|
||||
|
||||
// 初期ロードの直後は先頭に移動する
|
||||
try {
|
||||
viewHolder?.listLayoutManager?.scrollToPositionWithOffset(0, 0)
|
||||
} catch(ignored : Throwable) {
|
||||
}
|
||||
|
||||
viewHolder?.scrollToTop()
|
||||
}
|
||||
}
|
||||
this.last_task = task
|
||||
@ -1915,7 +1932,7 @@ class Column(
|
||||
object : AsyncTask<Void, Void, TootApiResult?>() {
|
||||
internal var parser = TootParser(context, access_info, highlightTrie = highlight_trie)
|
||||
|
||||
internal var list_tmp : ArrayList<Any>? = null
|
||||
internal var list_tmp : ArrayList<TimelineItem>? = null
|
||||
|
||||
internal fun getAccountList(
|
||||
client : TootApiClient,
|
||||
@ -2419,7 +2436,7 @@ class Column(
|
||||
Utils.runOnMainThread {
|
||||
if(isCancelled) return@runOnMainThread
|
||||
task_progress = s
|
||||
fireShowContent()
|
||||
fireShowContent(reason="refresh progress",changeList = ArrayList())
|
||||
}
|
||||
}
|
||||
})
|
||||
@ -2593,20 +2610,13 @@ class Column(
|
||||
val error = result.error
|
||||
if(error != null) {
|
||||
mRefreshLoadingError = error
|
||||
fireShowContent()
|
||||
return
|
||||
}
|
||||
|
||||
val list_tmp = this.list_tmp
|
||||
if(list_tmp == null || list_tmp.isEmpty()) {
|
||||
fireShowContent()
|
||||
fireShowContent(reason="refresh error",changeList = ArrayList())
|
||||
return
|
||||
}
|
||||
|
||||
val list_new = duplicate_map.filterDuplicate(list_tmp)
|
||||
|
||||
if(list_new.isEmpty()) {
|
||||
fireShowContent()
|
||||
if(list_new.isEmpty() ) {
|
||||
fireShowContent(reason="refresh list_new is empty",changeList = ArrayList())
|
||||
return
|
||||
}
|
||||
|
||||
@ -2617,9 +2627,12 @@ class Column(
|
||||
sp = holder.scrollPosition
|
||||
}
|
||||
|
||||
val added = list_new.size
|
||||
|
||||
if(bBottom) {
|
||||
val changeList = listOf(AdapterChange(AdapterChangeType.RangeInsert,list_data.size,added))
|
||||
list_data.addAll(list_new)
|
||||
fireShowContent()
|
||||
fireShowContent(reason="refresh updated bottom",changeList = changeList)
|
||||
|
||||
// 新着が少しだけ見えるようにスクロール位置を移動する
|
||||
if(sp != null) {
|
||||
@ -2637,23 +2650,19 @@ class Column(
|
||||
}
|
||||
}
|
||||
|
||||
// 投稿後のリフレッシュなら当該投稿の位置を探す
|
||||
var status_index = - 1
|
||||
var i = 0
|
||||
val ie = list_new.size
|
||||
while(i < ie) {
|
||||
for( i in 0 until added){
|
||||
val o = list_new[i]
|
||||
if(o is TootStatus) {
|
||||
if(o.id == posted_status_id) {
|
||||
status_index = i
|
||||
break
|
||||
}
|
||||
if(o is TootStatus && o.id == posted_status_id) {
|
||||
status_index = i
|
||||
break
|
||||
}
|
||||
++ i
|
||||
}
|
||||
|
||||
val added = list_new.size
|
||||
val changeList = listOf(AdapterChange(AdapterChangeType.RangeInsert,0,added))
|
||||
list_data.addAll(0, list_new)
|
||||
fireShowContent()
|
||||
fireShowContent(reason="refresh updated head",changeList=changeList)
|
||||
|
||||
if(status_index >= 0 && refresh_after_toot == Pref.RAT_REFRESH_SCROLL) {
|
||||
// 投稿後にその投稿にスクロールする
|
||||
@ -2714,7 +2723,7 @@ class Column(
|
||||
object : AsyncTask<Void, Void, TootApiResult?>() {
|
||||
internal var max_id = gap.max_id
|
||||
internal val since_id = gap.since_id
|
||||
internal var list_tmp : ArrayList<Any>? = null
|
||||
internal var list_tmp : ArrayList<TimelineItem>? = null
|
||||
|
||||
internal var parser = TootParser(context, access_info, highlightTrie = highlight_trie)
|
||||
|
||||
@ -2940,7 +2949,7 @@ class Column(
|
||||
Utils.runOnMainThread {
|
||||
if(isCancelled) return@runOnMainThread
|
||||
task_progress = s
|
||||
fireShowContent()
|
||||
fireShowContent(reason="gap progress",changeList = ArrayList())
|
||||
}
|
||||
}
|
||||
})
|
||||
@ -3035,23 +3044,26 @@ class Column(
|
||||
val error = result.error
|
||||
if(error != null) {
|
||||
mRefreshLoadingError = error
|
||||
fireShowContent()
|
||||
fireShowContent(reason="gap error",changeList = ArrayList())
|
||||
return
|
||||
}
|
||||
|
||||
val position = list_data.indexOf(gap)
|
||||
if(position == - 1) {
|
||||
log.d("gap not found..")
|
||||
fireShowContent(reason="gap not found",changeList = ArrayList())
|
||||
return
|
||||
}
|
||||
|
||||
val list_tmp = this.list_tmp
|
||||
if(list_tmp == null) {
|
||||
fireShowContent()
|
||||
fireShowContent(reason="gap list_tmp is null",changeList = ArrayList())
|
||||
return
|
||||
}
|
||||
|
||||
// 0個でもギャップを消すために以下の処理を続ける
|
||||
|
||||
val position = list_data.indexOf(gap)
|
||||
if(position == - 1) {
|
||||
log.d("gap is not found..")
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
val list_new = duplicate_map.filterDuplicate(list_tmp)
|
||||
|
||||
// idx番目の要素がListViewのtopから何ピクセル下にあるか
|
||||
@ -3074,7 +3086,13 @@ class Column(
|
||||
val added = list_new.size // may 0
|
||||
list_data.removeAt(position)
|
||||
list_data.addAll(position, list_new)
|
||||
fireShowContent()
|
||||
|
||||
val changeList = ArrayList<AdapterChange>()
|
||||
changeList.add(AdapterChange( AdapterChangeType.RangeRemove,position))
|
||||
if( added > 0 ){
|
||||
changeList.add(AdapterChange(AdapterChangeType.RangeInsert,position,added))
|
||||
}
|
||||
fireShowContent(reason="gap updated",changeList = changeList)
|
||||
|
||||
if(holder != null) {
|
||||
if(restore_idx >= 0) {
|
||||
@ -3192,13 +3210,12 @@ class Column(
|
||||
&& ! Pref.bpDontRefreshOnResume(App1.getAppState(context).pref)
|
||||
&& ! dont_auto_refresh
|
||||
) {
|
||||
|
||||
// リフレッシュしてからストリーミング開始
|
||||
log.d("onStart: start auto refresh.")
|
||||
startRefresh(true, false, - 1L, - 1)
|
||||
} else if(isSearchColumn) {
|
||||
// 検索カラムはリフレッシュもストリーミングもないが、表示開始のタイミングでリストの再描画を行いたい
|
||||
fireShowContent()
|
||||
fireShowContent(reason="Column onStart isSearchColumn",reset=true)
|
||||
} else {
|
||||
// ギャップつきでストリーミング開始
|
||||
log.d("onStart: start streaming with gap.")
|
||||
@ -3322,28 +3339,29 @@ class Column(
|
||||
|
||||
private val onStreamingMessage = fun(event_type : String, item : Any?) {
|
||||
if(is_dispose.get()) return
|
||||
if("delete" == event_type) {
|
||||
if(item is Long) {
|
||||
removeStatus(access_info, item)
|
||||
|
||||
if(item is Long) {
|
||||
if("delete" == event_type) {
|
||||
removeStatus(access_info, item )
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if(item is TootNotification) {
|
||||
if(column_type != TYPE_NOTIFICATIONS) return
|
||||
if(isFiltered(item)) return
|
||||
} else if(item is TootStatus) {
|
||||
if(column_type == TYPE_NOTIFICATIONS) return
|
||||
if(column_type == TYPE_LOCAL && item.account.acct.indexOf('@') != - 1) return
|
||||
if(isFiltered(item)) return
|
||||
|
||||
if(this.enable_speech) {
|
||||
App1.getAppState(context).addSpeech(item.reblog ?: item)
|
||||
} else if(item is TimelineItem) {
|
||||
if(item is TootNotification) {
|
||||
if(column_type != TYPE_NOTIFICATIONS) return
|
||||
if(isFiltered(item)) return
|
||||
} else if(item is TootStatus) {
|
||||
if(column_type == TYPE_NOTIFICATIONS) return
|
||||
if(column_type == TYPE_LOCAL && item.account.acct.indexOf('@') != - 1) return
|
||||
if(isFiltered(item)) return
|
||||
|
||||
if(this.enable_speech) {
|
||||
App1.getAppState(context).addSpeech(item.reblog ?: item)
|
||||
}
|
||||
}
|
||||
stream_data_queue.addFirst(item)
|
||||
mergeStreamingMessage.run()
|
||||
}
|
||||
|
||||
stream_data_queue.addFirst(item)
|
||||
mergeStreamingMessage.run()
|
||||
}
|
||||
|
||||
private val mergeStreamingMessage = object : Runnable {
|
||||
@ -3446,21 +3464,26 @@ class Column(
|
||||
}
|
||||
}
|
||||
|
||||
list_data.addAll(0, list_new)
|
||||
fireShowContent()
|
||||
val added = list_new.size
|
||||
val changeList = listOf(AdapterChange(AdapterChangeType.RangeInsert,0,added))
|
||||
list_data.addAll(0, list_new)
|
||||
fireShowContent(reason="mergeStreamingMessage",changeList = changeList)
|
||||
|
||||
if(holder != null) {
|
||||
if(holder_sp == null) {
|
||||
// スクロール位置が先頭なら先頭のまま
|
||||
// スクロール位置が先頭なら先頭にする
|
||||
log.d("mergeStreamingMessage: has VH. missing scroll position.")
|
||||
viewHolder?.scrollToTop()
|
||||
|
||||
|
||||
} else if(holder_sp.adapterIndex == 0 && holder_sp.offset == 0) {
|
||||
// スクロール位置が先頭なら先頭のまま
|
||||
// スクロール位置が先頭なら先頭にする
|
||||
log.d(
|
||||
"mergeStreamingMessage: has VH. keep head. pos=%s,offset=%s"
|
||||
, holder_sp.adapterIndex
|
||||
, holder_sp.offset
|
||||
)
|
||||
holder.setScrollPosition(ScrollPosition(0,0))
|
||||
} else if(restore_idx < - 1) {
|
||||
// 可視範囲の検出に失敗
|
||||
log.d("mergeStreamingMessage: has VH. can't find visible range.")
|
||||
@ -3481,4 +3504,5 @@ class Column(
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -195,6 +195,12 @@ class ColumnViewHolder(
|
||||
if(Pref.bpShareViewPool(activity.pref)) {
|
||||
listView.recycledViewPool = activity.viewPool
|
||||
}
|
||||
listView.itemAnimator = null
|
||||
//
|
||||
// val animator = listView.itemAnimator
|
||||
// if( animator is DefaultItemAnimator){
|
||||
// animator.supportsChangeAnimations = false
|
||||
// }
|
||||
|
||||
btnSearch = root.findViewById(R.id.btnSearch)
|
||||
etSearch = root.findViewById(R.id.etSearch)
|
||||
@ -483,7 +489,7 @@ class ColumnViewHolder(
|
||||
|
||||
showColumnColor()
|
||||
|
||||
showContent()
|
||||
showContent(reason="onPageCreate",reset=true)
|
||||
} finally {
|
||||
loading_busy = false
|
||||
}
|
||||
@ -738,7 +744,7 @@ class ColumnViewHolder(
|
||||
R.id.cbHideMediaDefault -> {
|
||||
column.hide_media_default = isChecked
|
||||
activity.app_state.saveColumnList()
|
||||
column.fireShowContent()
|
||||
column.fireShowContent(reason="HideMediaDefault in ColumnSetting",reset=true)
|
||||
}
|
||||
|
||||
R.id.cbEnableSpeech -> {
|
||||
@ -782,7 +788,7 @@ class ColumnViewHolder(
|
||||
|
||||
R.id.llColumnHeader -> {
|
||||
if(status_adapter.itemCount > 0) {
|
||||
listLayoutManager.scrollToPositionWithOffset(0, 0)
|
||||
scrollToTop()
|
||||
}
|
||||
}
|
||||
|
||||
@ -827,6 +833,17 @@ class ColumnViewHolder(
|
||||
btnColumnClose.alpha = if(dont_close) 0.3f else 1f
|
||||
}
|
||||
|
||||
// 相対時刻を更新する
|
||||
fun updateRelativeTime() = rebindAdapterItems()
|
||||
|
||||
fun rebindAdapterItems() {
|
||||
for( childIndex in 0 until listView.childCount){
|
||||
val adapterIndex = listView.getChildAdapterPosition(listView.getChildAt(childIndex))
|
||||
if( adapterIndex == RecyclerView.NO_POSITION) continue
|
||||
status_adapter?.notifyItemChanged( adapterIndex )
|
||||
}
|
||||
}
|
||||
|
||||
// カラムヘッダなど、負荷が低い部分の表示更新
|
||||
fun showColumnHeader() {
|
||||
val column = this.column ?: return
|
||||
@ -861,12 +878,15 @@ class ColumnViewHolder(
|
||||
|
||||
}
|
||||
|
||||
fun showContent() {
|
||||
|
||||
internal fun showContent(
|
||||
reason:String,
|
||||
changeList : List<AdapterChange>? = null ,
|
||||
reset : Boolean = false
|
||||
) {
|
||||
// クラッシュレポートにadapterとリストデータの状態不整合が多かったので、
|
||||
// とりあえずリストデータ変更の通知だけは最優先で行っておく
|
||||
try {
|
||||
status_adapter?.notifyDataSetChanged()
|
||||
status_adapter?.notifyChange(reason,changeList,reset)
|
||||
} catch(ex : Throwable) {
|
||||
log.trace(ex)
|
||||
}
|
||||
@ -920,7 +940,7 @@ class ColumnViewHolder(
|
||||
}
|
||||
|
||||
// 表示状態が変わった後にもう一度呼び出す必要があるらしい。。。
|
||||
status_adapter.notifyDataSetChanged()
|
||||
// 試しにやめてみる status_adapter.notifyChange()
|
||||
|
||||
proc_restoreScrollPosition.run()
|
||||
}
|
||||
@ -961,7 +981,7 @@ class ColumnViewHolder(
|
||||
}
|
||||
}
|
||||
|
||||
fun setScrollPosition(sp : ScrollPosition, deltaDp : Float) {
|
||||
fun setScrollPosition(sp : ScrollPosition, deltaDp : Float =0f) {
|
||||
val last_adapter = listView.adapter
|
||||
if(column == null || last_adapter == null) return
|
||||
|
||||
@ -1074,4 +1094,12 @@ class ColumnViewHolder(
|
||||
?: throw IndexOutOfBoundsException()
|
||||
|
||||
}
|
||||
|
||||
fun scrollToTop() {
|
||||
try {
|
||||
listLayoutManager.scrollToPositionWithOffset(0, 0)
|
||||
} catch(ignored : Throwable) {
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,10 +1,13 @@
|
||||
package jp.juggler.subwaytooter
|
||||
|
||||
import android.os.Handler
|
||||
import android.os.SystemClock
|
||||
import android.support.v7.util.DiffUtil
|
||||
import android.support.v7.util.ListUpdateCallback
|
||||
import android.support.v7.widget.RecyclerView
|
||||
import android.view.ViewGroup
|
||||
import jp.juggler.subwaytooter.api.entity.TootAccount
|
||||
import jp.juggler.subwaytooter.api.entity.TootNotification
|
||||
import jp.juggler.subwaytooter.api.entity.TootStatus
|
||||
import jp.juggler.subwaytooter.api.entity.TimelineItem
|
||||
import jp.juggler.subwaytooter.util.LogCategory
|
||||
|
||||
internal class ItemListAdapter(
|
||||
private val activity : ActMain,
|
||||
@ -12,40 +15,69 @@ internal class ItemListAdapter(
|
||||
internal val columnVh : ColumnViewHolder,
|
||||
private val bSimpleList : Boolean
|
||||
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
|
||||
|
||||
private val list : List<Any>
|
||||
|
||||
|
||||
companion object {
|
||||
private val log = LogCategory("ItemListAdapter")
|
||||
}
|
||||
|
||||
private inner class DiffCallback(
|
||||
val oldList : List<TimelineItem>,
|
||||
val newList : List<TimelineItem>,
|
||||
val biasListIndex : Int
|
||||
) : DiffUtil.Callback() {
|
||||
|
||||
override fun getOldListSize() : Int {
|
||||
return oldList.size - biasListIndex
|
||||
}
|
||||
|
||||
override fun getNewListSize() : Int {
|
||||
return newList.size - biasListIndex
|
||||
}
|
||||
|
||||
override fun areItemsTheSame(oldAdapterIndex : Int, newAdapterIndex : Int) : Boolean {
|
||||
val oldListIndex = oldAdapterIndex + biasListIndex
|
||||
val newListIndex = newAdapterIndex + biasListIndex
|
||||
|
||||
// header?
|
||||
if(oldListIndex < 0 || newListIndex < 0) return oldListIndex == newListIndex
|
||||
|
||||
// compare object address
|
||||
return oldList[oldListIndex] === newList[newListIndex]
|
||||
}
|
||||
|
||||
override fun areContentsTheSame(oldAdapterIndex : Int, newAdapterIndex : Int) : Boolean {
|
||||
val oldListIndex = oldAdapterIndex + biasListIndex
|
||||
val newListIndex = newAdapterIndex + biasListIndex
|
||||
|
||||
// headerは毎回更新する
|
||||
return ! (oldListIndex < 0 || newListIndex < 0)
|
||||
}
|
||||
}
|
||||
|
||||
private var list : ArrayList<TimelineItem>
|
||||
private val handler : Handler
|
||||
|
||||
init {
|
||||
this.list = column.list_data
|
||||
setHasStableIds(false)
|
||||
this.list = ArrayList()
|
||||
this.handler = activity.handler
|
||||
setHasStableIds(true)
|
||||
}
|
||||
|
||||
override fun getItemCount() : Int {
|
||||
return column.toAdapterIndex(column.list_data.size)
|
||||
}
|
||||
|
||||
private fun getItemIdForListIndex(position : Int):Long{
|
||||
val o = list[position]
|
||||
return when(o){
|
||||
is TootAccount -> o.id
|
||||
is TootStatus -> o.id
|
||||
is TootNotification -> o.id
|
||||
else-> 0L
|
||||
}
|
||||
}
|
||||
|
||||
override fun getItemId(position : Int) : Long {
|
||||
val headerType = column.headerType
|
||||
if( headerType != null){
|
||||
if(position==0) return 0
|
||||
return getItemIdForListIndex(position-1)
|
||||
return try {
|
||||
list[column.toListIndex(position)].listViewItemId
|
||||
} catch(ignored : Throwable) {
|
||||
0L
|
||||
}
|
||||
return getItemIdForListIndex(position)
|
||||
}
|
||||
|
||||
override fun getItemViewType(position : Int) : Int {
|
||||
val headerType = column.headerType
|
||||
if( headerType == null || position>0 ) return 0
|
||||
if(headerType == null || position > 0) return 0
|
||||
return headerType.viewType
|
||||
}
|
||||
|
||||
@ -56,41 +88,46 @@ internal class ItemListAdapter(
|
||||
holder.viewRoot.tag = holder
|
||||
return ViewHolderItem(holder)
|
||||
}
|
||||
|
||||
Column.HeaderType.Profile.viewType -> {
|
||||
val viewRoot = activity.layoutInflater.inflate(R.layout.lv_header_profile, parent, false)
|
||||
val holder = ViewHolderHeaderProfile(activity,viewRoot)
|
||||
val viewRoot =
|
||||
activity.layoutInflater.inflate(R.layout.lv_header_profile, parent, false)
|
||||
val holder = ViewHolderHeaderProfile(activity, viewRoot)
|
||||
viewRoot.tag = holder
|
||||
return holder
|
||||
}
|
||||
|
||||
Column.HeaderType.Search.viewType -> {
|
||||
val viewRoot = activity.layoutInflater.inflate(R.layout.lv_header_search_desc, parent, false)
|
||||
val holder = ViewHolderHeaderSearch(activity,viewRoot)
|
||||
val viewRoot =
|
||||
activity.layoutInflater.inflate(R.layout.lv_header_search_desc, parent, false)
|
||||
val holder = ViewHolderHeaderSearch(activity, viewRoot)
|
||||
viewRoot.tag = holder
|
||||
return holder
|
||||
}
|
||||
|
||||
Column.HeaderType.Instance.viewType -> {
|
||||
val viewRoot = activity.layoutInflater.inflate(R.layout.lv_header_instance, parent, false)
|
||||
val holder = ViewHolderHeaderInstance(activity,viewRoot)
|
||||
val viewRoot =
|
||||
activity.layoutInflater.inflate(R.layout.lv_header_instance, parent, false)
|
||||
val holder = ViewHolderHeaderInstance(activity, viewRoot)
|
||||
viewRoot.tag = holder
|
||||
return holder
|
||||
}
|
||||
|
||||
else -> throw RuntimeException("unknown viewType: $viewType")
|
||||
}
|
||||
}
|
||||
|
||||
fun findHeaderViewHolder(listView:RecyclerView): ViewHolderHeaderBase?{
|
||||
return when(column.headerType){
|
||||
null-> null
|
||||
else-> listView.findViewHolderForAdapterPosition(0) as? ViewHolderHeaderBase
|
||||
fun findHeaderViewHolder(listView : RecyclerView) : ViewHolderHeaderBase? {
|
||||
return when(column.headerType) {
|
||||
null -> null
|
||||
else -> listView.findViewHolderForAdapterPosition(0) as? ViewHolderHeaderBase
|
||||
}
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder : RecyclerView.ViewHolder, positionArg : Int) {
|
||||
val headerType = column.headerType
|
||||
override fun onBindViewHolder(holder : RecyclerView.ViewHolder, adapterIndex : Int) {
|
||||
if(holder is ViewHolderItem) {
|
||||
val position = if(headerType != null) positionArg - 1 else positionArg
|
||||
val o = if(position >= 0 && position < list.size) list[position] else null
|
||||
holder.ivh.bind(this, column, bSimpleList, o)
|
||||
val listIndex = column.toListIndex(adapterIndex)
|
||||
holder.ivh.bind(this, column, bSimpleList, list[listIndex])
|
||||
} else if(holder is ViewHolderHeaderBase) {
|
||||
holder.bindData(column)
|
||||
}
|
||||
@ -104,45 +141,93 @@ internal class ItemListAdapter(
|
||||
}
|
||||
}
|
||||
|
||||
// override fun getViewTypeCount() : Int {
|
||||
// return if(header != null) 2 else 1
|
||||
// }
|
||||
|
||||
fun notifyChange(
|
||||
reason : String,
|
||||
changeList : List<AdapterChange>? = null,
|
||||
reset : Boolean = false
|
||||
) {
|
||||
|
||||
val time_start = SystemClock.elapsedRealtime()
|
||||
|
||||
// カラムから最新データをコピーする
|
||||
val new_list = ArrayList<TimelineItem>()
|
||||
new_list.ensureCapacity(column.list_data.size)
|
||||
new_list.addAll(column.list_data)
|
||||
|
||||
when {
|
||||
// 変更リストが指定された場合はヘッダ部分とリスト要素を通知する
|
||||
changeList != null -> {
|
||||
|
||||
log.d("notifyChange: changeList=${changeList.size},reason=$reason")
|
||||
|
||||
// override fun getItem(positionArg : Int) : Any? {
|
||||
// var position = positionArg
|
||||
// if(header != null) {
|
||||
// if(position == 0) return header
|
||||
// -- position
|
||||
// }
|
||||
// return if(position >= 0 && position < column.list_data.size) list[position] else null
|
||||
// }
|
||||
this.list = new_list
|
||||
|
||||
// override fun hasStableIds():Boolean = false
|
||||
|
||||
|
||||
// override fun getView(positionArg : Int, viewOld : View?, parent : ViewGroup) : View {
|
||||
// var position = positionArg
|
||||
// val header = this.header
|
||||
// if(header != null) {
|
||||
// if(position == 0) return header.viewRoot
|
||||
// -- position
|
||||
// }
|
||||
//
|
||||
// val o = if(position >= 0 && position < list.size) list[position] else null
|
||||
//
|
||||
// val holder : ItemViewHolder
|
||||
// val view : View
|
||||
// if(viewOld == null) {
|
||||
//
|
||||
// } else {
|
||||
// view = viewOld
|
||||
// holder = view.tag as ItemViewHolder
|
||||
// }
|
||||
// holder.bind(o)
|
||||
// return view
|
||||
// }
|
||||
// ヘッダは毎回更新する
|
||||
// (ヘッダだけ更新するためにカラのchangeListが渡される)
|
||||
if(column.headerType != null){
|
||||
notifyItemRangeChanged(0, 1)
|
||||
}
|
||||
|
||||
// 変更リストを順番に通知する
|
||||
for(c in changeList) {
|
||||
val adapterIndex = column.toAdapterIndex(c.listIndex)
|
||||
log.d("notifyChange: ChangeType=${c.type} pos=$adapterIndex,count=${c.count}")
|
||||
when(c.type) {
|
||||
AdapterChangeType.RangeInsert -> notifyItemRangeInserted(adapterIndex, c.count)
|
||||
AdapterChangeType.RangeRemove -> notifyItemRangeRemoved(adapterIndex, c.count)
|
||||
AdapterChangeType.RangeChange -> notifyItemRangeChanged(adapterIndex, c.count)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
reset -> {
|
||||
log.d("notifyChange: DataSetChanged! reason=$reason")
|
||||
this.list = new_list
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
|
||||
else -> {
|
||||
|
||||
val diffResult = DiffUtil.calculateDiff(
|
||||
DiffCallback(
|
||||
oldList = this.list, // 比較対象の古いデータ
|
||||
newList = new_list,
|
||||
biasListIndex = column.toListIndex(0)
|
||||
),
|
||||
false // ログを見た感じ、移動なんてなかった
|
||||
)
|
||||
val time = SystemClock.elapsedRealtime() - time_start
|
||||
log.d("notifyChange: size=${new_list.size},time=${time}ms,reason=$reason")
|
||||
|
||||
this.list = new_list
|
||||
diffResult.dispatchUpdatesTo(object : ListUpdateCallback {
|
||||
|
||||
override fun onInserted(position : Int, count : Int) {
|
||||
log.d("notifyChange: notifyItemRangeInserted pos=$position,count=$count")
|
||||
notifyItemRangeInserted(position, count)
|
||||
}
|
||||
|
||||
override fun onRemoved(position : Int, count : Int) {
|
||||
log.d("notifyChange: notifyItemRangeRemoved pos=$position,count=$count")
|
||||
notifyItemRangeRemoved(position, count)
|
||||
}
|
||||
|
||||
override fun onChanged(position : Int, count : Int, payload : Any?) {
|
||||
log.d("notifyChange: notifyItemRangeChanged pos=$position,count=$count")
|
||||
notifyItemRangeChanged(position, count, payload)
|
||||
}
|
||||
|
||||
override fun onMoved(fromPosition : Int, toPosition : Int) {
|
||||
log.d("notifyChange: notifyItemMoved from=$fromPosition,to=$toPosition")
|
||||
notifyItemMoved(fromPosition, toPosition)
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// diffを取る部分をワーカースレッドで実行したいが、直後にスクロール位置の処理があるので差支えがある…
|
||||
}
|
||||
}
|
@ -120,13 +120,15 @@ internal class ItemViewHolder(
|
||||
|
||||
private var buttons_for_status : StatusButtons? = null
|
||||
|
||||
private var item : Any? = null
|
||||
private var item : TimelineItem? = null
|
||||
|
||||
private var status_showing : TootStatus? = null
|
||||
private var status_account : TootAccount? = null
|
||||
private var boost_account : TootAccount? = null
|
||||
private var follow_account : TootAccount? = null
|
||||
|
||||
private var boost_time :Long = 0L
|
||||
|
||||
private val content_color_default : Int
|
||||
private var acct_color : Int = 0
|
||||
|
||||
@ -213,7 +215,7 @@ internal class ItemViewHolder(
|
||||
|
||||
}
|
||||
|
||||
fun bind(list_adapter : ItemListAdapter, column : Column, bSimpleList : Boolean, item : Any?) {
|
||||
fun bind(list_adapter : ItemListAdapter, column : Column, bSimpleList : Boolean, item : TimelineItem) {
|
||||
this.list_adapter = list_adapter
|
||||
this.column = column
|
||||
this.bSimpleList = bSimpleList
|
||||
@ -293,12 +295,11 @@ internal class ItemViewHolder(
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
this.item = null
|
||||
this.status_showing = null
|
||||
this.status_account = null
|
||||
this.boost_account = null
|
||||
this.follow_account = null
|
||||
this.boost_time = 0L
|
||||
|
||||
llBoosted.visibility = View.GONE
|
||||
llFollow.visibility = View.GONE
|
||||
@ -306,11 +307,9 @@ internal class ItemViewHolder(
|
||||
llSearchTag.visibility = View.GONE
|
||||
llList.visibility = View.GONE
|
||||
llExtra.removeAllViews()
|
||||
|
||||
if(item == null) return
|
||||
|
||||
|
||||
|
||||
var c : Int
|
||||
|
||||
c = if(column.content_color != 0) column.content_color else content_color_default
|
||||
tvBoosted.setTextColor(c)
|
||||
tvFollowerName.setTextColor(c)
|
||||
@ -331,10 +330,11 @@ internal class ItemViewHolder(
|
||||
// tvBoostedAcct.setTextColor( c );
|
||||
// tvFollowerAcct.setTextColor( c );
|
||||
// tvAcct.setTextColor( c );
|
||||
|
||||
|
||||
this.item = item
|
||||
when(item) {
|
||||
is String -> showSearchTag(item)
|
||||
is TootTag -> showSearchTag(item)
|
||||
is TootAccount -> showAccount(item)
|
||||
is TootNotification -> showNotification(item)
|
||||
is TootGap -> showGap()
|
||||
@ -461,9 +461,9 @@ internal class ItemViewHolder(
|
||||
btnSearchTag.text = domain_block.domain
|
||||
}
|
||||
|
||||
private fun showSearchTag(tag : String) {
|
||||
private fun showSearchTag(tag : TootTag) {
|
||||
llSearchTag.visibility = View.VISIBLE
|
||||
btnSearchTag.text = "#" + tag
|
||||
btnSearchTag.text = "#" + tag.name
|
||||
}
|
||||
|
||||
private fun showGap() {
|
||||
@ -473,6 +473,7 @@ internal class ItemViewHolder(
|
||||
|
||||
private fun showBoost(who : TootAccount, time : Long, icon_attr_id : Int, text : Spannable) {
|
||||
boost_account = who
|
||||
boost_time = time
|
||||
llBoosted.visibility = View.VISIBLE
|
||||
ivBoosted.setImageResource(Styler.getAttributeResourceId(activity, icon_attr_id))
|
||||
tvBoostedTime.text = TootStatus.formatTime(tvBoostedTime.context, time, true)
|
||||
@ -695,6 +696,18 @@ internal class ItemViewHolder(
|
||||
tvTime.text = sb
|
||||
}
|
||||
|
||||
fun updateRelativeTime() {
|
||||
val boost_time = this.boost_time
|
||||
if( boost_time != 0L ){
|
||||
tvBoostedTime.text = TootStatus.formatTime(tvBoostedTime.context, boost_time, true)
|
||||
}
|
||||
val status_showing = this.status_showing
|
||||
if( status_showing != null ){
|
||||
showStatusTime(activity, status_showing)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun setAcct(tv : TextView, acctLong : String, acctShort : String?) {
|
||||
|
||||
val ac = AcctColor.load(acctLong)
|
||||
@ -819,7 +832,10 @@ internal class ItemViewHolder(
|
||||
btnContentWarning -> status_showing?.let { status ->
|
||||
val new_shown = llContents.visibility == View.GONE
|
||||
ContentWarning.save(status, new_shown)
|
||||
list_adapter.notifyDataSetChanged()
|
||||
|
||||
// 1個だけ開閉するのではなく、例えば通知TLにある複数の要素をまとめて開閉するなどある
|
||||
list_adapter.notifyChange(reason="ContentWarning onClick", reset=true)
|
||||
|
||||
}
|
||||
|
||||
ivThumbnail -> status_account?.let { who ->
|
||||
@ -868,13 +884,12 @@ internal class ItemViewHolder(
|
||||
.show()
|
||||
}
|
||||
|
||||
is String -> {
|
||||
// search_tag は#を含まない
|
||||
is TootTag -> {
|
||||
Action_HashTag.timeline(
|
||||
activity,
|
||||
activity.nextPosition(column),
|
||||
access_info,
|
||||
item
|
||||
item.name // #を含まない
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -982,9 +997,9 @@ internal class ItemViewHolder(
|
||||
// .show()
|
||||
// }
|
||||
|
||||
is String -> {
|
||||
is TootTag -> {
|
||||
// search_tag は#を含まない
|
||||
val tagEncoded = Uri.encode(item)
|
||||
val tagEncoded = Uri.encode(item.name)
|
||||
val host = access_info.host
|
||||
val url = "https://$host/tags/$tagEncoded"
|
||||
Action_HashTag.timelineOtherInstance(
|
||||
@ -992,7 +1007,7 @@ internal class ItemViewHolder(
|
||||
pos = activity.nextPosition(column),
|
||||
url = url,
|
||||
host = host,
|
||||
tag_without_sharp = item
|
||||
tag_without_sharp = item.name
|
||||
)
|
||||
}
|
||||
|
||||
@ -1682,6 +1697,7 @@ internal class ItemViewHolder(
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
@ -349,4 +349,11 @@ internal class ViewHolderHeaderProfile(
|
||||
override fun onViewRecycled() {
|
||||
}
|
||||
|
||||
fun updateRelativeTime(){
|
||||
val who = this.who
|
||||
if( who != null ) {
|
||||
tvCreated.text = TootStatus.formatTime(tvCreated.context, who.time_created_at, true)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,13 +1,9 @@
|
||||
package jp.juggler.subwaytooter.api
|
||||
|
||||
import jp.juggler.subwaytooter.api.entity.*
|
||||
import java.util.ArrayList
|
||||
import java.util.HashSet
|
||||
|
||||
import jp.juggler.subwaytooter.api.entity.TootAccount
|
||||
import jp.juggler.subwaytooter.api.entity.TootNotification
|
||||
import jp.juggler.subwaytooter.api.entity.TootReport
|
||||
import jp.juggler.subwaytooter.api.entity.TootStatus
|
||||
|
||||
class DuplicateMap {
|
||||
|
||||
private val set_status_id = HashSet<Long>()
|
||||
@ -24,7 +20,7 @@ class DuplicateMap {
|
||||
set_status_uri.clear()
|
||||
}
|
||||
|
||||
fun isDuplicate(o : Any) : Boolean {
|
||||
fun isDuplicate(o : TimelineItem) : Boolean {
|
||||
|
||||
when(o) {
|
||||
|
||||
@ -69,11 +65,13 @@ class DuplicateMap {
|
||||
return false
|
||||
}
|
||||
|
||||
fun filterDuplicate(src : Collection<Any>) : ArrayList<Any> {
|
||||
val list_new = ArrayList<Any>()
|
||||
for(o in src) {
|
||||
if(isDuplicate(o)) continue
|
||||
list_new.add(o)
|
||||
fun filterDuplicate(src : Collection<TimelineItem>?) : ArrayList<TimelineItem> {
|
||||
val list_new = ArrayList<TimelineItem>()
|
||||
if( src != null ) {
|
||||
for(o in src) {
|
||||
if(isDuplicate(o)) continue
|
||||
list_new.add(o)
|
||||
}
|
||||
}
|
||||
return list_new
|
||||
}
|
||||
|
@ -0,0 +1,14 @@
|
||||
package jp.juggler.subwaytooter.api.entity
|
||||
|
||||
import java.util.concurrent.atomic.AtomicLong
|
||||
|
||||
// カラムに表示する要素全てのベースクラス
|
||||
open class TimelineItem{
|
||||
|
||||
companion object {
|
||||
val idSeed = AtomicLong(3) // ヘッダ用にいくつか空けておく
|
||||
}
|
||||
|
||||
// AdapterView用のIDを採番する
|
||||
val listViewItemId :Long = idSeed.incrementAndGet()
|
||||
}
|
@ -21,7 +21,7 @@ open class TootAccount(
|
||||
accessInfo : LinkClickContext,
|
||||
src : JSONObject,
|
||||
serviceType : ServiceType
|
||||
) {
|
||||
) :TimelineItem(){
|
||||
|
||||
//URL of the user's profile page (can be remote)
|
||||
// https://mastodon.juggler.jp/@tateisu
|
||||
|
@ -6,7 +6,7 @@ import java.util.ArrayList
|
||||
|
||||
class TootDomainBlock(
|
||||
val domain : String
|
||||
) {
|
||||
):TimelineItem() {
|
||||
|
||||
companion object {
|
||||
|
||||
|
@ -3,7 +3,7 @@ package jp.juggler.subwaytooter.api.entity
|
||||
class TootGap(
|
||||
val max_id : String,
|
||||
val since_id : String
|
||||
) {
|
||||
) :TimelineItem(){
|
||||
|
||||
constructor(max_id : Long, since_id : Long) : this(max_id.toString(), since_id.toString())
|
||||
}
|
||||
|
@ -12,7 +12,7 @@ import jp.juggler.subwaytooter.util.Utils
|
||||
class TootList(
|
||||
val id : Long,
|
||||
val title : String?
|
||||
) : Comparable<TootList> {
|
||||
) : TimelineItem(), Comparable<TootList> {
|
||||
|
||||
// タイトルの数字列部分は数字の大小でソートされるようにしたい
|
||||
private val title_for_sort : ArrayList<Any>?
|
||||
|
@ -12,7 +12,7 @@ class TootNotification(
|
||||
private val created_at : String?, // The time the notification was created
|
||||
val account : TootAccount?, // The Account sending the notification to the user
|
||||
val status : TootStatus? // The Status associated with the notification, if applicable
|
||||
) {
|
||||
) :TimelineItem(){
|
||||
|
||||
val time_created_at : Long
|
||||
|
||||
|
@ -7,7 +7,7 @@ import jp.juggler.subwaytooter.util.Utils
|
||||
class TootReport(
|
||||
val id : Long,
|
||||
private val action_taken : String? // The action taken in response to the report
|
||||
) {
|
||||
) :TimelineItem() {
|
||||
|
||||
constructor(src : JSONObject) : this(
|
||||
id = Utils.optLongX(src, "id"),
|
||||
|
@ -5,18 +5,21 @@ import org.json.JSONObject
|
||||
import java.util.ArrayList
|
||||
|
||||
import jp.juggler.subwaytooter.api.TootParser
|
||||
import jp.juggler.subwaytooter.util.Utils
|
||||
|
||||
class TootResults(
|
||||
val accounts : ArrayList<TootAccount>, // An array of matched Accounts
|
||||
val statuses : TootStatus.List, // An array of matched Statuses
|
||||
val hashtags : ArrayList<String> // An array of matched hashtags, as strings
|
||||
val hashtags : ArrayList<TootTag> // An array of matched hashtags
|
||||
) {
|
||||
|
||||
constructor(parser : TootParser, src : JSONObject) : this(
|
||||
accounts = TootAccount.parseList(parser.context, parser.accessInfo, src.optJSONArray("accounts")),
|
||||
accounts = TootAccount.parseList(
|
||||
parser.context,
|
||||
parser.accessInfo,
|
||||
src.optJSONArray("accounts")
|
||||
),
|
||||
statuses = TootStatus.parseList(parser, src.optJSONArray("statuses")),
|
||||
hashtags = Utils.parseStringArray(src.optJSONArray("hashtags"))
|
||||
hashtags = TootTag.parseStringArray(src.optJSONArray("hashtags"))
|
||||
)
|
||||
|
||||
}
|
||||
|
@ -22,7 +22,7 @@ import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
|
||||
@Suppress("MemberVisibilityCanPrivate")
|
||||
class TootStatus(parser : TootParser, src : JSONObject, serviceType : ServiceType) {
|
||||
class TootStatus(parser : TootParser, src : JSONObject, serviceType : ServiceType) :TimelineItem(){
|
||||
|
||||
val json : JSONObject
|
||||
|
||||
|
@ -1,13 +1,37 @@
|
||||
package jp.juggler.subwaytooter.api.entity
|
||||
|
||||
import jp.juggler.subwaytooter.util.Utils
|
||||
import org.json.JSONArray
|
||||
import org.json.JSONObject
|
||||
|
||||
class TootTag(
|
||||
val name : String, // The hashtag, not including the preceding #
|
||||
val url : String // The URL of the hashtag
|
||||
) {
|
||||
// The hashtag, not including the preceding #
|
||||
val name : String,
|
||||
// The URL of the hashtag. may null if generated from TootContext
|
||||
val url : String? =null
|
||||
) :TimelineItem(){
|
||||
|
||||
|
||||
constructor(src : JSONObject):this(
|
||||
name = src.notEmptyOrThrow("name"),
|
||||
url = src.notEmptyOrThrow("url")
|
||||
url = Utils.optStringX(src,"url")
|
||||
)
|
||||
|
||||
companion object {
|
||||
// 検索結果のhashtagリストから生成する
|
||||
fun parseStringArray( array: JSONArray?):ArrayList<TootTag>{
|
||||
val result = ArrayList<TootTag>()
|
||||
if(array != null) {
|
||||
for( i in 0 until array.length() ){
|
||||
val sv = Utils.optStringX(array, i)
|
||||
if( sv?.isNotEmpty() == true ) {
|
||||
result.add(TootTag(name = sv))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -22,7 +22,7 @@ class CustomEmojiCache(internal val context : Context) {
|
||||
|
||||
private val log = LogCategory("CustomEmojiCache")
|
||||
|
||||
internal const val DEBUG = true
|
||||
internal const val DEBUG = false
|
||||
internal const val CACHE_MAX = 512 // 使用中のビットマップは掃除しないので、頻度によってはこれより多くなることもある
|
||||
internal const val ERROR_EXPIRE = 60000L * 10
|
||||
|
||||
@ -243,12 +243,20 @@ class CustomEmojiCache(internal val context : Context) {
|
||||
|
||||
private fun decodeAPNG(data : ByteArray, url : String) : APNGFrames? {
|
||||
try {
|
||||
val frames = APNGFrames.parseAPNG(ByteArrayInputStream(data), 64)
|
||||
if(frames?.hasMultipleFrame == true) return frames
|
||||
if(DEBUG) log.d("parseAPNG returns null or single frame.")
|
||||
// mastodonのstatic_urlが返すPNG画像はAPNGだと透明になってる場合がある。BitmapFactoryでデコードしなおすべき
|
||||
frames?.dispose()
|
||||
|
||||
// PNGヘッダを確認
|
||||
if( data.size >= 8
|
||||
&& (data[0].toInt() and 0xff) == 0x89
|
||||
&& (data[1].toInt() and 0xff) == 0x50
|
||||
){
|
||||
// APNGをデコード
|
||||
val frames = APNGFrames.parseAPNG(ByteArrayInputStream(data), 64)
|
||||
if(frames?.hasMultipleFrame == true) return frames
|
||||
frames?.dispose()
|
||||
|
||||
// mastodonのstatic_urlが返すPNG画像はAPNGだと透明になってる場合がある。BitmapFactoryでデコードしなおすべき
|
||||
if(DEBUG) log.d("parseAPNG returns null or single frame.")
|
||||
}
|
||||
|
||||
// fall thru
|
||||
} catch(ex : Throwable) {
|
||||
if(DEBUG) log.trace(ex)
|
||||
|
@ -454,4 +454,23 @@ class TestKotlinFeature {
|
||||
var e2 = arrayOf( null,1,null,2)
|
||||
|
||||
}
|
||||
|
||||
@Test fun testOutProjectedType(){
|
||||
fun foo( args: Array<out Number>){
|
||||
val sb = StringBuilder()
|
||||
for(s in args){
|
||||
if(sb.isNotEmpty()) sb.append(',')
|
||||
sb
|
||||
.append(s.toString())
|
||||
.append('#')
|
||||
.append( s.javaClass.simpleName)
|
||||
|
||||
}
|
||||
println(sb)
|
||||
println(args.contains(6)) // 禁止されていない。inポジションって何だ…?
|
||||
// arggs[0]=6 //禁止されている
|
||||
}
|
||||
foo( arrayOf(1,2,3))
|
||||
foo( arrayOf(1f,2f,3f))
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user