ダイレクトメッセージカラムを2.6.0の新APIに対応させた

This commit is contained in:
tateisu 2018-10-09 08:20:43 +09:00
parent aed04b8622
commit 28c1db12e8
6 changed files with 755 additions and 39 deletions

View File

@ -70,6 +70,7 @@ class Column(
// ステータスのリストを返すAPI
private const val PATH_HOME = "/api/v1/timelines/home?limit=$READ_LIMIT"
private const val PATH_DIRECT_MESSAGES = "/api/v1/timelines/direct?limit=$READ_LIMIT"
private const val PATH_DIRECT_MESSAGES2 = "/api/v1/conversations?limit=$READ_LIMIT"
private const val PATH_LOCAL = "/api/v1/timelines/public?limit=$READ_LIMIT&local=true"
private const val PATH_TL_FEDERATE = "/api/v1/timelines/public?limit=$READ_LIMIT"
@ -208,7 +209,6 @@ class Column(
internal const val TYPE_FEDERATED_AROUND = 30
internal const val TYPE_ACCOUNT_AROUND = 31
internal const val TAB_STATUS = 0
internal const val TAB_FOLLOWING = 1
internal const val TAB_FOLLOWERS = 2
@ -235,13 +235,13 @@ class Column(
TYPE_LOCAL_AROUND -> context.getString(R.string.ltl_around)
TYPE_FEDERATED_AROUND -> context.getString(R.string.ftl_around)
TYPE_ACCOUNT_AROUND-> context.getString(R.string.account_tl_around)
TYPE_ACCOUNT_AROUND -> context.getString(R.string.account_tl_around)
TYPE_LOCAL -> context.getString(R.string.local_timeline)
TYPE_FEDERATE -> context.getString(R.string.federate_timeline)
TYPE_MISSKEY_HYBRID -> context.getString(R.string.misskey_hybrid_timeline)
TYPE_PROFILE -> context.getString(R.string.profile)
TYPE_FAVOURITES -> context.getString(R.string.favourites)
TYPE_REPORTS -> context.getString(R.string.reports)
@ -279,11 +279,11 @@ class Column(
TYPE_LOCAL_AROUND -> R.attr.btn_local_tl
TYPE_FEDERATED_AROUND -> R.attr.btn_federate_tl
TYPE_ACCOUNT_AROUND -> R.attr.btn_statuses
TYPE_LOCAL -> R.attr.btn_local_tl
TYPE_FEDERATE -> R.attr.btn_federate_tl
TYPE_MISSKEY_HYBRID -> R.attr.ic_share
TYPE_PROFILE -> R.attr.btn_statuses
TYPE_FAVOURITES -> if(SavedAccount.isNicoru(acct)) R.attr.ic_nicoru else R.attr.btn_favourite
TYPE_NOTIFICATIONS -> R.attr.btn_notification
@ -678,7 +678,7 @@ class Column(
: this(app_state, app_state.context, access_info, type, generateColumnId()) {
this.callback_ref = WeakReference(callback)
when(type) {
TYPE_CONVERSATION, TYPE_BOOSTED_BY, TYPE_FAVOURITED_BY, TYPE_LOCAL_AROUND, TYPE_FEDERATED_AROUND ,TYPE_ACCOUNT_AROUND-> status_id =
TYPE_CONVERSATION, TYPE_BOOSTED_BY, TYPE_FAVOURITED_BY, TYPE_LOCAL_AROUND, TYPE_FEDERATED_AROUND, TYPE_ACCOUNT_AROUND -> status_id =
getParamAt(params, 0)
TYPE_PROFILE, TYPE_LIST_TL, TYPE_LIST_MEMBER -> profile_id = getParamAt(params, 0)
TYPE_HASHTAG -> hashtag = getParamAt(params, 0)
@ -732,7 +732,7 @@ class Column(
when(column_type) {
TYPE_CONVERSATION, TYPE_BOOSTED_BY, TYPE_FAVOURITED_BY, TYPE_LOCAL_AROUND, TYPE_FEDERATED_AROUND ,TYPE_ACCOUNT_AROUND-> status_id =
TYPE_CONVERSATION, TYPE_BOOSTED_BY, TYPE_FAVOURITED_BY, TYPE_LOCAL_AROUND, TYPE_FEDERATED_AROUND, TYPE_ACCOUNT_AROUND -> status_id =
when(isMisskey) {
true -> EntityId.mayNull(src.parseString(KEY_STATUS_ID))
else -> EntityId.mayNull(src.parseLong(KEY_STATUS_ID))
@ -802,7 +802,7 @@ class Column(
when(column_type) {
TYPE_CONVERSATION, TYPE_BOOSTED_BY, TYPE_FAVOURITED_BY, TYPE_LOCAL_AROUND, TYPE_FEDERATED_AROUND ,TYPE_ACCOUNT_AROUND->
TYPE_CONVERSATION, TYPE_BOOSTED_BY, TYPE_FAVOURITED_BY, TYPE_LOCAL_AROUND, TYPE_FEDERATED_AROUND, TYPE_ACCOUNT_AROUND ->
dst.put(KEY_STATUS_ID, status_id.toString())
TYPE_PROFILE ->
@ -844,7 +844,7 @@ class Column(
else -> EntityIdLong(getParamAt(params, 0))
}
TYPE_CONVERSATION, TYPE_BOOSTED_BY, TYPE_FAVOURITED_BY, TYPE_LOCAL_AROUND, TYPE_FEDERATED_AROUND ,TYPE_ACCOUNT_AROUND->
TYPE_CONVERSATION, TYPE_BOOSTED_BY, TYPE_FAVOURITED_BY, TYPE_LOCAL_AROUND, TYPE_FEDERATED_AROUND, TYPE_ACCOUNT_AROUND ->
status_id == when(isMisskey) {
true -> EntityIdString(getParamAt(params, 0))
else -> EntityIdLong(getParamAt(params, 0))
@ -897,18 +897,18 @@ class Column(
R.string.conversation_around,
(status_id?.toString() ?: "null")
)
TYPE_LOCAL_AROUND -> context.getString(
R.string.ltl_around_of,
(status_id?.toString() ?: "null")
)
TYPE_FEDERATED_AROUND -> context.getString(
R.string.ftl_around_of,
(status_id?.toString() ?: "null")
)
TYPE_ACCOUNT_AROUND-> context.getString(
TYPE_ACCOUNT_AROUND -> context.getString(
R.string.account_tl_around_of,
(status_id?.toString() ?: "null")
)
@ -1452,6 +1452,19 @@ class Column(
return dst
}
private fun addWithFilterConversationSummary(
dstArg : ArrayList<TimelineItem>?,
src : List<TootConversationSummary>
) : ArrayList<TimelineItem> {
val dst = dstArg ?: ArrayList(src.size)
for(cs in src) {
if(! isFiltered(cs.last_status)) {
dst.add(cs)
}
}
return dst
}
private fun addWithFilterNotification(
dstArg : ArrayList<TimelineItem>?,
src : List<TootNotification>
@ -1732,6 +1745,8 @@ class Column(
env.update(client, parser)
}
private var useConversationSummarys = false
internal fun startLoading() {
cancelLastTask()
@ -1955,6 +1970,163 @@ class Column(
return result
}
fun getConversationSummary(
client : TootApiClient,
path_base : String,
aroundMin : Boolean = false,
aroundMax : Boolean = false,
misskeyParams : JSONObject? = null,
misskeyCustomParser : (parser : TootParser, jsonArray : JSONArray) -> ArrayList<TootConversationSummary> =
{ parser, jsonArray -> parseList(::TootConversationSummary, parser, jsonArray) }
) : TootApiResult? {
val params = misskeyParams ?: makeMisskeyTimelineParameter(parser)
val delimiter = if(- 1 != path_base.indexOf('?')) '&' else '?'
val time_start = SystemClock.elapsedRealtime()
// 初回の取得
val result = when {
isMisskey -> client.request(path_base, params.toPostRequestBuilder())
aroundMin -> client.request("$path_base&min_id=$status_id")
aroundMax -> client.request("$path_base&max_id=$status_id")
else -> client.request(path_base)
}
var jsonArray = result?.jsonArray
if(jsonArray != null) {
var src = misskeyCustomParser(parser, jsonArray !!)
if(list_tmp == null) {
list_tmp = ArrayList(src.size)
}
this.list_tmp = addWithFilterConversationSummary(list_tmp, src)
saveRange(true, true, result, src)
if(aroundMin) {
while(true) {
if(client.isApiCancelled) {
log.d("loading-ConversationSummary: cancelled.")
break
}
if(! isFilterEnabled) {
log.d("loading-ConversationSummary: isFiltered is false.")
break
}
if(idRecent == null) {
log.d("loading-ConversationSummary: idRecent is empty.")
break
}
if((list_tmp?.size ?: 0) >= LOOP_READ_ENOUGH) {
log.d("loading-ConversationSummary: read enough data.")
break
}
if(src.isEmpty()) {
log.d("loading-ConversationSummary: previous response is empty.")
break
}
if(SystemClock.elapsedRealtime() - time_start > LOOP_TIMEOUT) {
log.d("loading-ConversationSummary: timeout.")
break
}
// フィルタなどが有効な場合は2回目以降の取得
val result2 = if(isMisskey) {
idOld?.putMisskeyUntil(params)
client.request(path_base, params.toPostRequestBuilder())
} else {
val path = "$path_base${delimiter}min_id=${idRecent}"
client.request(path)
}
jsonArray = result2?.jsonArray
if(jsonArray == null) {
log.d("loading-ConversationSummary: error or cancelled.")
break
}
src = misskeyCustomParser(parser, jsonArray !!)
addWithFilterConversationSummary(list_tmp, src)
if(! saveRangeStart(result = result2, list = src)) {
log.d("loading-ConversationSummary: missing range info.")
break
}
}
} else {
while(true) {
if(client.isApiCancelled) {
log.d("loading-ConversationSummary: cancelled.")
break
}
if(! isFilterEnabled) {
log.d("loading-ConversationSummary: isFiltered is false.")
break
}
if(idOld == null) {
log.d("loading-ConversationSummary: idOld is empty.")
break
}
if((list_tmp?.size ?: 0) >= LOOP_READ_ENOUGH) {
log.d("loading-ConversationSummary: read enough data.")
break
}
if(src.isEmpty()) {
log.d("loading-ConversationSummary: previous response is empty.")
break
}
if(SystemClock.elapsedRealtime() - time_start > LOOP_TIMEOUT) {
log.d("loading-ConversationSummary: timeout.")
break
}
// フィルタなどが有効な場合は2回目以降の取得
val result2 = if(isMisskey) {
idOld?.putMisskeyUntil(params)
client.request(path_base, params.toPostRequestBuilder())
} else {
val path = "$path_base${delimiter}max_id=${idOld}"
client.request(path)
}
jsonArray = result2?.jsonArray
if(jsonArray == null) {
log.d("loading-ConversationSummary: error or cancelled.")
break
}
src = misskeyCustomParser(parser, jsonArray !!)
addWithFilterConversationSummary(list_tmp, src)
if(! saveRangeEnd(result = result2, list = src)) {
log.d("loading-ConversationSummary: missing range info.")
break
}
}
}
}
return result
}
fun parseAccountList(
client : TootApiClient,
path_base : String,
@ -2189,7 +2361,7 @@ class Column(
client.request(String.format(Locale.JAPAN, PATH_STATUSES, status_id))
val target_status = parser.status(result?.jsonObject) ?: return result
list_tmp = addOne(list_tmp, target_status)
// ↑のトゥートのアカウントのID
profile_id = target_status.account.id
@ -2199,7 +2371,7 @@ class Column(
profile_id
)
if(with_attachment && ! with_highlight) path += "&only_media=1"
idOld = null
idRecent = null
@ -2244,7 +2416,27 @@ class Column(
when(column_type) {
TYPE_DIRECT_MESSAGES -> return getStatuses(client, PATH_DIRECT_MESSAGES)
TYPE_DIRECT_MESSAGES -> {
// try 2.6.0 new API https://github.com/tootsuite/mastodon/pull/8832
val result = getConversationSummary(client, PATH_DIRECT_MESSAGES2)
return when {
// cancelled
result == null -> null
// not error
result.error.isNullOrBlank() -> {
useConversationSummarys = true
result
}
else -> {
// fallback to old api
getStatuses(client, PATH_DIRECT_MESSAGES)
}
}
}
TYPE_LOCAL -> return getStatuses(client, makePublicLocalUrl())
@ -2257,9 +2449,9 @@ class Column(
client,
makePublicFederateUrl()
)
TYPE_ACCOUNT_AROUND->return getAccountAroundStatuses(
TYPE_ACCOUNT_AROUND -> return getAccountAroundStatuses(
client
)
TYPE_MISSKEY_HYBRID -> return getStatuses(client, makeMisskeyHybridTlUrl())
@ -3680,6 +3872,261 @@ class Column(
return firstResult
}
fun getConversationSummaryList(
client : TootApiClient,
path_base : String,
aroundMin : Boolean = false,
misskeyParams : JSONObject? = null,
misskeyCustomParser : (parser : TootParser, jsonArray : JSONArray) -> ArrayList<TootConversationSummary> =
{ parser, jsonArray -> parseList(::TootConversationSummary,parser,jsonArray) }
) : TootApiResult? {
val isMisskey = access_info.isMisskey
val params = misskeyParams ?: makeMisskeyTimelineParameter(parser)
val time_start = SystemClock.elapsedRealtime()
val delimiter = if(- 1 != path_base.indexOf('?')) '&' else '?'
val last_since_id = idRecent
var result = when {
isMisskey -> {
addRangeMisskey(bBottom, params)
client.request(path_base, params.toPostRequestBuilder())
}
aroundMin -> client.request(addRangeMin(path_base))
else -> client.request(addRange(bBottom, path_base))
}
val firstResult = result
val jsonArray = result?.jsonArray
if(jsonArray != null) {
var src = misskeyCustomParser(parser, jsonArray)
saveRange(bBottom, ! bBottom, result, src)
list_tmp = addWithFilterConversationSummary(null, src)
if(! bBottom) {
if(isMisskey) {
// Misskeyの場合はsinceIdを指定しても取得できるのは未読のうち古い範囲に偏る
var bHeadGap = false
while(true) {
if(isCancelled) {
log.d("refresh-ConversationSummary-top: cancelled.")
break
}
// 頭の方を読む時は隙間を減らすため、フィルタの有無に関係なく繰り返しを行う
// 直前のデータが0個なら終了とみなす
if(src.isEmpty()) {
log.d("refresh-ConversationSummary-top: previous size == 0.")
break
}
if((list_tmp?.size ?: 0) >= LOOP_READ_ENOUGH) {
log.d("refresh-ConversationSummary-top: read enough. make gap.")
bHeadGap = true
break
}
if(SystemClock.elapsedRealtime() - time_start > LOOP_TIMEOUT) {
log.d("refresh-ConversationSummary-top: timeout. make gap.")
bHeadGap = true
break
}
idRecent?.putMisskeySince(params)
result =
client.request(path_base, params.toPostRequestBuilder())
val jsonArray2 = result?.jsonArray
if(jsonArray2 == null) {
log.d("refresh-ConversationSummary-top: error or cancelled. make gap.")
bHeadGap = true
break
}
src = misskeyCustomParser(parser, jsonArray2)
saveRange(false, true, result, src)
addWithFilterConversationSummary(list_tmp, src)
}
if(isMisskey && ! bBottom) {
list_tmp?.sortBy { it.getOrderId() }
list_tmp?.reverse()
}
if(! isCancelled
&& list_tmp?.isNotEmpty() == true
&& (bHeadGap || Pref.bpForceGap(context))
) {
addOneFirst(list_tmp, TootGap.mayNull(null, idRecent))
}
} else if(aroundMin) {
while(true) {
saveRangeStart(result, src)
if(isCancelled) {
log.d("refresh-ConversationSummary-aroundMin: cancelled.")
break
}
// 頭の方を読む時は隙間を減らすため、フィルタの有無に関係なく繰り返しを行う
// 直前のデータが0個なら終了とみなすしかなさそう
if(src.isEmpty()) {
log.d("refresh-ConversationSummary-aroundMin: previous size == 0.")
break
}
if((list_tmp?.size ?: 0) >= LOOP_READ_ENOUGH) {
log.d("refresh-ConversationSummary-aroundMin: read enough.")
break
}
if(SystemClock.elapsedRealtime() - time_start > LOOP_TIMEOUT) {
log.d("refresh-ConversationSummary-aroundMin: timeout.")
break
}
val path = "$path_base${delimiter}min_id=$idRecent"
result = client.request(path)
val jsonArray2 = result?.jsonArray
if(jsonArray2 == null) {
log.d("refresh-ConversationSummary-aroundMin: error or cancelled.")
break
}
src = misskeyCustomParser(parser, jsonArray2)
addWithFilterConversationSummary(list_tmp, src)
}
} else {
var bGapAdded = false
var max_id : EntityId? = null
while(true) {
if(isCancelled) {
log.d("refresh-ConversationSummary-top: cancelled.")
break
}
// 頭の方を読む時は隙間を減らすため、フィルタの有無に関係なく繰り返しを行う
// max_id だけを指定した場合、必ずlimit個のデータが帰ってくるとは限らない
// 直前のデータが0個なら終了とみなすしかなさそう
if(src.isEmpty()) {
log.d("refresh-ConversationSummary-top: previous size == 0.")
break
}
max_id = parseRange(result, src).first
if((list_tmp?.size ?: 0) >= LOOP_READ_ENOUGH) {
log.d("refresh-ConversationSummary-top: read enough. make gap.")
// 隙間ができるかもしれない。後ほど手動で試してもらうしかない
addOne(list_tmp, TootGap.mayNull(max_id, last_since_id))
bGapAdded = true
break
}
if(SystemClock.elapsedRealtime() - time_start > LOOP_TIMEOUT) {
log.d("refresh-ConversationSummary-top: timeout. make gap.")
// タイムアウト
// 隙間ができるかもしれない。後ほど手動で試してもらうしかない
addOne(list_tmp, TootGap.mayNull(max_id, last_since_id))
bGapAdded = true
break
}
val path =
"$path_base${delimiter}max_id=$max_id&since_id=$last_since_id"
result = client.request(path)
val jsonArray2 = result?.jsonArray
if(jsonArray2 == null) {
log.d("refresh-ConversationSummary-top: error or cancelled. make gap.")
// エラー
// 隙間ができるかもしれない。後ほど手動で試してもらうしかない
addOne(list_tmp, TootGap.mayNull(max_id, last_since_id))
bGapAdded = true
break
}
src = misskeyCustomParser(parser, jsonArray2)
addWithFilterConversationSummary(list_tmp, src)
}
if(Pref.bpForceGap(context) && ! isCancelled && ! bGapAdded && list_tmp?.isNotEmpty() == true) {
addOne(list_tmp, TootGap.mayNull(max_id, last_since_id))
}
}
} else {
while(true) {
if(isCancelled) {
log.d("refresh-ConversationSummary-bottom: cancelled.")
break
}
// bottomの場合、フィルタなしなら繰り返さない
if(! isFilterEnabled) {
log.d("refresh-ConversationSummary-bottom: isFiltered is false.")
break
}
// max_id だけを指定した場合、必ずlimit個のデータが帰ってくるとは限らない
// 直前のデータが0個なら終了とみなすしかなさそう
if(src.isEmpty()) {
log.d("refresh-ConversationSummary-bottom: previous size == 0.")
break
}
// 十分読んだらそれで終了
if((list_tmp?.size ?: 0) >= LOOP_READ_ENOUGH) {
log.d("refresh-ConversationSummary-bottom: read enough data.")
break
}
if(SystemClock.elapsedRealtime() - time_start > LOOP_TIMEOUT) {
// タイムアウト
log.d("refresh-ConversationSummary-bottom: loop timeout.")
break
}
result = if(isMisskey) {
idOld?.putMisskeyUntil(params)
client.request(path_base, params.toPostRequestBuilder())
} else {
val path = "$path_base${delimiter}max_id=$idOld"
client.request(path)
}
val jsonArray2 = result?.jsonArray
if(jsonArray2 == null) {
log.d("refresh-ConversationSummary-bottom: error or cancelled.")
break
}
src = misskeyCustomParser(parser, jsonArray2)
addWithFilterConversationSummary(list_tmp, src)
if(! saveRangeEnd(result, src)) {
log.d("refresh-ConversationSummary-bottom: saveRangeEnd failed.")
break
}
}
}
}
return firstResult
}
fun getStatusList(
client : TootApiClient,
path_base : String,
@ -3935,6 +4382,7 @@ class Column(
return firstResult
}
var filterUpdated = false
override fun doInBackground(vararg unused : Void) : TootApiResult? {
@ -3965,7 +4413,13 @@ class Column(
return when(column_type) {
TYPE_DIRECT_MESSAGES -> getStatusList(client, PATH_DIRECT_MESSAGES)
TYPE_DIRECT_MESSAGES -> if(useConversationSummarys) {
// try 2.6.0 new API https://github.com/tootsuite/mastodon/pull/8832
getConversationSummaryList(client, PATH_DIRECT_MESSAGES2)
}else {
// fallback to old api
getStatusList(client, PATH_DIRECT_MESSAGES)
}
TYPE_LOCAL -> getStatusList(client, makePublicLocalUrl())
@ -3995,7 +4449,7 @@ class Column(
}
}
TYPE_ACCOUNT_AROUND->{
TYPE_ACCOUNT_AROUND -> {
var s = String.format(
Locale.JAPAN,
PATH_ACCOUNT_STATUSES,
@ -4881,6 +5335,135 @@ class Column(
return result
}
fun getConversationSummaryList(
client : TootApiClient,
path_base : String,
misskeyParams : JSONObject? = null
,
misskeyCustomParser : (parser : TootParser, jsonArray : JSONArray) -> ArrayList<TootConversationSummary> =
{ parser, jsonArray -> parseList(::TootConversationSummary,parser,jsonArray) }
) : TootApiResult? {
val isMisskey = access_info.isMisskey
val params = misskeyParams ?: makeMisskeyTimelineParameter(parser)
val time_start = SystemClock.elapsedRealtime()
val delimiter = if(- 1 != path_base.indexOf('?')) '&' else '?'
list_tmp = ArrayList()
var result : TootApiResult? = null
if(isMisskey) {
var bHeadGap = false
while(true) {
if(isCancelled) {
log.d("gap-statuses: cancelled.")
break
}
if(result != null && SystemClock.elapsedRealtime() - time_start > LOOP_TIMEOUT) {
log.d("gap-statuses: timeout.")
bHeadGap = true
break
}
since_id?.putMisskeySince(params)
val r2 = client.request(path_base, params.toPostRequestBuilder())
val jsonArray = r2?.jsonArray
if(jsonArray == null) {
log.d("gap-statuses: error or cancelled. make gap.")
// 成功データがない場合だけ、今回のエラーを返すようにする
if(result == null) result = r2
bHeadGap = true
break
}
// 成功した場合はそれを返したい
result = r2
val src = misskeyCustomParser(parser, jsonArray)
if(src.isEmpty()) {
// 直前の取得でカラのデータが帰ってきたら終了
log.d("gap-statuses: empty.")
break
}
// 隙間の最新のステータスIDは取得データ末尾のステータスIDである
since_id = parseRange(result, src).second
addWithFilterConversationSummary(list_tmp, src)
}
if(isMisskey) {
list_tmp?.sortBy { it.getOrderId() }
list_tmp?.reverse()
}
if(bHeadGap) {
addOneFirst(list_tmp, TootGap.mayNull(max_id, since_id))
}
} else {
var bLastGap = false
while(true) {
if(isCancelled) {
log.d("gap-statuses: cancelled.")
break
}
if(result != null && SystemClock.elapsedRealtime() - time_start > LOOP_TIMEOUT) {
log.d("gap-statuses: timeout.")
// タイムアウト
bLastGap = true
break
}
val path = "${path_base}${delimiter}max_id=${max_id}&since_id=${since_id}"
val r2 = client.request(path)
val jsonArray = r2?.jsonArray
if(jsonArray == null) {
log.d("gap-statuses: error or cancelled. make gap.")
// 成功データがない場合だけ、今回のエラーを返すようにする
if(result == null) result = r2
bLastGap = true
break
}
// 成功した場合はそれを返したい
result = r2
val src = misskeyCustomParser(parser, jsonArray)
if(src.isEmpty()) {
// 直前の取得でカラのデータが帰ってきたら終了
log.d("gap-statuses: empty.")
break
}
// 隙間の最新のステータスIDは取得データ末尾のステータスIDである
max_id = parseRange(result, src).first
addWithFilterConversationSummary(list_tmp, src)
}
if(bLastGap) {
addOne(list_tmp, TootGap.mayNull(max_id, since_id))
}
}
return result
}
override fun doInBackground(vararg unused : Void) : TootApiResult? {
ctStarted.set(true)
@ -5047,7 +5630,14 @@ class Column(
}
}
TYPE_DIRECT_MESSAGES -> getStatusList(client, PATH_DIRECT_MESSAGES)
TYPE_DIRECT_MESSAGES -> if(useConversationSummarys) {
// try 2.6.0 new API https://github.com/tootsuite/mastodon/pull/8832
getConversationSummaryList(client, PATH_DIRECT_MESSAGES2)
}else {
// fallback to old api
getStatusList(client, PATH_DIRECT_MESSAGES)
}
else -> getStatusList(client, makeHomeTlUrl())
}
@ -5415,14 +6005,20 @@ class Column(
override fun onTimelineItem(item : TimelineItem) {
if(is_dispose.get()) return
if(item is TootNotification) {
if(item is TootConversationSummary) {
if(column_type != TYPE_DIRECT_MESSAGES) return
if(isFiltered(item.last_status)) return
}else 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(useConversationSummarys) return
if(column_type == TYPE_LOCAL && ! isMisskey && item.account.acct.indexOf('@') != - 1) return
if(isFiltered(item)) return
}
stream_data_queue.add(item)
val handler = App1.getAppState(context).handler
@ -5547,6 +6143,8 @@ class Column(
}
}
// 最新のIDをsince_idとして覚える(ソートはしない)
var new_id_max : EntityId? = null
var new_id_min : EntityId? = null
@ -5615,19 +6213,40 @@ class Column(
}
for(o in list_new) {
if(o is TootStatus) {
val highlight_sound = o.highlight_sound
if(highlight_sound != null) {
App1.sound(highlight_sound)
break
val removeSet = HashSet<EntityId>()
loop@ for(o in list_new) {
when(o) {
is TootStatus -> {
val highlight_sound = o.highlight_sound
if(highlight_sound != null) {
App1.sound(highlight_sound)
break@loop
}
}
is TootConversationSummary -> {
removeSet.add( o.getOrderId() )
}
}
}
val added = list_new.size
val changeList = listOf(AdapterChange(AdapterChangeType.RangeInsert, 0, added))
val changeList = ArrayList<AdapterChange>()
if( list_data .isNotEmpty() && removeSet.isNotEmpty() ){
for( i in list_data.size-1 downTo 0 ){
val o = list_data[i]
if( o is TootConversationSummary && removeSet.contains(o.id) ){
changeList.add(AdapterChange(AdapterChangeType.RangeRemove, i, 1))
list_data.removeAt(i)
}
}
}
changeList.add(AdapterChange(AdapterChangeType.RangeInsert, 0, added))
list_data.addAll(0, list_new)
fireShowContent(reason = "mergeStreamingMessage", changeList = changeList)
if(holder != null) {

View File

@ -30,7 +30,7 @@ internal class DlgContextMenu(
private val column : Column,
private val whoRef : TootAccountRef?,
private val status : TootStatus?,
private val notification : TootNotification?
private val notification : TootNotification? = null
) : View.OnClickListener, View.OnLongClickListener {
companion object {

View File

@ -34,7 +34,7 @@ import okhttp3.Request
import okhttp3.RequestBody
import org.jetbrains.anko.*
import org.json.JSONObject
import java.util.ArrayList
import kotlin.collections.ArrayList
import kotlin.collections.HashMap
internal class ItemViewHolder(
@ -346,7 +346,7 @@ internal class ItemViewHolder(
llSearchTag.visibility = View.GONE
llList.visibility = View.GONE
llFollowRequest.visibility = View.GONE
llExtra.removeAllViews()
removeExtraView()
tvMessageHolder.visibility = View.GONE
llTrendTag.visibility = View.GONE
llFilter.visibility = View.GONE
@ -395,7 +395,7 @@ internal class ItemViewHolder(
R.attr.btn_boost,
R.string.renote_to
)
showStatus(activity, item)
showStatus(item)
}
else -> {
@ -427,11 +427,86 @@ internal class ItemViewHolder(
is TootFilter -> showFilter(item)
is TootConversationSummary -> {
showStatus(item.last_status)
showConversationIcons(item.accounts)
}
else -> {
}
}
}
private fun removeExtraView() {
llExtra.scan { v ->
if(v is MyNetworkImageView) {
v.cancelLoading()
}
}
llExtra.removeAllViews()
}
private fun showConversationIcons(accounts : ArrayList<TootAccountRef>) {
if(accounts.isEmpty()) return
// 消えてしまったりちらついたりするので保留
// val density = llExtra.resources.displayMetrics.density
// val wh = (activity.avatarIconSize * 0.75f + 0.5f).toInt()
// val me = (density * 3f + 0.5f).toInt()
// val mt = (density * 3f + 0.5f).toInt()
//
// val llIconBar = FlexboxLayout(activity)
// val boxLp = LinearLayout.LayoutParams(
// LinearLayout.LayoutParams.MATCH_PARENT,
// LinearLayout.LayoutParams.WRAP_CONTENT
// )
// boxLp.topMargin = mt
// llIconBar.layoutParams = boxLp
// llIconBar.flexWrap = FlexWrap.WRAP
// llIconBar.justifyContent = JustifyContent.FLEX_START
// llExtra.addView(llIconBar)
//
// for(whoRef in accounts) {
// val who = whoRef.get()
// val icon = MyNetworkImageView(activity)
// val lp = FlexboxLayout.LayoutParams(wh, wh)
// lp.marginEnd = me
// icon.layoutParams = lp
// icon.contentDescription = who.acct
// icon.scaleType = ImageView.ScaleType.CENTER_CROP
// llIconBar.addView(icon)
//
// // ビュー階層に追加した後にURLをセットする
// icon.setImageUrl(
// activity.pref,
// Styler.calcIconRound(lp),
// access_info.supplyBaseUrl(who.avatar_static),
// access_info.supplyBaseUrl(who.avatar)
// )
// icon.setOnClickListener {
// val pos = activity.nextPosition(column)
// when {
// access_info.isMisskey -> Action_User.profileLocal(
// activity,
// pos,
// access_info,
// who
// )
// access_info.isPseudo -> DlgContextMenu(
// activity,
// column,
// whoRef,
// null,
// null
// ).show()
// else -> Action_User.profileLocal(activity, pos, access_info, who)
// }
// }
//
// }
}
private fun showStatusOrReply(item : TootStatus) {
val reply = item.reply
if(reply != null) {
@ -441,9 +516,9 @@ internal class ItemViewHolder(
R.attr.btn_reply,
R.string.reply_to
)
showStatus(activity, item)
showStatus(item)
} else {
showStatus(activity, item)
showStatus(item)
}
}
@ -479,7 +554,7 @@ internal class ItemViewHolder(
R.attr.btn_boost,
R.string.renote_to
)
showStatus(activity, item)
showStatus(item)
}
}
}
@ -756,7 +831,7 @@ internal class ItemViewHolder(
}
}
private fun showStatus(activity : ActMain, status : TootStatus) {
private fun showStatus(status : TootStatus) {
if(status.filtered) {
showMessageHolder(TootMessageHolder(activity.getString(R.string.filtered)))
@ -1793,7 +1868,7 @@ internal class ItemViewHolder(
lp.topMargin = (0.5f + llExtra.resources.displayMetrics.density * 3f).toInt()
val b = Button(activity)
b.layoutParams = lp
b.setAllCaps(false)
b.isAllCaps = false
val text = if(access_info.isMisskey) {
val sb = SpannableStringBuilder()

View File

@ -0,0 +1,20 @@
package jp.juggler.subwaytooter.api.entity
import jp.juggler.subwaytooter.api.TootParser
import jp.juggler.subwaytooter.util.parseLong
import org.json.JSONObject
class TootConversationSummary(parser:TootParser,src:JSONObject) : TimelineItem(){
val id : EntityId
val accounts: ArrayList<TootAccountRef>
val last_status : TootStatus
init{
this.id = EntityId.mayDefault(src.parseLong("id"))
this.accounts = parser.accountList( src.optJSONArray("accounts"))
this.last_status = parser.status(src.optJSONObject("last_status")) !!
}
override fun getOrderId() =id
}

View File

@ -67,6 +67,8 @@ object TootPayload {
// 2017/8/24 18:37 mastodon.juggler.jpでここを通った
"notification" -> parser.notification(src)
"conversation" -> parseItem(::TootConversationSummary,parser,src)
// ここを通るケースはまだ確認できていない
else -> {
log.e("unknown payload(2). message=%s", parent_text)

View File

@ -116,7 +116,7 @@ class MyNetworkImageView : AppCompatImageView {
return null
}
private fun cancelLoading(defaultDrawable : Drawable?) {
fun cancelLoading(defaultDrawable : Drawable? =null) {
val d = drawable
if(d is Animatable) {