タグのフォロー操作の結果をフォロータグ一覧やトレンドタグや検索結果のカラムに反映させる。それらのカラムにタグのフォロー中状態を表示する。

This commit is contained in:
tateisu 2022-07-21 06:41:53 +09:00
parent b148c6dd03
commit 1de061a5b1
10 changed files with 132 additions and 62 deletions

View File

@ -3,18 +3,22 @@ package jp.juggler.subwaytooter.action
import jp.juggler.subwaytooter.ActMain
import jp.juggler.subwaytooter.R
import jp.juggler.subwaytooter.actmain.addColumn
import jp.juggler.subwaytooter.api.TootParser
import jp.juggler.subwaytooter.api.entity.Acct
import jp.juggler.subwaytooter.api.entity.Host
import jp.juggler.subwaytooter.api.entity.TootInstance
import jp.juggler.subwaytooter.api.entity.TootTag
import jp.juggler.subwaytooter.api.runApiTask
import jp.juggler.subwaytooter.column.ColumnType
import jp.juggler.subwaytooter.column.onTagFollowChanged
import jp.juggler.subwaytooter.dialog.ActionsDialog
import jp.juggler.subwaytooter.global.appDispatchers
import jp.juggler.subwaytooter.table.AcctColor
import jp.juggler.subwaytooter.table.SavedAccount
import jp.juggler.subwaytooter.util.matchHost
import jp.juggler.subwaytooter.util.openCustomTab
import jp.juggler.util.*
import kotlinx.coroutines.withContext
private val log = LogCategory("Action_Tag")
@ -31,14 +35,23 @@ fun ActMain.longClickTootTag(pos: Int, accessInfo: SavedAccount, item: TootTag)
fun ActMain.tagDialog(
accessInfo: SavedAccount?,
pos: Int,
// タグのURL
// URLのホスト部分は普通はaccessInfoと同じだが、検索などでバラバラになる場合がある
url: String,
// タグのHost。
// 普通はaccessInfoと同じだが、検索などでバラバラになる場合がある
host: Host,
// タグの名前
tagWithoutSharp: String,
tagList: ArrayList<String>?,
whoAcct: Acct?,
// 複数のタグをまとめて引用したい場合がある
tagList: ArrayList<String>? = null,
// nullでなければ投稿者別タグTLを開く選択肢を表示する
whoAcct: Acct? = null,
// TootTagの情報があれば
tagInfo: TootTag? = null,
) {
val activity = this
val tagWithSharp = "#$tagWithoutSharp"
launchMain {
try {
@ -52,12 +65,11 @@ fun ActMain.tagDialog(
)
}
// https://mastodon.juggler.jp/@tateisu/101865456016473337
// 一時的に使えなくする
// 投稿者別タグTL
if (whoAcct != null) {
d.addAction(
AcctColor.getStringWithNickname(
this@tagDialog,
activity,
R.string.open_hashtag_from_account,
whoAcct
)
@ -93,22 +105,23 @@ fun ActMain.tagDialog(
val ti = TootInstance.getCached(accessInfo)
if (ti != null && accessInfo?.isMisskey == false) {
val result = runApiTask(accessInfo) { client ->
client.request("/api/v1/tags/${tagWithoutSharp.encodePercent()}")
var tag = tagInfo
if (tag == null) {
val result = runApiTask(accessInfo) { client ->
client.request("/api/v1/tags/${tagWithoutSharp.encodePercent()}")
} ?: return@launchMain //cancelled.
TootParser(activity, accessInfo)
.tag(result.jsonObject)
?.let { tag = it }
}
val following = when {
result == null || result.error != null -> null
else -> result.jsonObject?.boolean("following")
val toggle = !(tag?.following ?: false)
val toggleCaption = when (toggle) {
true -> R.string.follow_hashtag_of
else -> R.string.unfollow_hashtag_of
}
val toggle = following?.let { !it }
if (toggle != null) {
val toggleCaption = when (toggle) {
true -> R.string.follow_hashtag_of
else -> R.string.unfollow_hashtag_of
}
d.addAction(getString(toggleCaption, tagWithSharp)) {
followHashTag(accessInfo, tagWithoutSharp, toggle)
}
d.addAction(getString(toggleCaption, tagWithSharp)) {
followHashTag(accessInfo, tagWithoutSharp, toggle)
}
}
@ -142,9 +155,15 @@ fun ActMain.tagTimeline(
// アカウントを選んでハッシュタグカラムを開く
fun ActMain.tagTimelineFromAccount(
pos: Int,
// タグのURL
// URLのホスト部分は普通はaccessInfoと同じだが、検索などでバラバラになる場合がある
url: String,
// タグのHost。
// 普通はaccessInfoと同じだが、検索などでバラバラになる場合がある
host: Host,
// タグの名前。#を含まない
tagWithoutSharp: String,
// 「投稿者別タグTL」を開くなら、投稿者のacctを指定する
acct: Acct? = null,
) {
@ -166,14 +185,13 @@ fun ActMain.tagTimelineFromAccount(
}
} else {
when {
// 疑似アカウントはacctからaccount idを取得できないので
// アカウント別タグTLを開けない
a.isPseudo -> Unit
// acctからidを取得できない
a.isPseudo -> {
}
// ミスキーのアカウント別タグTLは未対応
a.isMisskey -> {
}
// ミスキーはアカウント別タグTLがないので
// アカウント別タグTLを開けない
a.isMisskey -> Unit
!a.matchHost(host) -> listOther.add(a)
else -> listOriginal.add(a)
@ -227,6 +245,7 @@ fun ActMain.followHashTag(
tagWithoutSharp: String,
isSet: Boolean,
) {
val activity = this
launchMain {
runApiTask(accessInfo) { client ->
client.request(
@ -235,14 +254,24 @@ fun ActMain.followHashTag(
)
}?.let { result ->
when (val error = result.error) {
// 成功時はTagオブジェクトが返るが、使っていない
null -> showToast(
false,
when {
isSet -> R.string.follow_succeeded
else -> R.string.unfollow_succeeded
null -> {
showToast(
false,
when {
isSet -> R.string.follow_succeeded
else -> R.string.unfollow_succeeded
}
)
// 成功時はTagオブジェクトが返る
// フォロー中のタグ一覧を更新する
TootParser(activity, accessInfo).tag(result.jsonObject)?.let { tag ->
withContext(appDispatchers.main.immediate) {
for (column in appState.columnList) {
column.onTagFollowChanged(accessInfo, tag)
}
}
}
)
}
else -> showToast(true, error)
}
}

View File

@ -1417,7 +1417,6 @@ suspend fun TootApiClient.syncStatus(
TootParser(
context,
linkHelper = LinkHelper.create(host, misskeyVersion = 10),
serviceType = ServiceType.MISSKEY
)
.status(result.jsonObject)
?.apply {

View File

@ -13,7 +13,10 @@ class TootParser(
val linkHelper: LinkHelper,
var pinned: Boolean = false, // プロフィールカラムからpinned TL を読んだ時だけ真
var highlightTrie: WordTrieTree? = null,
var serviceType: ServiceType = ServiceType.MASTODON,
var serviceType: ServiceType = when {
linkHelper.isMisskey -> ServiceType.MISSKEY
else -> ServiceType.MASTODON
},
var misskeyDecodeProfilePin: Boolean = false,
var fromStream: Boolean = false,
var decodeQuote: Boolean = true,
@ -28,10 +31,6 @@ class TootParser(
val apDomain: Host
get() = linkHelper.apDomain
init {
if (linkHelper.isMisskey) serviceType = ServiceType.MISSKEY
}
fun getFullAcct(acct: Acct?) = linkHelper.getFullAcct(acct)
fun account(src: JsonObject?) = parseItem(::TootAccount, this, src)
@ -44,6 +43,9 @@ class TootParser(
fun notification(src: JsonObject?) = parseItem(::TootNotification, this, src)
fun notificationList(src: JsonArray?) = parseList(::TootNotification, this, src)
fun tag(src: JsonObject?) =
src?.let { TootTag.parse(this, it) }
fun tagList(array: JsonArray?) =
TootTag.parseList(this, array)

View File

@ -13,6 +13,9 @@ open class TootTag constructor(
var type: TagType = TagType.Tag,
// (Mastodon 3.6) タグをフォロー中なら真
val following: Boolean? = null,
// The URL of the hashtag. may null if generated from TootContext
val url: String? = null,
@ -22,12 +25,12 @@ open class TootTag constructor(
// Mastodon /api/v2/search provides history.
val history: ArrayList<History>? = null,
) : TimelineItem() {
) : TimelineItem() {
enum class TagType {
Tag,
TrendLink,
FollowedTags,
Link,
}
val countDaily: Int
@ -71,12 +74,12 @@ open class TootTag constructor(
val name = src.stringOrThrow("tag").replaceFirst(reHeadSharp, "")
TootTag(
name = name,
url = "https://${parser.apiHost}/tags/${Uri.encode(name)}"
url = "https://${parser.apiHost}/tags/${Uri.encode(name)}",
)
}
src.string("type") == "link" -> {
TootTag(
type = TagType.TrendLink,
type = TagType.Link,
name = src.string("title") ?: "",
url = src.string("url") ?: "",
description = src.string("description") ?: "",
@ -95,7 +98,8 @@ open class TootTag constructor(
url = (src.string("url") ?: src.string("href"))
?.replaceFirst(reUserTagUrl, "/tags/")
?.replaceFirst(reNotestockTagUrl, "/tags/"),
history = parseHistories(src.jsonArray("history"))
history = parseHistories(src.jsonArray("history")),
following = src.boolean("following"),
)
}
}

View File

@ -434,3 +434,39 @@ fun replaceConversationSummary(
if (removeSet.contains(o.id)) it.remove()
}
}
// タグのフォロー状態が変わったら呼ばれる
fun Column.onTagFollowChanged(account: SavedAccount, newTag: TootTag) {
if (isDispose.get() || bInitialLoading || bRefreshLoading) return
if (accessInfo != account) return
when (type) {
ColumnType.FOLLOWED_HASHTAGS, ColumnType.TREND_TAG, ColumnType.SEARCH -> {
val tmpList = ArrayList<TimelineItem>(listData.size)
for (o in listData) {
if (o is TootTag && o.name == newTag.name) {
tmpList.add(newTag)
} else {
tmpList.add(o)
}
}
if (type == ColumnType.FOLLOWED_HASHTAGS) {
val tagFinder:(TimelineItem)->Boolean = {it is TootTag && it.name == newTag.name}
when (newTag.following) {
true ->
if (tmpList.none(tagFinder)) {
tmpList.add(0, newTag)
}
else -> tmpList.indexOfFirst(tagFinder)
.takeIf { it>=0 }?.let{ tmpList.removeAt(it)}
}
}
listData.clear()
listData.addAll(tmpList)
fireShowContent(reason = "onTagFollowChanged")
}
else -> Unit
}
}

View File

@ -866,7 +866,6 @@ class ColumnTask_Loading(
suspend fun getFollowedHashtags(client: TootApiClient): TootApiResult? {
val result = client.request("/api/v1/followed_tags")
val src = parser.tagList(result?.jsonArray)
.onEach { it.type = TootTag.TagType.FollowedTags }
listTmp = addAll(listTmp, src)
column.saveRange(bBottom = true, bTop = true, result = result, list = src)
return result
@ -1217,7 +1216,7 @@ class ColumnTask_Loading(
val (apiResult, searchResult) = client.requestMastodonSearch(parser, query)
if (searchResult != null) {
listTmp = java.util.ArrayList()
listTmp = ArrayList()
addAll(listTmp, searchResult.hashtags)
if (searchResult.searchApiVersion >= 2 && searchResult.hashtags.isNotEmpty()) {
addOne(listTmp, TootSearchGap(TootSearchGap.SearchType.Hashtag))

View File

@ -1178,7 +1178,6 @@ class ColumnTask_Refresh(
val path = column.addRange(bBottom = bBottom, "/api/v1/followed_tags")
val result = client.request(path)
val src = parser.tagList(result?.jsonArray)
.onEach { it.type = TootTag.TagType.FollowedTags }
listTmp = addAll(listTmp, src)
column.saveRange(bBottom = bBottom, bTop = !bBottom, result = result, list = src)
return result

View File

@ -233,7 +233,7 @@ suspend fun Column.makeHashtagAcctUrl(client: TootApiClient): String? {
fun Column.makeMisskeyBaseParameter(parser: TootParser?) =
accessInfo.putMisskeyApiToken().apply {
if (accessInfo.isMisskey) {
if (parser != null) parser.serviceType = ServiceType.MISSKEY
parser?.serviceType = ServiceType.MISSKEY
put("limit", 40)
}
}

View File

@ -265,18 +265,15 @@ private fun ItemViewHolder.clickTag(pos: Int, item: TimelineItem?) {
when (item) {
is TootTag -> when (item.type) {
TootTag.TagType.Tag ->
tagTimeline(pos, accessInfo, item.name)
TootTag.TagType.FollowedTags -> {
val host = accessInfo.apiHost
tagDialog(accessInfo,
tagDialog(
accessInfo,
pos,
item.url!!,
host,
accessInfo.apiHost,
item.name,
tagList = null,
whoAcct = null)
}
TootTag.TagType.TrendLink ->
tagInfo = item,
)
TootTag.TagType.Link ->
openCustomTab(item.url)
}
is TootSearchGap -> column.startGap(item, isHead = true)

View File

@ -421,14 +421,19 @@ fun ItemViewHolder.showSearchTag(tag: TootTag) {
tvTrendTagCount.text = "${tag.countDaily}(${tag.countWeekly})"
cvTagHistory.setHistory(tag.history)
when (tag.type) {
TootTag.TagType.TrendLink -> {
TootTag.TagType.Link -> {
tvTrendTagName.text = tag.url?.ellipsizeDot3(256)
tvTrendTagDesc.text = tag.name + "\n" + tag.description
}
else -> {
TootTag.TagType.Tag -> {
tvTrendTagName.text = "#${tag.name.ellipsizeDot3(256)}"
tvTrendTagDesc.text =
tvTrendTagDesc.text = listOf(
when (tag.following) {
true -> activity.getString(R.string.following)
else -> ""
},
activity.getString(R.string.people_talking, tag.accountDaily, tag.accountWeekly)
).filter { it.isNotEmpty() }.joinToString(" ")
}
}
} else {