編集履歴カラムの追加。投稿のヘッダに編集済表記を追加。
This commit is contained in:
parent
8bba82b930
commit
0bbb3728bb
|
@ -3,14 +3,12 @@ package jp.juggler.subwaytooter.action
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
import jp.juggler.subwaytooter.*
|
import jp.juggler.subwaytooter.*
|
||||||
|
import jp.juggler.subwaytooter.actmain.addColumn
|
||||||
import jp.juggler.subwaytooter.actmain.reloadAccountSetting
|
import jp.juggler.subwaytooter.actmain.reloadAccountSetting
|
||||||
import jp.juggler.subwaytooter.actmain.showColumnMatchAccount
|
import jp.juggler.subwaytooter.actmain.showColumnMatchAccount
|
||||||
import jp.juggler.subwaytooter.api.*
|
import jp.juggler.subwaytooter.api.*
|
||||||
import jp.juggler.subwaytooter.api.entity.*
|
import jp.juggler.subwaytooter.api.entity.*
|
||||||
import jp.juggler.subwaytooter.column.Column
|
import jp.juggler.subwaytooter.column.*
|
||||||
import jp.juggler.subwaytooter.column.findStatus
|
|
||||||
import jp.juggler.subwaytooter.column.onScheduleDeleted
|
|
||||||
import jp.juggler.subwaytooter.column.onStatusRemoved
|
|
||||||
import jp.juggler.subwaytooter.dialog.ActionsDialog
|
import jp.juggler.subwaytooter.dialog.ActionsDialog
|
||||||
import jp.juggler.subwaytooter.dialog.DlgConfirm
|
import jp.juggler.subwaytooter.dialog.DlgConfirm
|
||||||
import jp.juggler.subwaytooter.dialog.pickAccount
|
import jp.juggler.subwaytooter.dialog.pickAccount
|
||||||
|
@ -642,3 +640,12 @@ fun ActMain.scheduledPostEdit(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// アカウントを選んでタイムラインカラムを追加
|
||||||
|
fun ActMain.openStatusHistory(
|
||||||
|
pos: Int,
|
||||||
|
accessInfo: SavedAccount,
|
||||||
|
status: TootStatus,
|
||||||
|
) {
|
||||||
|
addColumn(pos, accessInfo, ColumnType.STATUS_HISTORY, status.id, status.json)
|
||||||
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ package jp.juggler.subwaytooter.api
|
||||||
|
|
||||||
import jp.juggler.subwaytooter.api.entity.*
|
import jp.juggler.subwaytooter.api.entity.*
|
||||||
import jp.juggler.util.LogCategory
|
import jp.juggler.util.LogCategory
|
||||||
|
import jp.juggler.util.cast
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
class DuplicateMap {
|
class DuplicateMap {
|
||||||
|
@ -67,4 +68,30 @@ class DuplicateMap {
|
||||||
}
|
}
|
||||||
return listNew
|
return listNew
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun isDuplicateByCreatedAt(o: TimelineItem): Boolean {
|
||||||
|
if (o is TootStatus) {
|
||||||
|
val createdAt = EntityId(o.time_created_at.toString())
|
||||||
|
if (createdAt.notDefaultOrConfirming) {
|
||||||
|
if (idSet.contains(createdAt)) return true
|
||||||
|
idSet.add(createdAt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//編集履歴以外のカラムではここを通らない
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
fun filterDuplicateByCreatedAt(src: Collection<TimelineItem>?): ArrayList<TimelineItem> {
|
||||||
|
val listNew = ArrayList<TimelineItem>()
|
||||||
|
if (src != null) {
|
||||||
|
for (o in src) {
|
||||||
|
if (isDuplicateByCreatedAt(o)) {
|
||||||
|
log.d("filterDuplicateByCreatedAt: filtered. ${o.cast<TootStatus>()?.time_created_at}")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
listNew.add(o)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return listNew
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -182,6 +182,9 @@ class TootStatus(parser: TootParser, src: JsonObject) : TimelineItem() {
|
||||||
var isPromoted = false
|
var isPromoted = false
|
||||||
var isFeatured = false
|
var isFeatured = false
|
||||||
|
|
||||||
|
// Mastodon 3.5.0
|
||||||
|
var time_edited_at = 0L
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////
|
||||||
// 以下はentityから取得したデータではなく、アプリ内部で使う
|
// 以下はentityから取得したデータではなく、アプリ内部で使う
|
||||||
|
|
||||||
|
@ -602,6 +605,8 @@ class TootStatus(parser: TootParser, src: JsonObject) : TimelineItem() {
|
||||||
this.favourites_count = src.long("favourites_count")
|
this.favourites_count = src.long("favourites_count")
|
||||||
this.replies_count = src.long("replies_count")
|
this.replies_count = src.long("replies_count")
|
||||||
|
|
||||||
|
this.time_edited_at = parseTime(src.string("edited_at"))
|
||||||
|
|
||||||
this.reactionSet = TootReactionSet.parseFedibird(
|
this.reactionSet = TootReactionSet.parseFedibird(
|
||||||
src.jsonArray("emoji_reactions")
|
src.jsonArray("emoji_reactions")
|
||||||
?: src.jsonObject("pleroma")?.jsonArray("emoji_reactions")
|
?: src.jsonObject("pleroma")?.jsonArray("emoji_reactions")
|
||||||
|
@ -1267,7 +1272,7 @@ class TootStatus(parser: TootParser, src: JsonObject) : TimelineItem() {
|
||||||
// 時刻を解釈してエポック秒(ミリ単位)を返す
|
// 時刻を解釈してエポック秒(ミリ単位)を返す
|
||||||
// 解釈に失敗すると0Lを返す
|
// 解釈に失敗すると0Lを返す
|
||||||
fun parseTime(strTime: String?): Long {
|
fun parseTime(strTime: String?): Long {
|
||||||
if (strTime?.isNotBlank() != true) return 0L
|
if (strTime.isNullOrBlank()) return 0L
|
||||||
|
|
||||||
// last_status_at などでは YYYY-MM-DD になることがある
|
// last_status_at などでは YYYY-MM-DD になることがある
|
||||||
reDate.find(strTime)?.groupValues?.let { gv ->
|
reDate.find(strTime)?.groupValues?.let { gv ->
|
||||||
|
@ -1552,5 +1557,23 @@ class TootStatus(parser: TootParser, src: JsonObject) : TimelineItem() {
|
||||||
|
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val supplyEditHistoryKeys = arrayOf(
|
||||||
|
"id",
|
||||||
|
"uri",
|
||||||
|
"url",
|
||||||
|
"visibility",
|
||||||
|
)
|
||||||
|
|
||||||
|
// 編集履歴のデータはTootStatusとしては不足があるので、srcを元に補う
|
||||||
|
fun supplyEditHistory(array: JsonArray?, src: JsonObject?) {
|
||||||
|
src ?: return
|
||||||
|
array?.objectList()?.forEach {
|
||||||
|
for (key in supplyEditHistoryKeys) {
|
||||||
|
if (it.containsKey(key)) continue
|
||||||
|
it[key] = src[key]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -160,6 +160,7 @@ class Column(
|
||||||
internal var profileTab = ProfileTab.Status
|
internal var profileTab = ProfileTab.Status
|
||||||
|
|
||||||
internal var statusId: EntityId? = null
|
internal var statusId: EntityId? = null
|
||||||
|
internal var originalStatus: JsonObject? = null
|
||||||
|
|
||||||
// プロフカラムではアカウントのID。リストカラムではリストのID
|
// プロフカラムではアカウントのID。リストカラムではリストのID
|
||||||
internal var profileId: EntityId? = null
|
internal var profileId: EntityId? = null
|
||||||
|
|
|
@ -242,6 +242,7 @@ fun Column.onMuteUpdated() {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Column.replaceStatus(statusId: EntityId, statusJson: JsonObject) {
|
fun Column.replaceStatus(statusId: EntityId, statusJson: JsonObject) {
|
||||||
|
if (type == ColumnType.STATUS_HISTORY) return
|
||||||
|
|
||||||
fun createStatus() =
|
fun createStatus() =
|
||||||
TootParser(context, accessInfo).status(statusJson)
|
TootParser(context, accessInfo).status(statusJson)
|
||||||
|
|
|
@ -56,6 +56,7 @@ object ColumnEncoder {
|
||||||
private const val KEY_PROFILE_ID = "profile_id"
|
private const val KEY_PROFILE_ID = "profile_id"
|
||||||
private const val KEY_PROFILE_TAB = "tab"
|
private const val KEY_PROFILE_TAB = "tab"
|
||||||
private const val KEY_STATUS_ID = "status_id"
|
private const val KEY_STATUS_ID = "status_id"
|
||||||
|
private const val KEY_ORIGINAL_STATUS = "original_status"
|
||||||
|
|
||||||
private const val KEY_HASHTAG = "hashtag"
|
private const val KEY_HASHTAG = "hashtag"
|
||||||
private const val KEY_HASHTAG_ANY = "hashtag_any"
|
private const val KEY_HASHTAG_ANY = "hashtag_any"
|
||||||
|
@ -170,6 +171,11 @@ object ColumnEncoder {
|
||||||
->
|
->
|
||||||
dst[KEY_STATUS_ID] = statusId.toString()
|
dst[KEY_STATUS_ID] = statusId.toString()
|
||||||
|
|
||||||
|
ColumnType.STATUS_HISTORY -> {
|
||||||
|
dst[KEY_STATUS_ID] = statusId.toString()
|
||||||
|
dst[KEY_ORIGINAL_STATUS] = originalStatus
|
||||||
|
}
|
||||||
|
|
||||||
ColumnType.FEDERATED_AROUND -> {
|
ColumnType.FEDERATED_AROUND -> {
|
||||||
dst[KEY_STATUS_ID] = statusId.toString()
|
dst[KEY_STATUS_ID] = statusId.toString()
|
||||||
dst[KEY_REMOTE_ONLY] = remoteOnly
|
dst[KEY_REMOTE_ONLY] = remoteOnly
|
||||||
|
@ -299,6 +305,11 @@ object ColumnEncoder {
|
||||||
ColumnType.ACCOUNT_AROUND,
|
ColumnType.ACCOUNT_AROUND,
|
||||||
-> statusId = EntityId.mayNull(src.string(KEY_STATUS_ID))
|
-> statusId = EntityId.mayNull(src.string(KEY_STATUS_ID))
|
||||||
|
|
||||||
|
ColumnType.STATUS_HISTORY -> {
|
||||||
|
statusId = EntityId.mayNull(src.string(KEY_STATUS_ID))
|
||||||
|
originalStatus = src.jsonObject(KEY_ORIGINAL_STATUS)
|
||||||
|
}
|
||||||
|
|
||||||
ColumnType.FEDERATED_AROUND,
|
ColumnType.FEDERATED_AROUND,
|
||||||
-> {
|
-> {
|
||||||
statusId = EntityId.mayNull(src.string(KEY_STATUS_ID))
|
statusId = EntityId.mayNull(src.string(KEY_STATUS_ID))
|
||||||
|
|
|
@ -36,6 +36,7 @@ fun Column.canReloadWhenRefreshTop(): Boolean = when (type) {
|
||||||
ColumnType.TREND_TAG,
|
ColumnType.TREND_TAG,
|
||||||
ColumnType.FOLLOW_SUGGESTION,
|
ColumnType.FOLLOW_SUGGESTION,
|
||||||
ColumnType.PROFILE_DIRECTORY,
|
ColumnType.PROFILE_DIRECTORY,
|
||||||
|
ColumnType.STATUS_HISTORY,
|
||||||
-> true
|
-> true
|
||||||
|
|
||||||
ColumnType.LIST_MEMBER,
|
ColumnType.LIST_MEMBER,
|
||||||
|
@ -56,7 +57,7 @@ fun Column.canRefreshTopBySwipe(): Boolean =
|
||||||
else -> true
|
else -> true
|
||||||
}
|
}
|
||||||
|
|
||||||
// カラム操作的にリフレッシュを許容するかどうか
|
// カラム操作的に下端リフレッシュを許容するかどうか
|
||||||
fun Column.canRefreshBottomBySwipe(): Boolean = when (type) {
|
fun Column.canRefreshBottomBySwipe(): Boolean = when (type) {
|
||||||
ColumnType.LIST_LIST,
|
ColumnType.LIST_LIST,
|
||||||
ColumnType.CONVERSATION,
|
ColumnType.CONVERSATION,
|
||||||
|
@ -65,6 +66,7 @@ fun Column.canRefreshBottomBySwipe(): Boolean = when (type) {
|
||||||
ColumnType.SEARCH,
|
ColumnType.SEARCH,
|
||||||
ColumnType.TREND_TAG,
|
ColumnType.TREND_TAG,
|
||||||
ColumnType.FOLLOW_SUGGESTION,
|
ColumnType.FOLLOW_SUGGESTION,
|
||||||
|
ColumnType.STATUS_HISTORY,
|
||||||
-> false
|
-> false
|
||||||
|
|
||||||
ColumnType.FOLLOW_REQUESTS -> isMisskey
|
ColumnType.FOLLOW_REQUESTS -> isMisskey
|
||||||
|
|
|
@ -44,20 +44,26 @@ fun Column.getFilterContext() = when (type) {
|
||||||
|
|
||||||
ColumnType.PROFILE -> TootFilter.CONTEXT_PROFILE
|
ColumnType.PROFILE -> TootFilter.CONTEXT_PROFILE
|
||||||
|
|
||||||
|
ColumnType.STATUS_HISTORY -> TootFilter.CONTEXT_NONE
|
||||||
|
|
||||||
else -> TootFilter.CONTEXT_PUBLIC
|
else -> TootFilter.CONTEXT_PUBLIC
|
||||||
// ColumnType.MISSKEY_HYBRID や ColumnType.MISSKEY_ANTENNA_TL はHOMEでもPUBLICでもある…
|
// ColumnType.MISSKEY_HYBRID や ColumnType.MISSKEY_ANTENNA_TL はHOMEでもPUBLICでもある…
|
||||||
// Misskeyだし関係ないが、NONEにするとアプリ内で完結するフィルタも働かなくなる
|
// Misskeyだし関係ないが、NONEにするとアプリ内で完結するフィルタも働かなくなる
|
||||||
}
|
}
|
||||||
|
|
||||||
// カラム設定に正規表現フィルタを含めるなら真
|
// カラム設定に正規表現フィルタを含めるなら真
|
||||||
fun Column.canStatusFilter(): Boolean {
|
fun Column.canStatusFilter() =
|
||||||
if (getFilterContext() != TootFilter.CONTEXT_NONE) return true
|
when (type) {
|
||||||
|
ColumnType.SEARCH_MSP,
|
||||||
return when (type) {
|
ColumnType.SEARCH_TS,
|
||||||
ColumnType.SEARCH_MSP, ColumnType.SEARCH_TS, ColumnType.SEARCH_NOTESTOCK -> true
|
ColumnType.SEARCH_NOTESTOCK,
|
||||||
else -> false
|
ColumnType.STATUS_HISTORY,
|
||||||
|
-> true
|
||||||
|
else -> when {
|
||||||
|
getFilterContext() == TootFilter.CONTEXT_NONE -> false
|
||||||
|
else -> true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// カラム設定に「すべての画像を隠す」ボタンを含めるなら真
|
// カラム設定に「すべての画像を隠す」ボタンを含めるなら真
|
||||||
fun Column.canNSFWDefault(): Boolean = canStatusFilter()
|
fun Column.canNSFWDefault(): Boolean = canStatusFilter()
|
||||||
|
|
|
@ -4,6 +4,7 @@ import jp.juggler.subwaytooter.api.entity.Acct
|
||||||
import jp.juggler.subwaytooter.api.entity.EntityId
|
import jp.juggler.subwaytooter.api.entity.EntityId
|
||||||
import jp.juggler.subwaytooter.api.entity.Host
|
import jp.juggler.subwaytooter.api.entity.Host
|
||||||
import jp.juggler.subwaytooter.table.SavedAccount
|
import jp.juggler.subwaytooter.table.SavedAccount
|
||||||
|
import jp.juggler.util.JsonObject
|
||||||
import jp.juggler.util.LogCategory
|
import jp.juggler.util.LogCategory
|
||||||
|
|
||||||
private val log = LogCategory("ColumnSpec")
|
private val log = LogCategory("ColumnSpec")
|
||||||
|
@ -34,6 +35,12 @@ object ColumnSpec {
|
||||||
else -> error("getParamString [$idx] bad type. $o")
|
else -> error("getParamString [$idx] bad type. $o")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun getParamJsonObject(params: Array<out Any>, idx: Int): JsonObject =
|
||||||
|
when (val o = params[idx]) {
|
||||||
|
is JsonObject -> o
|
||||||
|
else -> error("getParamJsonObject [$idx] bad type. $o")
|
||||||
|
}
|
||||||
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
private inline fun <reified T> getParamAtNullable(params: Array<out Any>, idx: Int): T? {
|
private inline fun <reified T> getParamAtNullable(params: Array<out Any>, idx: Int): T? {
|
||||||
if (idx >= params.size) return null
|
if (idx >= params.size) return null
|
||||||
|
@ -53,6 +60,11 @@ object ColumnSpec {
|
||||||
->
|
->
|
||||||
statusId = getParamEntityId(params, 0)
|
statusId = getParamEntityId(params, 0)
|
||||||
|
|
||||||
|
ColumnType.STATUS_HISTORY -> {
|
||||||
|
statusId = getParamEntityId(params, 0)
|
||||||
|
originalStatus = getParamJsonObject(params, 1)
|
||||||
|
}
|
||||||
|
|
||||||
ColumnType.PROFILE, ColumnType.LIST_TL, ColumnType.LIST_MEMBER,
|
ColumnType.PROFILE, ColumnType.LIST_TL, ColumnType.LIST_MEMBER,
|
||||||
ColumnType.MISSKEY_ANTENNA_TL,
|
ColumnType.MISSKEY_ANTENNA_TL,
|
||||||
->
|
->
|
||||||
|
@ -121,19 +133,20 @@ object ColumnSpec {
|
||||||
ColumnType.LOCAL_AROUND,
|
ColumnType.LOCAL_AROUND,
|
||||||
ColumnType.FEDERATED_AROUND,
|
ColumnType.FEDERATED_AROUND,
|
||||||
ColumnType.ACCOUNT_AROUND,
|
ColumnType.ACCOUNT_AROUND,
|
||||||
|
ColumnType.STATUS_HISTORY,
|
||||||
->
|
->
|
||||||
column.statusId == getParamEntityId(params, 0)
|
column.statusId == getParamEntityId(params, 0)
|
||||||
|
|
||||||
ColumnType.HASHTAG -> {
|
ColumnType.HASHTAG -> {
|
||||||
(getParamString(params, 0) == column.hashtag) &&
|
(getParamString(params, 0) == column.hashtag) &&
|
||||||
((getParamAtNullable<String>(params, 1) ?: "") == column.hashtagAny) &&
|
((getParamAtNullable<String>(params, 1) ?: "") == column.hashtagAny) &&
|
||||||
((getParamAtNullable<String>(params, 2) ?: "") == column.hashtagAll) &&
|
((getParamAtNullable<String>(params, 2) ?: "") == column.hashtagAll) &&
|
||||||
((getParamAtNullable<String>(params, 3) ?: "") == column.hashtagNone)
|
((getParamAtNullable<String>(params, 3) ?: "") == column.hashtagNone)
|
||||||
}
|
}
|
||||||
|
|
||||||
ColumnType.HASHTAG_FROM_ACCT -> {
|
ColumnType.HASHTAG_FROM_ACCT -> {
|
||||||
(getParamString(params, 0) == column.hashtag) &&
|
(getParamString(params, 0) == column.hashtag) &&
|
||||||
((getParamAtNullable<String>(params, 1) ?: "") == column.hashtagAcct)
|
((getParamAtNullable<String>(params, 1) ?: "") == column.hashtagAcct)
|
||||||
}
|
}
|
||||||
|
|
||||||
ColumnType.NOTIFICATION_FROM_ACCT -> {
|
ColumnType.NOTIFICATION_FROM_ACCT -> {
|
||||||
|
@ -142,7 +155,7 @@ object ColumnSpec {
|
||||||
|
|
||||||
ColumnType.SEARCH ->
|
ColumnType.SEARCH ->
|
||||||
getParamString(params, 0) == column.searchQuery &&
|
getParamString(params, 0) == column.searchQuery &&
|
||||||
getParamAtNullable<Boolean>(params, 1) == column.searchResolve
|
getParamAtNullable<Boolean>(params, 1) == column.searchResolve
|
||||||
|
|
||||||
ColumnType.SEARCH_MSP,
|
ColumnType.SEARCH_MSP,
|
||||||
ColumnType.SEARCH_TS,
|
ColumnType.SEARCH_TS,
|
||||||
|
@ -155,8 +168,8 @@ object ColumnSpec {
|
||||||
|
|
||||||
ColumnType.PROFILE_DIRECTORY ->
|
ColumnType.PROFILE_DIRECTORY ->
|
||||||
getParamString(params, 0) == column.instanceUri &&
|
getParamString(params, 0) == column.instanceUri &&
|
||||||
getParamAtNullable<String>(params, 1) == column.searchQuery &&
|
getParamAtNullable<String>(params, 1) == column.searchQuery &&
|
||||||
getParamAtNullable<Boolean>(params, 2) == column.searchResolve
|
getParamAtNullable<Boolean>(params, 2) == column.searchResolve
|
||||||
|
|
||||||
ColumnType.DOMAIN_TIMELINE ->
|
ColumnType.DOMAIN_TIMELINE ->
|
||||||
getParamString(params, 0) == column.instanceUri
|
getParamString(params, 0) == column.instanceUri
|
||||||
|
|
|
@ -104,6 +104,9 @@ class ColumnTask_Loading(
|
||||||
// 検索カラムはIDによる重複排除が不可能
|
// 検索カラムはIDによる重複排除が不可能
|
||||||
ColumnType.SEARCH -> listTmp
|
ColumnType.SEARCH -> listTmp
|
||||||
|
|
||||||
|
// 編集履歴は投稿日時で重複排除する
|
||||||
|
ColumnType.STATUS_HISTORY -> column.duplicateMap.filterDuplicateByCreatedAt(listTmp)
|
||||||
|
|
||||||
// 他のカラムは重複排除してから追加
|
// 他のカラムは重複排除してから追加
|
||||||
else -> column.duplicateMap.filterDuplicate(listTmp)
|
else -> column.duplicateMap.filterDuplicate(listTmp)
|
||||||
}
|
}
|
||||||
|
@ -847,6 +850,19 @@ class ColumnTask_Loading(
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun getEditHistory(client: TootApiClient): TootApiResult? {
|
||||||
|
// ページングなし
|
||||||
|
val result = client.request("/api/v1/statuses/${column.statusId}/history")
|
||||||
|
|
||||||
|
// TootStatusとしては不足している情報があるのを補う
|
||||||
|
TootStatus.supplyEditHistory(result?.jsonArray, column.originalStatus)
|
||||||
|
|
||||||
|
val src = parser.statusList(result?.jsonArray).reversed()
|
||||||
|
listTmp = addAll(listTmp, src)
|
||||||
|
column.saveRange(bBottom = true, bTop = true, result = result, list = src)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
suspend fun getListList(
|
suspend fun getListList(
|
||||||
client: TootApiClient,
|
client: TootApiClient,
|
||||||
pathBase: String,
|
pathBase: String,
|
||||||
|
|
|
@ -92,7 +92,12 @@ class ColumnTask_Refresh(
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
val listNew = column.duplicateMap.filterDuplicate(listTmp)
|
val listNew = when (column.type) {
|
||||||
|
// 編集履歴は投稿日時で重複排除する
|
||||||
|
ColumnType.STATUS_HISTORY -> column.duplicateMap.filterDuplicateByCreatedAt(listTmp)
|
||||||
|
|
||||||
|
else -> column.duplicateMap.filterDuplicate(listTmp)
|
||||||
|
}
|
||||||
if (listNew.isEmpty()) {
|
if (listNew.isEmpty()) {
|
||||||
column.fireShowContent(
|
column.fireShowContent(
|
||||||
reason = "refresh list_new is empty",
|
reason = "refresh list_new is empty",
|
||||||
|
@ -1153,4 +1158,17 @@ class ColumnTask_Refresh(
|
||||||
column.saveRange(bBottom, !bBottom, result, src)
|
column.saveRange(bBottom, !bBottom, result, src)
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun getEditHistory(client: TootApiClient): TootApiResult? {
|
||||||
|
// ページングなし
|
||||||
|
val result = client.request("/api/v1/statuses/${column.statusId}/history")
|
||||||
|
|
||||||
|
// TootStatusとしては不足している情報があるのを補う
|
||||||
|
TootStatus.supplyEditHistory(result?.jsonArray, column.originalStatus)
|
||||||
|
|
||||||
|
val src = parser.statusList(result?.jsonArray).reversed()
|
||||||
|
listTmp = addAll(listTmp, src)
|
||||||
|
column.saveRange(bBottom, !bBottom, result, src)
|
||||||
|
return result
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -663,7 +663,11 @@ enum class ColumnType(
|
||||||
},
|
},
|
||||||
|
|
||||||
loading = { client -> getPublicTlAroundTime(client, column.makePublicFederateUrl()) },
|
loading = { client -> getPublicTlAroundTime(client, column.makePublicFederateUrl()) },
|
||||||
refresh = { client -> getStatusList(client, column.makePublicFederateUrl(), useMinId = true) },
|
refresh = { client ->
|
||||||
|
getStatusList(client,
|
||||||
|
column.makePublicFederateUrl(),
|
||||||
|
useMinId = true)
|
||||||
|
},
|
||||||
|
|
||||||
canStreamingMastodon = streamingTypeNo,
|
canStreamingMastodon = streamingTypeNo,
|
||||||
canStreamingMisskey = streamingTypeNo,
|
canStreamingMisskey = streamingTypeNo,
|
||||||
|
@ -1965,6 +1969,24 @@ enum class ColumnType(
|
||||||
// Misskey10 にアンテナはない
|
// Misskey10 にアンテナはない
|
||||||
),
|
),
|
||||||
|
|
||||||
|
STATUS_HISTORY(
|
||||||
|
43,
|
||||||
|
iconId = { R.drawable.ic_history },
|
||||||
|
name1 = { it.getString(R.string.edit_history) },
|
||||||
|
bAllowPseudo = true,
|
||||||
|
bAllowMisskey = false,
|
||||||
|
|
||||||
|
loading = { client ->
|
||||||
|
getEditHistory(client)
|
||||||
|
},
|
||||||
|
refresh = { client ->
|
||||||
|
getEditHistory(client)
|
||||||
|
},
|
||||||
|
|
||||||
|
canStreamingMastodon = streamingTypeNo,
|
||||||
|
canStreamingMisskey = streamingTypeNo,
|
||||||
|
),
|
||||||
|
|
||||||
;
|
;
|
||||||
|
|
||||||
init {
|
init {
|
||||||
|
@ -1985,7 +2007,7 @@ enum class ColumnType(
|
||||||
min = min(min, id)
|
min = min(min, id)
|
||||||
max = max(max, id)
|
max = max(max, id)
|
||||||
}
|
}
|
||||||
log.d("dump: ColumnType range=$min..$max")
|
log.i("dump: ColumnType range=$min..$max")
|
||||||
}
|
}
|
||||||
|
|
||||||
fun parse(id: Int) = Column.typeMap[id] ?: HOME
|
fun parse(id: Int) = Column.typeMap[id] ?: HOME
|
||||||
|
@ -2015,7 +2037,11 @@ fun Column.streamKeyHashtagTl() =
|
||||||
"hashtag"
|
"hashtag"
|
||||||
.appendIf(":local", instanceLocal)
|
.appendIf(":local", instanceLocal)
|
||||||
|
|
||||||
private fun unmatchMastodonStream(stream: JsonArray, name: String, expectArg: String? = null): Boolean {
|
private fun unmatchMastodonStream(
|
||||||
|
stream: JsonArray,
|
||||||
|
name: String,
|
||||||
|
expectArg: String? = null,
|
||||||
|
): Boolean {
|
||||||
|
|
||||||
val key = stream.string(0)
|
val key = stream.string(0)
|
||||||
|
|
||||||
|
|
|
@ -88,71 +88,11 @@ internal class DlgContextMenu(
|
||||||
dialog.setCancelable(true)
|
dialog.setCancelable(true)
|
||||||
dialog.setCanceledOnTouchOutside(true)
|
dialog.setCanceledOnTouchOutside(true)
|
||||||
|
|
||||||
arrayOf(
|
views.root.scan { v ->
|
||||||
views.btnAccountWebPage,
|
when (v) {
|
||||||
views.btnAroundAccountTL,
|
is Button -> v.setOnClickListener(this)
|
||||||
views.btnAroundFTL,
|
}
|
||||||
views.btnAroundLTL,
|
}
|
||||||
views.btnAvatarImage,
|
|
||||||
views.btnBlock,
|
|
||||||
views.btnBookmarkAnotherAccount,
|
|
||||||
views.btnBoostAnotherAccount,
|
|
||||||
views.btnBoostedBy,
|
|
||||||
views.btnBoostWithVisibility,
|
|
||||||
views.btnConversationAnotherAccount,
|
|
||||||
views.btnConversationMute,
|
|
||||||
views.btnCopyAccountId,
|
|
||||||
views.btnDelete,
|
|
||||||
views.btnDeleteSuggestion,
|
|
||||||
views.btnDomainBlock,
|
|
||||||
views.btnDomainTimeline,
|
|
||||||
views.btnEndorse,
|
|
||||||
views.btnFavouriteAnotherAccount,
|
|
||||||
views.btnFavouritedBy,
|
|
||||||
views.btnFollow,
|
|
||||||
views.btnFollowFromAnotherAccount,
|
|
||||||
views.btnFollowRequestNG,
|
|
||||||
views.btnFollowRequestOK,
|
|
||||||
views.btnHideBoost,
|
|
||||||
views.btnHideFavourite,
|
|
||||||
views.btnInstanceInformation,
|
|
||||||
views.btnListMemberAddRemove,
|
|
||||||
views.btnMute,
|
|
||||||
views.btnMuteApp,
|
|
||||||
views.btnNotificationDelete,
|
|
||||||
views.btnNotificationFrom,
|
|
||||||
views.btnOpenAccountInAdminWebUi,
|
|
||||||
views.btnOpenInstanceInAdminWebUi,
|
|
||||||
views.btnOpenProfileFromAnotherAccount,
|
|
||||||
views.btnOpenTimeline,
|
|
||||||
views.btnProfile,
|
|
||||||
views.btnProfileDirectory,
|
|
||||||
views.btnProfilePin,
|
|
||||||
views.btnProfileUnpin,
|
|
||||||
views.btnQuoteAnotherAccount,
|
|
||||||
views.btnQuoteTootBT,
|
|
||||||
views.btnReactionAnotherAccount,
|
|
||||||
views.btnRedraft,
|
|
||||||
views.btnStatusEdit,
|
|
||||||
views.btnReplyAnotherAccount,
|
|
||||||
views.btnReportStatus,
|
|
||||||
views.btnReportUser,
|
|
||||||
views.btnSendMessage,
|
|
||||||
views.btnSendMessageFromAnotherAccount,
|
|
||||||
views.btnShowBoost,
|
|
||||||
views.btnShowFavourite,
|
|
||||||
views.btnStatusNotification,
|
|
||||||
views.btnStatusWebPage,
|
|
||||||
views.btnText,
|
|
||||||
|
|
||||||
views.btnQuoteUrlStatus,
|
|
||||||
views.btnTranslate,
|
|
||||||
views.btnQuoteUrlAccount,
|
|
||||||
views.btnShareUrlStatus,
|
|
||||||
views.btnShareUrlAccount,
|
|
||||||
views.btnQuoteName
|
|
||||||
|
|
||||||
).forEach { it.setOnClickListener(this) }
|
|
||||||
|
|
||||||
arrayOf(
|
arrayOf(
|
||||||
views.btnBlock,
|
views.btnBlock,
|
||||||
|
@ -219,6 +159,11 @@ internal class DlgContextMenu(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
views.btnStatusHistory.vg(status.time_edited_at > 0L && columnType != ColumnType.STATUS_HISTORY)
|
||||||
|
?.text = activity.getString(R.string.edit_history) + "\n" +
|
||||||
|
TootStatus.formatTime(activity, status.time_edited_at, bAllowRelative = false)
|
||||||
|
|
||||||
views.llLinks.vg(views.llLinks.childCount > 1)
|
views.llLinks.vg(views.llLinks.childCount > 1)
|
||||||
|
|
||||||
views.btnGroupStatusByMe.vg(statusByMe)
|
views.btnGroupStatusByMe.vg(statusByMe)
|
||||||
|
@ -372,8 +317,6 @@ internal class DlgContextMenu(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
views.btnAccountText.setOnClickListener(this)
|
|
||||||
|
|
||||||
if (accessInfo.isPseudo) {
|
if (accessInfo.isPseudo) {
|
||||||
views.btnProfile.visibility = View.GONE
|
views.btnProfile.visibility = View.GONE
|
||||||
views.btnSendMessage.visibility = View.GONE
|
views.btnSendMessage.visibility = View.GONE
|
||||||
|
@ -399,10 +342,6 @@ internal class DlgContextMenu(
|
||||||
views.btnSendMessageFromAnotherAccount.visibility = View.GONE
|
views.btnSendMessageFromAnotherAccount.visibility = View.GONE
|
||||||
}
|
}
|
||||||
|
|
||||||
views.btnNickname.setOnClickListener(this)
|
|
||||||
views.btnCancel.setOnClickListener(this)
|
|
||||||
views.btnAccountQrCode.setOnClickListener(this)
|
|
||||||
|
|
||||||
if (accessInfo.isPseudo ||
|
if (accessInfo.isPseudo ||
|
||||||
who == null ||
|
who == null ||
|
||||||
!relation.getFollowing(who) ||
|
!relation.getFollowing(who) ||
|
||||||
|
@ -641,6 +580,7 @@ internal class DlgContextMenu(
|
||||||
R.id.btnConversationMute -> conversationMute(accessInfo, status)
|
R.id.btnConversationMute -> conversationMute(accessInfo, status)
|
||||||
R.id.btnProfilePin -> statusPin(accessInfo, status, true)
|
R.id.btnProfilePin -> statusPin(accessInfo, status, true)
|
||||||
R.id.btnProfileUnpin -> statusPin(accessInfo, status, false)
|
R.id.btnProfileUnpin -> statusPin(accessInfo, status, false)
|
||||||
|
R.id.btnStatusHistory -> openStatusHistory(pos, accessInfo, status)
|
||||||
else -> return false
|
else -> return false
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
|
|
|
@ -2,10 +2,12 @@ package jp.juggler.subwaytooter.itemviewholder
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.content.res.ColorStateList
|
import android.content.res.ColorStateList
|
||||||
|
import android.graphics.Typeface
|
||||||
import android.os.SystemClock
|
import android.os.SystemClock
|
||||||
import android.text.Spannable
|
import android.text.Spannable
|
||||||
import android.text.SpannableString
|
import android.text.SpannableString
|
||||||
import android.text.SpannableStringBuilder
|
import android.text.SpannableStringBuilder
|
||||||
|
import android.text.style.StyleSpan
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.Button
|
import android.widget.Button
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
|
@ -602,6 +604,14 @@ fun ItemViewHolder.showStatusTime(
|
||||||
if (sb.isNotEmpty()) sb.append(' ')
|
if (sb.isNotEmpty()) sb.append(' ')
|
||||||
sb.append(activity.getString(R.string.featured))
|
sb.append(activity.getString(R.string.featured))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (status.time_edited_at > 0L) {
|
||||||
|
if (sb.isNotEmpty()) sb.append(' ')
|
||||||
|
val start = sb.length
|
||||||
|
sb.append(activity.getString(R.string.edited))
|
||||||
|
val end = sb.length
|
||||||
|
sb.setSpan(StyleSpan(Typeface.BOLD), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
reblogVisibility?.takeIf { it != TootVisibility.Unknown }?.let { visibility ->
|
reblogVisibility?.takeIf { it != TootVisibility.Unknown }?.let { visibility ->
|
||||||
val visIconId = Styler.getVisibilityIconId(accessInfo.isMisskey, visibility)
|
val visIconId = Styler.getVisibilityIconId(accessInfo.isMisskey, visibility)
|
||||||
|
@ -626,12 +636,18 @@ fun ItemViewHolder.showStatusTime(
|
||||||
time != null -> TootStatus.formatTime(
|
time != null -> TootStatus.formatTime(
|
||||||
activity,
|
activity,
|
||||||
time,
|
time,
|
||||||
column.type != ColumnType.CONVERSATION
|
when (column.type) {
|
||||||
|
ColumnType.CONVERSATION, ColumnType.STATUS_HISTORY -> false
|
||||||
|
else -> true
|
||||||
|
}
|
||||||
)
|
)
|
||||||
status != null -> TootStatus.formatTime(
|
status != null -> TootStatus.formatTime(
|
||||||
activity,
|
activity,
|
||||||
status.time_created_at,
|
status.time_created_at,
|
||||||
column.type != ColumnType.CONVERSATION
|
when (column.type) {
|
||||||
|
ColumnType.CONVERSATION, ColumnType.STATUS_HISTORY -> false
|
||||||
|
else -> true
|
||||||
|
}
|
||||||
)
|
)
|
||||||
else -> "?"
|
else -> "?"
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24"
|
||||||
|
android:tint="?attr/colorControlNormal">
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/white"
|
||||||
|
android:pathData="M13,3c-4.97,0 -9,4.03 -9,9L1,12l3.89,3.89 0.07,0.14L9,12L6,12c0,-3.87 3.13,-7 7,-7s7,3.13 7,7 -3.13,7 -7,7c-1.93,0 -3.68,-0.79 -4.94,-2.06l-1.42,1.42C8.27,19.99 10.51,21 13,21c4.97,0 9,-4.03 9,-9s-4.03,-9 -9,-9zM12,8v5l4.28,2.54 0.72,-1.21 -3.5,-2.08L13.5,8L12,8z"/>
|
||||||
|
</vector>
|
|
@ -50,6 +50,21 @@
|
||||||
android:textColor="?attr/colorTimeSmall"
|
android:textColor="?attr/colorTimeSmall"
|
||||||
android:textSize="12sp" />
|
android:textSize="12sp" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/btnStatusHistory"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="@drawable/btn_bg_transparent_round6dp"
|
||||||
|
android:gravity="start|center_vertical"
|
||||||
|
android:minHeight="32dp"
|
||||||
|
android:paddingStart="8dp"
|
||||||
|
android:paddingTop="4dp"
|
||||||
|
android:paddingEnd="8dp"
|
||||||
|
android:paddingBottom="4dp"
|
||||||
|
android:text="@string/edit_history"
|
||||||
|
android:textAllCaps="false" />
|
||||||
|
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
android:id="@+id/btnStatusWebPage"
|
android:id="@+id/btnStatusWebPage"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
|
|
@ -1127,4 +1127,6 @@
|
||||||
<string name="always">常に</string>
|
<string name="always">常に</string>
|
||||||
<string name="auto">自動</string>
|
<string name="auto">自動</string>
|
||||||
<string name="status_edit">編集(Mastodon 3.5.0+)</string>
|
<string name="status_edit">編集(Mastodon 3.5.0+)</string>
|
||||||
|
<string name="edited">編集済</string>
|
||||||
|
<string name="edit_history">編集履歴</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -1138,4 +1138,6 @@
|
||||||
<string name="always">Always</string>
|
<string name="always">Always</string>
|
||||||
<string name="auto">Auto</string>
|
<string name="auto">Auto</string>
|
||||||
<string name="status_edit">Edit (Mastodon 3.5.0+)</string>
|
<string name="status_edit">Edit (Mastodon 3.5.0+)</string>
|
||||||
|
<string name="edited">edited</string>
|
||||||
|
<string name="edit_history">Edit history</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
Loading…
Reference in New Issue