diff --git a/app/src/main/java/jp/juggler/subwaytooter/action/Action_Status.kt b/app/src/main/java/jp/juggler/subwaytooter/action/Action_Status.kt index 823fad97..c65610a1 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/action/Action_Status.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/action/Action_Status.kt @@ -3,14 +3,12 @@ package jp.juggler.subwaytooter.action import android.content.Intent import androidx.appcompat.app.AlertDialog import jp.juggler.subwaytooter.* +import jp.juggler.subwaytooter.actmain.addColumn import jp.juggler.subwaytooter.actmain.reloadAccountSetting import jp.juggler.subwaytooter.actmain.showColumnMatchAccount import jp.juggler.subwaytooter.api.* import jp.juggler.subwaytooter.api.entity.* -import jp.juggler.subwaytooter.column.Column -import jp.juggler.subwaytooter.column.findStatus -import jp.juggler.subwaytooter.column.onScheduleDeleted -import jp.juggler.subwaytooter.column.onStatusRemoved +import jp.juggler.subwaytooter.column.* import jp.juggler.subwaytooter.dialog.ActionsDialog import jp.juggler.subwaytooter.dialog.DlgConfirm 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) +} diff --git a/app/src/main/java/jp/juggler/subwaytooter/api/DuplicateMap.kt b/app/src/main/java/jp/juggler/subwaytooter/api/DuplicateMap.kt index 8497ebfb..86d634ba 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/api/DuplicateMap.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/api/DuplicateMap.kt @@ -2,6 +2,7 @@ package jp.juggler.subwaytooter.api import jp.juggler.subwaytooter.api.entity.* import jp.juggler.util.LogCategory +import jp.juggler.util.cast import java.util.* class DuplicateMap { @@ -67,4 +68,30 @@ class DuplicateMap { } 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?): ArrayList { + val listNew = ArrayList() + if (src != null) { + for (o in src) { + if (isDuplicateByCreatedAt(o)) { + log.d("filterDuplicateByCreatedAt: filtered. ${o.cast()?.time_created_at}") + continue + } + listNew.add(o) + } + } + return listNew + } } diff --git a/app/src/main/java/jp/juggler/subwaytooter/api/entity/TootStatus.kt b/app/src/main/java/jp/juggler/subwaytooter/api/entity/TootStatus.kt index bb6e9fee..39aa177e 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/api/entity/TootStatus.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/api/entity/TootStatus.kt @@ -182,6 +182,9 @@ class TootStatus(parser: TootParser, src: JsonObject) : TimelineItem() { var isPromoted = false var isFeatured = false + // Mastodon 3.5.0 + var time_edited_at = 0L + /////////////////////////////////////////////////////////////////// // 以下はentityから取得したデータではなく、アプリ内部で使う @@ -602,6 +605,8 @@ class TootStatus(parser: TootParser, src: JsonObject) : TimelineItem() { this.favourites_count = src.long("favourites_count") this.replies_count = src.long("replies_count") + this.time_edited_at = parseTime(src.string("edited_at")) + this.reactionSet = TootReactionSet.parseFedibird( src.jsonArray("emoji_reactions") ?: src.jsonObject("pleroma")?.jsonArray("emoji_reactions") @@ -1267,7 +1272,7 @@ class TootStatus(parser: TootParser, src: JsonObject) : TimelineItem() { // 時刻を解釈してエポック秒(ミリ単位)を返す // 解釈に失敗すると0Lを返す fun parseTime(strTime: String?): Long { - if (strTime?.isNotBlank() != true) return 0L + if (strTime.isNullOrBlank()) return 0L // last_status_at などでは YYYY-MM-DD になることがある reDate.find(strTime)?.groupValues?.let { gv -> @@ -1552,5 +1557,23 @@ class TootStatus(parser: TootParser, src: JsonObject) : TimelineItem() { 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] + } + } + } } } diff --git a/app/src/main/java/jp/juggler/subwaytooter/column/Column.kt b/app/src/main/java/jp/juggler/subwaytooter/column/Column.kt index 98af1e7d..2112e31a 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/column/Column.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/column/Column.kt @@ -160,6 +160,7 @@ class Column( internal var profileTab = ProfileTab.Status internal var statusId: EntityId? = null + internal var originalStatus: JsonObject? = null // プロフカラムではアカウントのID。リストカラムではリストのID internal var profileId: EntityId? = null diff --git a/app/src/main/java/jp/juggler/subwaytooter/column/ColumnActions.kt b/app/src/main/java/jp/juggler/subwaytooter/column/ColumnActions.kt index 3bd6c1c9..6698f8b4 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/column/ColumnActions.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/column/ColumnActions.kt @@ -242,6 +242,7 @@ fun Column.onMuteUpdated() { } fun Column.replaceStatus(statusId: EntityId, statusJson: JsonObject) { + if (type == ColumnType.STATUS_HISTORY) return fun createStatus() = TootParser(context, accessInfo).status(statusJson) diff --git a/app/src/main/java/jp/juggler/subwaytooter/column/ColumnEncoder.kt b/app/src/main/java/jp/juggler/subwaytooter/column/ColumnEncoder.kt index 531bc7d9..b91d30b5 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/column/ColumnEncoder.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/column/ColumnEncoder.kt @@ -56,6 +56,7 @@ object ColumnEncoder { private const val KEY_PROFILE_ID = "profile_id" private const val KEY_PROFILE_TAB = "tab" 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_ANY = "hashtag_any" @@ -170,6 +171,11 @@ object ColumnEncoder { -> dst[KEY_STATUS_ID] = statusId.toString() + ColumnType.STATUS_HISTORY -> { + dst[KEY_STATUS_ID] = statusId.toString() + dst[KEY_ORIGINAL_STATUS] = originalStatus + } + ColumnType.FEDERATED_AROUND -> { dst[KEY_STATUS_ID] = statusId.toString() dst[KEY_REMOTE_ONLY] = remoteOnly @@ -299,6 +305,11 @@ object ColumnEncoder { ColumnType.ACCOUNT_AROUND, -> 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, -> { statusId = EntityId.mayNull(src.string(KEY_STATUS_ID)) diff --git a/app/src/main/java/jp/juggler/subwaytooter/column/ColumnExtra1.kt b/app/src/main/java/jp/juggler/subwaytooter/column/ColumnExtra1.kt index 1db443bc..298a4d24 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/column/ColumnExtra1.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/column/ColumnExtra1.kt @@ -36,6 +36,7 @@ fun Column.canReloadWhenRefreshTop(): Boolean = when (type) { ColumnType.TREND_TAG, ColumnType.FOLLOW_SUGGESTION, ColumnType.PROFILE_DIRECTORY, + ColumnType.STATUS_HISTORY, -> true ColumnType.LIST_MEMBER, @@ -56,7 +57,7 @@ fun Column.canRefreshTopBySwipe(): Boolean = else -> true } -// カラム操作的にリフレッシュを許容するかどうか +// カラム操作的に下端リフレッシュを許容するかどうか fun Column.canRefreshBottomBySwipe(): Boolean = when (type) { ColumnType.LIST_LIST, ColumnType.CONVERSATION, @@ -65,6 +66,7 @@ fun Column.canRefreshBottomBySwipe(): Boolean = when (type) { ColumnType.SEARCH, ColumnType.TREND_TAG, ColumnType.FOLLOW_SUGGESTION, + ColumnType.STATUS_HISTORY, -> false ColumnType.FOLLOW_REQUESTS -> isMisskey diff --git a/app/src/main/java/jp/juggler/subwaytooter/column/ColumnFilters.kt b/app/src/main/java/jp/juggler/subwaytooter/column/ColumnFilters.kt index 833df985..2956d563 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/column/ColumnFilters.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/column/ColumnFilters.kt @@ -44,20 +44,26 @@ fun Column.getFilterContext() = when (type) { ColumnType.PROFILE -> TootFilter.CONTEXT_PROFILE + ColumnType.STATUS_HISTORY -> TootFilter.CONTEXT_NONE + else -> TootFilter.CONTEXT_PUBLIC // ColumnType.MISSKEY_HYBRID や ColumnType.MISSKEY_ANTENNA_TL はHOMEでもPUBLICでもある… // Misskeyだし関係ないが、NONEにするとアプリ内で完結するフィルタも働かなくなる } // カラム設定に正規表現フィルタを含めるなら真 -fun Column.canStatusFilter(): Boolean { - if (getFilterContext() != TootFilter.CONTEXT_NONE) return true - - return when (type) { - ColumnType.SEARCH_MSP, ColumnType.SEARCH_TS, ColumnType.SEARCH_NOTESTOCK -> true - else -> false +fun Column.canStatusFilter() = + when (type) { + ColumnType.SEARCH_MSP, + ColumnType.SEARCH_TS, + ColumnType.SEARCH_NOTESTOCK, + ColumnType.STATUS_HISTORY, + -> true + else -> when { + getFilterContext() == TootFilter.CONTEXT_NONE -> false + else -> true + } } -} // カラム設定に「すべての画像を隠す」ボタンを含めるなら真 fun Column.canNSFWDefault(): Boolean = canStatusFilter() diff --git a/app/src/main/java/jp/juggler/subwaytooter/column/ColumnSpec.kt b/app/src/main/java/jp/juggler/subwaytooter/column/ColumnSpec.kt index 3f523ca2..a3bfa027 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/column/ColumnSpec.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/column/ColumnSpec.kt @@ -4,6 +4,7 @@ import jp.juggler.subwaytooter.api.entity.Acct import jp.juggler.subwaytooter.api.entity.EntityId import jp.juggler.subwaytooter.api.entity.Host import jp.juggler.subwaytooter.table.SavedAccount +import jp.juggler.util.JsonObject import jp.juggler.util.LogCategory private val log = LogCategory("ColumnSpec") @@ -34,6 +35,12 @@ object ColumnSpec { else -> error("getParamString [$idx] bad type. $o") } + private fun getParamJsonObject(params: Array, idx: Int): JsonObject = + when (val o = params[idx]) { + is JsonObject -> o + else -> error("getParamJsonObject [$idx] bad type. $o") + } + @Suppress("UNCHECKED_CAST") private inline fun getParamAtNullable(params: Array, idx: Int): T? { if (idx >= params.size) return null @@ -53,6 +60,11 @@ object ColumnSpec { -> statusId = getParamEntityId(params, 0) + ColumnType.STATUS_HISTORY -> { + statusId = getParamEntityId(params, 0) + originalStatus = getParamJsonObject(params, 1) + } + ColumnType.PROFILE, ColumnType.LIST_TL, ColumnType.LIST_MEMBER, ColumnType.MISSKEY_ANTENNA_TL, -> @@ -121,19 +133,20 @@ object ColumnSpec { ColumnType.LOCAL_AROUND, ColumnType.FEDERATED_AROUND, ColumnType.ACCOUNT_AROUND, + ColumnType.STATUS_HISTORY, -> column.statusId == getParamEntityId(params, 0) ColumnType.HASHTAG -> { (getParamString(params, 0) == column.hashtag) && - ((getParamAtNullable(params, 1) ?: "") == column.hashtagAny) && - ((getParamAtNullable(params, 2) ?: "") == column.hashtagAll) && - ((getParamAtNullable(params, 3) ?: "") == column.hashtagNone) + ((getParamAtNullable(params, 1) ?: "") == column.hashtagAny) && + ((getParamAtNullable(params, 2) ?: "") == column.hashtagAll) && + ((getParamAtNullable(params, 3) ?: "") == column.hashtagNone) } ColumnType.HASHTAG_FROM_ACCT -> { (getParamString(params, 0) == column.hashtag) && - ((getParamAtNullable(params, 1) ?: "") == column.hashtagAcct) + ((getParamAtNullable(params, 1) ?: "") == column.hashtagAcct) } ColumnType.NOTIFICATION_FROM_ACCT -> { @@ -142,7 +155,7 @@ object ColumnSpec { ColumnType.SEARCH -> getParamString(params, 0) == column.searchQuery && - getParamAtNullable(params, 1) == column.searchResolve + getParamAtNullable(params, 1) == column.searchResolve ColumnType.SEARCH_MSP, ColumnType.SEARCH_TS, @@ -155,8 +168,8 @@ object ColumnSpec { ColumnType.PROFILE_DIRECTORY -> getParamString(params, 0) == column.instanceUri && - getParamAtNullable(params, 1) == column.searchQuery && - getParamAtNullable(params, 2) == column.searchResolve + getParamAtNullable(params, 1) == column.searchQuery && + getParamAtNullable(params, 2) == column.searchResolve ColumnType.DOMAIN_TIMELINE -> getParamString(params, 0) == column.instanceUri diff --git a/app/src/main/java/jp/juggler/subwaytooter/column/ColumnTask_Loading.kt b/app/src/main/java/jp/juggler/subwaytooter/column/ColumnTask_Loading.kt index ec551e26..e66fbd65 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/column/ColumnTask_Loading.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/column/ColumnTask_Loading.kt @@ -104,6 +104,9 @@ class ColumnTask_Loading( // 検索カラムはIDによる重複排除が不可能 ColumnType.SEARCH -> listTmp + // 編集履歴は投稿日時で重複排除する + ColumnType.STATUS_HISTORY -> column.duplicateMap.filterDuplicateByCreatedAt(listTmp) + // 他のカラムは重複排除してから追加 else -> column.duplicateMap.filterDuplicate(listTmp) } @@ -847,6 +850,19 @@ class ColumnTask_Loading( 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( client: TootApiClient, pathBase: String, diff --git a/app/src/main/java/jp/juggler/subwaytooter/column/ColumnTask_Refresh.kt b/app/src/main/java/jp/juggler/subwaytooter/column/ColumnTask_Refresh.kt index 97d8f7cd..db771c50 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/column/ColumnTask_Refresh.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/column/ColumnTask_Refresh.kt @@ -92,7 +92,12 @@ class ColumnTask_Refresh( 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()) { column.fireShowContent( reason = "refresh list_new is empty", @@ -1153,4 +1158,17 @@ class ColumnTask_Refresh( column.saveRange(bBottom, !bBottom, result, src) 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 + } } diff --git a/app/src/main/java/jp/juggler/subwaytooter/column/ColumnType.kt b/app/src/main/java/jp/juggler/subwaytooter/column/ColumnType.kt index 788f1c2a..98fa3851 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/column/ColumnType.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/column/ColumnType.kt @@ -663,7 +663,11 @@ enum class ColumnType( }, loading = { client -> getPublicTlAroundTime(client, column.makePublicFederateUrl()) }, - refresh = { client -> getStatusList(client, column.makePublicFederateUrl(), useMinId = true) }, + refresh = { client -> + getStatusList(client, + column.makePublicFederateUrl(), + useMinId = true) + }, canStreamingMastodon = streamingTypeNo, canStreamingMisskey = streamingTypeNo, @@ -1965,6 +1969,24 @@ enum class ColumnType( // 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 { @@ -1985,7 +2007,7 @@ enum class ColumnType( min = min(min, 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 @@ -2015,7 +2037,11 @@ fun Column.streamKeyHashtagTl() = "hashtag" .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) diff --git a/app/src/main/java/jp/juggler/subwaytooter/itemviewholder/DlgContextMenu.kt b/app/src/main/java/jp/juggler/subwaytooter/itemviewholder/DlgContextMenu.kt index d040c754..8b54720d 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/itemviewholder/DlgContextMenu.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/itemviewholder/DlgContextMenu.kt @@ -88,71 +88,11 @@ internal class DlgContextMenu( dialog.setCancelable(true) dialog.setCanceledOnTouchOutside(true) - arrayOf( - views.btnAccountWebPage, - views.btnAroundAccountTL, - 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) } + views.root.scan { v -> + when (v) { + is Button -> v.setOnClickListener(this) + } + } arrayOf( 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.btnGroupStatusByMe.vg(statusByMe) @@ -372,8 +317,6 @@ internal class DlgContextMenu( } } - views.btnAccountText.setOnClickListener(this) - if (accessInfo.isPseudo) { views.btnProfile.visibility = View.GONE views.btnSendMessage.visibility = View.GONE @@ -399,10 +342,6 @@ internal class DlgContextMenu( views.btnSendMessageFromAnotherAccount.visibility = View.GONE } - views.btnNickname.setOnClickListener(this) - views.btnCancel.setOnClickListener(this) - views.btnAccountQrCode.setOnClickListener(this) - if (accessInfo.isPseudo || who == null || !relation.getFollowing(who) || @@ -641,6 +580,7 @@ internal class DlgContextMenu( R.id.btnConversationMute -> conversationMute(accessInfo, status) R.id.btnProfilePin -> statusPin(accessInfo, status, true) R.id.btnProfileUnpin -> statusPin(accessInfo, status, false) + R.id.btnStatusHistory -> openStatusHistory(pos, accessInfo, status) else -> return false } return true diff --git a/app/src/main/java/jp/juggler/subwaytooter/itemviewholder/ItemViewHolderShow.kt b/app/src/main/java/jp/juggler/subwaytooter/itemviewholder/ItemViewHolderShow.kt index 68eb004b..290f6e93 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/itemviewholder/ItemViewHolderShow.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/itemviewholder/ItemViewHolderShow.kt @@ -2,10 +2,12 @@ package jp.juggler.subwaytooter.itemviewholder import android.annotation.SuppressLint import android.content.res.ColorStateList +import android.graphics.Typeface import android.os.SystemClock import android.text.Spannable import android.text.SpannableString import android.text.SpannableStringBuilder +import android.text.style.StyleSpan import android.view.View import android.widget.Button import android.widget.TextView @@ -602,6 +604,14 @@ fun ItemViewHolder.showStatusTime( if (sb.isNotEmpty()) sb.append(' ') 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 { reblogVisibility?.takeIf { it != TootVisibility.Unknown }?.let { visibility -> val visIconId = Styler.getVisibilityIconId(accessInfo.isMisskey, visibility) @@ -626,12 +636,18 @@ fun ItemViewHolder.showStatusTime( time != null -> TootStatus.formatTime( activity, time, - column.type != ColumnType.CONVERSATION + when (column.type) { + ColumnType.CONVERSATION, ColumnType.STATUS_HISTORY -> false + else -> true + } ) status != null -> TootStatus.formatTime( activity, status.time_created_at, - column.type != ColumnType.CONVERSATION + when (column.type) { + ColumnType.CONVERSATION, ColumnType.STATUS_HISTORY -> false + else -> true + } ) else -> "?" } diff --git a/app/src/main/res/drawable/ic_history.xml b/app/src/main/res/drawable/ic_history.xml new file mode 100644 index 00000000..9bc1fc38 --- /dev/null +++ b/app/src/main/res/drawable/ic_history.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/layout/dlg_context_menu.xml b/app/src/main/res/layout/dlg_context_menu.xml index fb4b7aa9..1c9f2e7c 100644 --- a/app/src/main/res/layout/dlg_context_menu.xml +++ b/app/src/main/res/layout/dlg_context_menu.xml @@ -50,6 +50,21 @@ android:textColor="?attr/colorTimeSmall" android:textSize="12sp" /> +