more clean up

This commit is contained in:
tateisu 2021-06-22 17:31:51 +09:00
parent 73afdb7b59
commit ac2f54c22a
68 changed files with 5122 additions and 5089 deletions

View File

@ -166,6 +166,7 @@
<w>utoken</w>
<w>weblate</w>
<w>webpushcallback</w>
<w>webpushendpoint</w>
<w>webpushserverkey</w>
<w>webpushtokencheck</w>
<w>weiner</w>

View File

@ -424,7 +424,7 @@ class ActAccountSetting : AppCompatActivity(), View.OnClickListener,
btnNotificationSoundReset = findViewById(R.id.btnNotificationSoundReset)
btnNotificationStyleEdit = findViewById(R.id.btnNotificationStyleEdit)
btnNotificationStyleEditReply = findViewById(R.id.btnNotificationStyleEditReply)
btnNotificationStyleEditReply.vg(Pref.bpSeparateReplyNotificationGroup(pref))
btnNotificationStyleEditReply.vg(PrefB.bpSeparateReplyNotificationGroup(pref))
nameInvalidator = NetworkEmojiInvalidator(handler, etDisplayName)
noteInvalidator = NetworkEmojiInvalidator(handler, etNote)
@ -894,7 +894,7 @@ class ActAccountSetting : AppCompatActivity(), View.OnClickListener,
launchMain {
runApiTask(account) { client ->
client.authentication1(
Pref.spClientName(this@ActAccountSetting),
PrefS.spClientName(this@ActAccountSetting),
forceUpdateClient = true
)
}?.let { result ->

View File

@ -243,7 +243,7 @@ class ActMain : AppCompatActivity(),
override fun run() {
handler.removeCallbacks(this)
if (!isStartedEx) return
if (Pref.bpRelativeTimestamp(pref)) {
if (PrefB.bpRelativeTimestamp(pref)) {
appState.columnList.forEach { it.fireRelativeTime() }
handler.postDelayed(this, 10000L)
}
@ -324,7 +324,7 @@ class ActMain : AppCompatActivity(),
set(value) {
if (value != quickTootVisibility) {
quickTootVisibility = value
pref.edit().put(Pref.spQuickTootVisibility, value.id.toString()).apply()
pref.edit().put(PrefS.spQuickTootVisibility, value.id.toString()).apply()
showQuickTootVisibility()
}
}
@ -399,7 +399,7 @@ class ActMain : AppCompatActivity(),
},
{ env ->
val dbId = Pref.lpTabletTootDefaultAccount(App1.pref)
val dbId = PrefL.lpTabletTootDefaultAccount(App1.pref)
if (dbId != -1L) {
val a = SavedAccount.loadAccount(this@ActMain, dbId)
if (a != null && !a.isPseudo) return a
@ -547,17 +547,17 @@ class ActMain : AppCompatActivity(),
appState = App1.getAppState(this)
pref = App1.pref
EmojiDecoder.handleUnicodeEmoji = Pref.bpInAppUnicodeEmoji(pref)
EmojiDecoder.handleUnicodeEmoji = PrefB.bpInAppUnicodeEmoji(pref)
density = appState.density
acctPadLr = (0.5f + 4f * density).toInt()
timelineFontSizeSp = Pref.fpTimelineFontSize(pref).clipFontSize()
acctFontSizeSp = Pref.fpAcctFontSize(pref).clipFontSize()
notificationTlFontSizeSp = Pref.fpNotificationTlFontSize(pref).clipFontSize()
headerTextSizeSp = Pref.fpHeaderTextSize(pref).clipFontSize()
timelineFontSizeSp = PrefF.fpTimelineFontSize(pref).clipFontSize()
acctFontSizeSp = PrefF.fpAcctFontSize(pref).clipFontSize()
notificationTlFontSizeSp = PrefF.fpNotificationTlFontSize(pref).clipFontSize()
headerTextSizeSp = PrefF.fpHeaderTextSize(pref).clipFontSize()
val fv = Pref.spTimelineSpacing(pref).toFloatOrNull()
val fv = PrefS.spTimelineSpacing(pref).toFloatOrNull()
timelineSpacing = if (fv != null && fv.isFinite() && fv != 0f) fv else null
initUI()
@ -566,7 +566,7 @@ class ActMain : AppCompatActivity(),
if (appState.columnCount > 0) {
val columnPos = Pref.ipLastColumnPos(pref)
val columnPos = PrefI.ipLastColumnPos(pref)
log.d("ipLastColumnPos load $columnPos")
// 前回最後に表示していたカラムの位置にスクロールする
@ -667,14 +667,14 @@ class ActMain : AppCompatActivity(),
benchmark("reload color") {
// カラーカスタマイズを読み直す
ListDivider.color = Pref.ipListDividerColor(pref)
TabletColumnDivider.color = Pref.ipListDividerColor(pref)
ItemViewHolder.toot_color_unlisted = Pref.ipTootColorUnlisted(pref)
ItemViewHolder.toot_color_follower = Pref.ipTootColorFollower(pref)
ItemViewHolder.toot_color_direct_user = Pref.ipTootColorDirectUser(pref)
ItemViewHolder.toot_color_direct_me = Pref.ipTootColorDirectMe(pref)
MyClickableSpan.showLinkUnderline = Pref.bpShowLinkUnderline(pref)
MyClickableSpan.defaultLinkColor = Pref.ipLinkColor(pref).notZero()
ListDivider.color = PrefI.ipListDividerColor(pref)
TabletColumnDivider.color = PrefI.ipListDividerColor(pref)
ItemViewHolder.toot_color_unlisted = PrefI.ipTootColorUnlisted(pref)
ItemViewHolder.toot_color_follower = PrefI.ipTootColorFollower(pref)
ItemViewHolder.toot_color_direct_user = PrefI.ipTootColorDirectUser(pref)
ItemViewHolder.toot_color_direct_me = PrefI.ipTootColorDirectMe(pref)
MyClickableSpan.showLinkUnderline = PrefB.bpShowLinkUnderline(pref)
MyClickableSpan.defaultLinkColor = PrefI.ipLinkColor(pref).notZero()
?: attrColor(R.attr.colorLink)
CustomShare.reloadCache(this, pref)
@ -683,7 +683,7 @@ class ActMain : AppCompatActivity(),
benchmark("reload timezone") {
try {
var tz = TimeZone.getDefault()
val tzId = Pref.spTimeZone(pref)
val tzId = PrefS.spTimeZone(pref)
if (tzId.isNotEmpty()) {
tz = TimeZone.getTimeZone(tzId)
}
@ -831,7 +831,7 @@ class ActMain : AppCompatActivity(),
// TODO MyClickableSpan.link_callback = WeakReference(link_click_listener)
if (Pref.bpDontScreenOff(pref)) {
if (PrefB.bpDontScreenOff(pref)) {
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
} else {
window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
@ -859,7 +859,7 @@ class ActMain : AppCompatActivity(),
{ env -> env.pager.currentItem },
{ env -> env.visibleColumnsIndices.first })
log.d("ipLastColumnPos save $lastPos")
pref.edit().put(Pref.ipLastColumnPos, lastPos).apply()
pref.edit().put(PrefI.ipLastColumnPos, lastPos).apply()
appState.columnList.forEach { it.saveScrollPosition() }
@ -1004,8 +1004,8 @@ class ActMain : AppCompatActivity(),
this.postedRedraftId = null
}
val refreshAfterToot = Pref.ipRefreshAfterToot(pref)
if (refreshAfterToot != Pref.RAT_DONT_REFRESH) {
val refreshAfterToot = PrefI.ipRefreshAfterToot(pref)
if (refreshAfterToot != PrefI.RAT_DONT_REFRESH) {
appState.columnList
.filter { it.accessInfo.acct == postedAcct }
.forEach {
@ -1046,7 +1046,7 @@ class ActMain : AppCompatActivity(),
private fun performQuickPost(account: SavedAccount?) {
if (account == null) {
val a = if (tabletEnv != null && !Pref.bpQuickTootOmitAccountSelection(pref)) {
val a = if (tabletEnv != null && !PrefB.bpQuickTootOmitAccountSelection(pref)) {
// タブレットモードでオプションが無効なら
// 簡易投稿は常にアカウント選択する
null
@ -1150,19 +1150,19 @@ class ActMain : AppCompatActivity(),
}
// カラムが1個以上ある場合は設定に合わせて挙動を変える
when (Pref.ipBackButtonAction(pref)) {
when (PrefI.ipBackButtonAction(pref)) {
Pref.BACK_EXIT_APP -> this@ActMain.finish()
PrefI.BACK_EXIT_APP -> this@ActMain.finish()
Pref.BACK_OPEN_COLUMN_LIST -> openColumnList()
PrefI.BACK_OPEN_COLUMN_LIST -> openColumnList()
Pref.BACK_CLOSE_COLUMN -> {
PrefI.BACK_CLOSE_COLUMN -> {
val closeableColumnList = getClosableColumnList()
when (closeableColumnList.size) {
0 -> {
if (Pref.bpExitAppWhenCloseProtectedColumn(pref) &&
Pref.bpDontConfirmBeforeCloseColumn(pref)
if (PrefB.bpExitAppWhenCloseProtectedColumn(pref) &&
PrefB.bpDontConfirmBeforeCloseColumn(pref)
) {
this@ActMain.finish()
} else {
@ -1209,12 +1209,12 @@ class ActMain : AppCompatActivity(),
App1.initEdgeToEdge(this)
quickTootVisibility =
TootVisibility.parseSavedVisibility(Pref.spQuickTootVisibility(pref))
TootVisibility.parseSavedVisibility(PrefS.spQuickTootVisibility(pref))
?: quickTootVisibility
Column.reloadDefaultColor(this, pref)
var sv = Pref.spTimelineFont(pref)
var sv = PrefS.spTimelineFont(pref)
if (sv.isNotEmpty()) {
try {
timelineFont = Typeface.createFromFile(sv)
@ -1223,7 +1223,7 @@ class ActMain : AppCompatActivity(),
}
}
sv = Pref.spTimelineFontBold(pref)
sv = PrefS.spTimelineFontBold(pref)
if (sv.isNotEmpty()) {
try {
timeline_font_bold = Typeface.createFromFile(sv)
@ -1252,21 +1252,21 @@ class ActMain : AppCompatActivity(),
return (0.5f + iconSizeDp * density).toInt()
}
avatarIconSize = parseIconSize(Pref.spAvatarIconSize)
notificationTlIconSize = parseIconSize(Pref.spNotificationTlIconSize)
boostButtonSize = parseIconSize(Pref.spBoostButtonSize)
replyIconSize = parseIconSize(Pref.spReplyIconSize)
headerIconSize = parseIconSize(Pref.spHeaderIconSize)
stripIconSize = parseIconSize(Pref.spStripIconSize)
screenBottomPadding = parseIconSize(Pref.spScreenBottomPadding, minDp = 0f)
avatarIconSize = parseIconSize(PrefS.spAvatarIconSize)
notificationTlIconSize = parseIconSize(PrefS.spNotificationTlIconSize)
boostButtonSize = parseIconSize(PrefS.spBoostButtonSize)
replyIconSize = parseIconSize(PrefS.spReplyIconSize)
headerIconSize = parseIconSize(PrefS.spHeaderIconSize)
stripIconSize = parseIconSize(PrefS.spStripIconSize)
screenBottomPadding = parseIconSize(PrefS.spScreenBottomPadding, minDp = 0f)
run {
var roundRatio = 33f
try {
if (Pref.bpDontRound(pref)) {
if (PrefB.bpDontRound(pref)) {
roundRatio = 0f
} else {
sv = Pref.spRoundRatio(pref)
sv = PrefS.spRoundRatio(pref)
if (sv.isNotEmpty()) {
val fv = sv.toFloat()
if (fv.isFinite()) {
@ -1283,7 +1283,7 @@ class ActMain : AppCompatActivity(),
run {
var boostAlpha = 0.8f
try {
val f = (Pref.spBoostAlpha.toInt(pref).toFloat() + 0.5f) / 100f
val f = (PrefS.spBoostAlpha.toInt(pref).toFloat() + 0.5f) / 100f
boostAlpha = when {
f >= 1f -> 1f
f < 0f -> 0.66f
@ -1321,8 +1321,8 @@ class ActMain : AppCompatActivity(),
etQuickToot.typeface = timelineFont
when (Pref.ipJustifyWindowContentPortrait(pref)) {
Pref.JWCP_START -> {
when (PrefI.ipJustifyWindowContentPortrait(pref)) {
PrefI.JWCP_START -> {
val iconW = (stripIconSize * 1.5f + 0.5f).toInt()
val padding = resources.displayMetrics.widthPixels / 2 - iconW
@ -1339,7 +1339,7 @@ class ActMain : AppCompatActivity(),
)
}
Pref.JWCP_END -> {
PrefI.JWCP_END -> {
val iconW = (stripIconSize * 1.5f + 0.5f).toInt()
val borderWidth = (1f * density + 0.5f).toInt()
val padding = resources.displayMetrics.widthPixels / 2 - iconW - borderWidth
@ -1358,7 +1358,7 @@ class ActMain : AppCompatActivity(),
}
}
if (!Pref.bpQuickTootBar(pref)) {
if (!PrefB.bpQuickTootBar(pref)) {
llQuickTootBar.visibility = View.GONE
}
@ -1367,7 +1367,7 @@ class ActMain : AppCompatActivity(),
btnQuickToot.setOnClickListener(this)
btnQuickTootMenu.setOnClickListener(this)
if (Pref.bpDontUseActionButtonWithQuickTootBar(pref)) {
if (PrefB.bpDontUseActionButtonWithQuickTootBar(pref)) {
etQuickToot.inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_FLAG_MULTI_LINE
etQuickToot.imeOptions = EditorInfo.IME_ACTION_NONE
// 最後に指定する必要がある?
@ -1397,7 +1397,7 @@ class ActMain : AppCompatActivity(),
val density = dm.density
var mediaThumbHeightDp = 64
sv = Pref.spMediaThumbHeight(pref)
sv = PrefS.spMediaThumbHeight(pref)
if (sv.isNotEmpty()) {
try {
val iv = Integer.parseInt(sv)
@ -1411,7 +1411,7 @@ class ActMain : AppCompatActivity(),
appState.mediaThumbHeight = (0.5f + mediaThumbHeightDp * density).toInt()
var columnWMinDp = COLUMN_WIDTH_MIN_DP
sv = Pref.spColumnWidth(pref)
sv = PrefS.spColumnWidth(pref)
if (sv.isNotEmpty()) {
try {
val iv = Integer.parseInt(sv)
@ -1426,7 +1426,7 @@ class ActMain : AppCompatActivity(),
val sw = dm.widthPixels
if (Pref.bpDisableTabletMode(pref) || sw < columnWMin * 2) {
if (PrefB.bpDisableTabletMode(pref) || sw < columnWMin * 2) {
// SmartPhone mode
phoneEnv = PhoneEnv()
} else {
@ -1571,7 +1571,7 @@ class ActMain : AppCompatActivity(),
viewRoot.tag = index
viewRoot.setOnClickListener { v ->
val idx = v.tag as Int
if (Pref.bpScrollTopFromColumnStrip(pref) && isVisibleColumn(idx)) {
if (PrefB.bpScrollTopFromColumnStrip(pref) && isVisibleColumn(idx)) {
column.viewHolder?.scrollToTop2()
return@setOnClickListener
}
@ -1871,7 +1871,7 @@ class ActMain : AppCompatActivity(),
)
client.authentication2Misskey(
Pref.spClientName(pref),
PrefS.spClientName(pref),
token,
ti.misskeyVersion
)?.also {
@ -1936,7 +1936,7 @@ class ActMain : AppCompatActivity(),
val refToken = AtomicReference<String>(null)
client.authentication2Mastodon(
Pref.spClientName(pref),
PrefS.spClientName(pref),
code,
outAccessToken = refToken
)?.also { result ->
@ -2116,7 +2116,7 @@ class ActMain : AppCompatActivity(),
return
}
if (!bConfirmed && !Pref.bpDontConfirmBeforeCloseColumn(pref)) {
if (!bConfirmed && !PrefB.bpDontConfirmBeforeCloseColumn(pref)) {
AlertDialog.Builder(this)
.setMessage(R.string.confirm_close_column)
.setNegativeButton(R.string.cancel, null)
@ -2200,7 +2200,7 @@ class ActMain : AppCompatActivity(),
vararg params: Any,
): Column {
return addColumn(
Pref.bpAllowColumnDuplication(pref),
PrefB.bpAllowColumnDuplication(pref),
indexArg,
ai,
type,
@ -2242,11 +2242,11 @@ class ActMain : AppCompatActivity(),
fun showFooterColor() {
val footerButtonBgColor = Pref.ipFooterButtonBgColor(pref)
val footerButtonFgColor = Pref.ipFooterButtonFgColor(pref)
val footerTabBgColor = Pref.ipFooterTabBgColor(pref)
val footerTabDividerColor = Pref.ipFooterTabDividerColor(pref)
val footerTabIndicatorColor = Pref.ipFooterTabIndicatorColor(pref)
val footerButtonBgColor = PrefI.ipFooterButtonBgColor(pref)
val footerButtonFgColor = PrefI.ipFooterButtonFgColor(pref)
val footerTabBgColor = PrefI.ipFooterTabBgColor(pref)
val footerTabDividerColor = PrefI.ipFooterTabDividerColor(pref)
val footerTabIndicatorColor = PrefI.ipFooterTabIndicatorColor(pref)
val colorColumnStripBackground = footerTabBgColor.notZero()
?: attrColor(R.attr.colorColumnStripBackground)
@ -2377,7 +2377,7 @@ class ActMain : AppCompatActivity(),
private fun resizeColumnWidth(env: TabletEnv) {
var columnWMinDp = COLUMN_WIDTH_MIN_DP
val sv = Pref.spColumnWidth(pref)
val sv = PrefS.spColumnWidth(pref)
if (sv.isNotEmpty()) {
try {
val iv = Integer.parseInt(sv)
@ -2618,7 +2618,7 @@ class ActMain : AppCompatActivity(),
}
private fun resizeAutoCW(columnW: Int) {
val sv = Pref.spAutoCWLines(pref)
val sv = PrefS.spAutoCWLines(pref)
nAutoCwLines = sv.optInt() ?: -1
if (nAutoCwLines > 0) {
val lvPad = (0.5f + 12 * density).toInt()
@ -2720,7 +2720,7 @@ class ActMain : AppCompatActivity(),
// 同意ずみなら表示しない
val digest = bytes.digestSHA256().encodeBase64Url()
if (digest == Pref.spAgreedPrivacyPolicyDigest(pref)) return
if (digest == PrefS.spAgreedPrivacyPolicyDigest(pref)) return
val dialog = AlertDialog.Builder(this)
.setTitle(R.string.privacy_policy)
@ -2732,7 +2732,7 @@ class ActMain : AppCompatActivity(),
finish()
}
.setPositiveButton(R.string.agree) { _, _ ->
pref.edit().put(Pref.spAgreedPrivacyPolicyDigest, digest).apply()
pref.edit().put(PrefS.spAgreedPrivacyPolicyDigest, digest).apply()
}
.create()
dlgPrivacyPolicy = WeakReference(dialog)

View File

@ -84,7 +84,7 @@ class ActPost : AppCompatActivity(),
internal const val KEY_REPLY_STATUS = "reply_status"
internal const val KEY_REDRAFT_STATUS = "redraft_status"
internal const val KEY_INITIAL_TEXT = "initial_text"
internal const val KEY_SENT_INTENT = "sent_intent"
internal const val KEY_SHARED_INTENT = "sent_intent"
internal const val KEY_QUOTE = "quote"
internal const val KEY_SCHEDULED_STATUS = "scheduled_status"
@ -356,7 +356,7 @@ class ActPost : AppCompatActivity(),
}
if (sharedIntent != null) {
putExtra(KEY_SENT_INTENT, sharedIntent)
putExtra(KEY_SHARED_INTENT, sharedIntent)
}
if (scheduledStatus != null) {
@ -740,7 +740,7 @@ class ActPost : AppCompatActivity(),
setContentView(R.layout.act_post)
App1.initEdgeToEdge(this)
if (Pref.bpPostButtonBarTop(this)) {
if (PrefB.bpPostButtonBarTop(this)) {
val bar = findViewById<View>(R.id.llFooterBar)
val parent = bar.parent as ViewGroup
parent.removeView(bar)
@ -2061,7 +2061,7 @@ class ActPost : AppCompatActivity(),
}
lastAttachmentComplete = now
if (Pref.bpAppendAttachmentUrlToContent(pref)) {
if (PrefB.bpAppendAttachmentUrlToContent(pref)) {
// 投稿欄の末尾に追記する
val selStart = etContent.selectionStart
val selEnd = etContent.selectionEnd
@ -3046,13 +3046,42 @@ class ActPost : AppCompatActivity(),
accountList.find { it.db_id == accountDbId }?.let { selectAccount(it) }
}
val sentIntent = intent.getParcelableExtra<Intent>(KEY_SENT_INTENT)
if (sentIntent != null) {
val sharedIntent = intent.getParcelableExtra<Intent>(KEY_SHARED_INTENT)
if (sharedIntent != null) {
initializeFromSharedIntent(sharedIntent)
}
val hasUri = when (sentIntent.action) {
appendContentText(intent.getStringExtra(KEY_INITIAL_TEXT))
val account = this.account
if (account != null) {
intent.getStringExtra(KEY_REPLY_STATUS)
?.let { initializeFromReplyStatus(account, it) }
}
appendContentText(account?.default_text, selectBefore = true)
cbNSFW.isChecked = account?.default_sensitive ?: false
if (account != null) {
// 再編集
intent.getStringExtra(KEY_REDRAFT_STATUS)
?.let { initializeFromRedraftStatus(account, it) }
// 予約編集の再編集
intent.getStringExtra(KEY_SCHEDULED_STATUS)
?.let { initializeFromScheduledStatus(account, it) }
}
afterUpdateText()
}
private fun initializeFromSharedIntent(sharedIntent: Intent) {
try {
val hasUri = when (sharedIntent.action) {
Intent.ACTION_VIEW -> {
val uri = sentIntent.data
val type = sentIntent.type
val uri = sharedIntent.data
val type = sharedIntent.type
if (uri != null) {
addAttachment(uri, type)
true
@ -3062,8 +3091,8 @@ class ActPost : AppCompatActivity(),
}
Intent.ACTION_SEND -> {
val uri = sentIntent.getParcelableExtra<Uri>(Intent.EXTRA_STREAM)
val type = sentIntent.type
val uri = sharedIntent.getParcelableExtra<Uri>(Intent.EXTRA_STREAM)
val type = sharedIntent.type
if (uri != null) {
addAttachment(uri, type)
true
@ -3074,7 +3103,7 @@ class ActPost : AppCompatActivity(),
Intent.ACTION_SEND_MULTIPLE -> {
val listUri =
sentIntent.getParcelableArrayListExtra<Uri>(Intent.EXTRA_STREAM)
sharedIntent.getParcelableArrayListExtra<Uri>(Intent.EXTRA_STREAM)
?.filterNotNull()
if (listUri?.isNotEmpty() == true) {
for (uri in listUri) {
@ -3089,279 +3118,262 @@ class ActPost : AppCompatActivity(),
else -> false
}
if (!hasUri || !Pref.bpIgnoreTextInSharedMedia(pref)) {
appendContentText(sentIntent)
if (!hasUri || !PrefB.bpIgnoreTextInSharedMedia(pref)) {
appendContentText(sharedIntent)
}
} catch (ex: Throwable) {
log.trace(ex)
}
}
appendContentText(intent.getStringExtra(KEY_INITIAL_TEXT))
private fun initializeFromReplyStatus(account: SavedAccount, jsonText: String) {
try {
val replyStatus =
TootParser(this@ActPost, account).status(jsonText.decodeJsonObject())
?: error("initializeFromReplyStatus: parse failed.")
val account = this.account
val isQuote = intent.getBooleanExtra(KEY_QUOTE, false)
if (isQuote) {
cbQuote.isChecked = true
var sv = intent.getStringExtra(KEY_REPLY_STATUS)
if (sv != null && account != null) {
// 引用リートはCWやメンションを引き継がない
} else {
// CW をリプライ元に合わせる
if (replyStatus.spoiler_text.isNotEmpty()) {
cbContentWarning.isChecked = true
etContentWarning.setText(replyStatus.spoiler_text)
}
// 新しいメンションリスト
val mentionList = ArrayList<Acct>()
// 自己レス以外なら元レスへのメンションを追加
// 最初に追加する https://github.com/tateisu/SubwayTooter/issues/94
if (!account.isMe(replyStatus.account)) {
mentionList.add(account.getFullAcct(replyStatus.account))
}
// 元レスに含まれていたメンションを複製
replyStatus.mentions?.forEach { mention ->
val whoAcct = mention.acct
// 空データなら追加しない
if (!whoAcct.isValid) return@forEach
// 自分なら追加しない
if (account.isMe(whoAcct)) return@forEach
// 既出でないなら追加する
val acct = account.getFullAcct(whoAcct)
if (!mentionList.contains(acct)) mentionList.add(acct)
}
if (mentionList.isNotEmpty()) {
appendContentText(
StringBuilder().apply {
for (acct in mentionList) {
if (isNotEmpty()) append(' ')
append("@${acct.ascii}")
}
append(' ')
}.toString()
)
}
}
// リプライ表示をつける
inReplyToId = replyStatus.id
inReplyToText = replyStatus.content
inReplyToImage = replyStatus.account.avatar_static
inReplyToUrl = replyStatus.url
// 公開範囲
try {
val replyStatus =
TootParser(this@ActPost, account).status(sv.decodeJsonObject())
// 比較する前にデフォルトの公開範囲を計算する
val isQuote = intent.getBooleanExtra(KEY_QUOTE, false)
visibility = visibility
?: account.visibility
// ?: TootVisibility.Public
// VISIBILITY_WEB_SETTING だと 1.5未満のタンスでトラブルになる
if (replyStatus != null) {
if (visibility == TootVisibility.Unknown) {
visibility = TootVisibility.PrivateFollowers
}
if (isQuote) {
cbQuote.isChecked = true
val sample = when (val base = replyStatus.visibility) {
TootVisibility.Unknown -> TootVisibility.PrivateFollowers
else -> base
}
// 引用リートはCWやメンションを引き継がない
} else {
// CW をリプライ元に合わせる
if (replyStatus.spoiler_text.isNotEmpty()) {
cbContentWarning.isChecked = true
etContentWarning.setText(replyStatus.spoiler_text)
}
// 新しいメンションリスト
val mentionList = ArrayList<Acct>()
// 自己レス以外なら元レスへのメンションを追加
// 最初に追加する https://github.com/tateisu/SubwayTooter/issues/94
if (!account.isMe(replyStatus.account)) {
mentionList.add(account.getFullAcct(replyStatus.account))
}
// 元レスに含まれていたメンションを複製
replyStatus.mentions?.forEach { mention ->
val whoAcct = mention.acct
// 空データなら追加しない
if (!whoAcct.isValid) return@forEach
// 自分なら追加しない
if (account.isMe(whoAcct)) return@forEach
// 既出でないなら追加する
val acct = account.getFullAcct(whoAcct)
if (!mentionList.contains(acct)) mentionList.add(acct)
}
if (mentionList.isNotEmpty()) {
appendContentText(
StringBuilder().apply {
for (acct in mentionList) {
if (isNotEmpty()) append(' ')
append("@${acct.ascii}")
}
append(' ')
}.toString()
)
}
}
// リプライ表示をつける
inReplyToId = replyStatus.id
inReplyToText = replyStatus.content
inReplyToImage = replyStatus.account.avatar_static
inReplyToUrl = replyStatus.url
// 公開範囲
try {
// 比較する前にデフォルトの公開範囲を計算する
visibility = visibility
?: account.visibility
// ?: TootVisibility.Public
// VISIBILITY_WEB_SETTING だと 1.5未満のタンスでトラブルになる
if (visibility == TootVisibility.Unknown) {
visibility = TootVisibility.PrivateFollowers
}
val sample = when (val base = replyStatus.visibility) {
TootVisibility.Unknown -> TootVisibility.PrivateFollowers
else -> base
}
if (TootVisibility.WebSetting == visibility) {
// 「Web設定に合わせる」だった場合は無条件にリプライ元の公開範囲に変更する
this.visibility = sample
} else if (TootVisibility.isVisibilitySpoilRequired(
this.visibility, sample
)
) {
// デフォルトの方が公開範囲が大きい場合、リプライ元に合わせて公開範囲を狭める
this.visibility = sample
}
} catch (ex: Throwable) {
log.trace(ex)
}
if (TootVisibility.WebSetting == visibility) {
// 「Web設定に合わせる」だった場合は無条件にリプライ元の公開範囲に変更する
this.visibility = sample
} else if (TootVisibility.isVisibilitySpoilRequired(
this.visibility, sample
)
) {
// デフォルトの方が公開範囲が大きい場合、リプライ元に合わせて公開範囲を狭める
this.visibility = sample
}
} catch (ex: Throwable) {
log.trace(ex)
}
} catch (ex: Throwable) {
log.trace(ex)
}
}
appendContentText(account?.default_text, selectBefore = true)
private fun initializeFromRedraftStatus(account: SavedAccount, jsonText: String) {
try {
val baseStatus =
TootParser(this@ActPost, account).status(jsonText.decodeJsonObject())
?: error("initializeFromRedraftStatus: parse failed.")
cbNSFW.isChecked = account?.default_sensitive ?: false
redraftStatusId = baseStatus.id
// 再編集
sv = intent.getStringExtra(KEY_REDRAFT_STATUS)
if (sv != null && account != null) {
try {
val baseStatus =
TootParser(this@ActPost, account).status(sv.decodeJsonObject())
if (baseStatus != null) {
this.visibility = baseStatus.visibility
redraftStatusId = baseStatus.id
this.visibility = baseStatus.visibility
val srcAttachments = baseStatus.media_attachments
if (srcAttachments?.isNotEmpty() == true) {
updateStateAttachmentList()
this.attachmentList.clear()
try {
for (src in srcAttachments) {
if (src is TootAttachment) {
src.redraft = true
val pa = PostAttachment(src)
pa.status = PostAttachment.STATUS_UPLOADED
this.attachmentList.add(pa)
}
}
} catch (ex: Throwable) {
log.trace(ex)
val srcAttachments = baseStatus.media_attachments
if (srcAttachments?.isNotEmpty() == true) {
updateStateAttachmentList()
this.attachmentList.clear()
try {
for (src in srcAttachments) {
if (src is TootAttachment) {
src.redraft = true
val pa = PostAttachment(src)
pa.status = PostAttachment.STATUS_UPLOADED
this.attachmentList.add(pa)
}
}
} catch (ex: Throwable) {
log.trace(ex)
}
}
cbNSFW.isChecked = baseStatus.sensitive == true
cbNSFW.isChecked = baseStatus.sensitive == true
// 再編集の場合はdefault_textは反映されない
// 再編集の場合はdefault_textは反映されない
val decodeOptions = DecodeOptions(
this,
mentionFullAcct = true,
mentions = baseStatus.mentions,
mentionDefaultHostDomain = account
val decodeOptions = DecodeOptions(
this,
mentionFullAcct = true,
mentions = baseStatus.mentions,
mentionDefaultHostDomain = account
)
var text: CharSequence = if (account.isMisskey) {
baseStatus.content ?: ""
} else {
decodeOptions.decodeHTML(baseStatus.content)
}
etContent.setText(text)
etContent.setSelection(text.length)
text = decodeOptions.decodeEmoji(baseStatus.spoiler_text)
etContentWarning.setText(text)
etContentWarning.setSelection(text.length)
cbContentWarning.isChecked = text.isNotEmpty()
val srcEnquete = baseStatus.enquete
val srcItems = srcEnquete?.items
when {
srcItems == null -> {
//
}
srcEnquete.pollType == TootPollsType.FriendsNico &&
srcEnquete.type != TootPolls.TYPE_ENQUETE -> {
// フレニコAPIのアンケート結果は再編集の対象外
}
else -> {
spEnquete.setSelection(
if (srcEnquete.pollType == TootPollsType.FriendsNico) {
2
} else {
1
}
)
var text: CharSequence = if (account.isMisskey) {
baseStatus.content ?: ""
} else {
decodeOptions.decodeHTML(baseStatus.content)
}
etContent.setText(text)
text = decodeOptions.decodeHTML(srcEnquete.question)
etContent.text = text
etContent.setSelection(text.length)
text = decodeOptions.decodeEmoji(baseStatus.spoiler_text)
etContentWarning.setText(text)
etContentWarning.setSelection(text.length)
cbContentWarning.isChecked = text.isNotEmpty()
val srcEnquete = baseStatus.enquete
val srcItems = srcEnquete?.items
when {
srcItems == null -> {
//
}
srcEnquete.pollType == TootPollsType.FriendsNico && srcEnquete.type != TootPolls.TYPE_ENQUETE -> {
// フレニコAPIのアンケート結果は再編集の対象外
}
else -> {
spEnquete.setSelection(
if (srcEnquete.pollType == TootPollsType.FriendsNico) {
2
} else {
1
var srcIndex = 0
for (et in etChoices) {
if (srcIndex < srcItems.size) {
val choice = srcItems[srcIndex]
when {
srcIndex == srcItems.size - 1 && choice.text == "\uD83E\uDD14" -> {
// :thinking_face: は再現しない
}
)
text = decodeOptions.decodeHTML(srcEnquete.question)
etContent.text = text
etContent.setSelection(text.length)
var srcIndex = 0
loop@ for (et in etChoices) {
if (srcIndex < srcItems.size) {
val choice = srcItems[srcIndex]
when {
srcIndex == srcItems.size - 1 && choice.text == "\uD83E\uDD14" -> {
// :thinking_face: は再現しない
}
else -> {
et.setText(decodeOptions.decodeEmoji(choice.text))
++srcIndex
continue@loop
}
}
}
et.setText("")
}
}
}
}
} catch (ex: Throwable) {
log.trace(ex)
}
}
// 予約編集の再編集
sv = intent.getStringExtra(KEY_SCHEDULED_STATUS)
if (sv != null && account != null) {
try {
val item = parseItem(
::TootScheduled,
TootParser(this@ActPost, account),
sv.decodeJsonObject(),
log
)
if (item != null) {
scheduledStatus = item
timeSchedule = item.timeScheduledAt
val text = item.text
etContent.setText(text)
val cw = item.spoilerText
if (cw?.isNotEmpty() == true) {
etContentWarning.setText(cw)
cbContentWarning.isChecked = true
} else {
cbContentWarning.isChecked = false
}
visibility = item.visibility
// 2019/1/7 どうも添付データを古い投稿から引き継げないようだ…。
// 2019/1/22 https://github.com/tootsuite/mastodon/pull/9894 で直った。
val srcAttachments = item.mediaAttachments
if (srcAttachments?.isNotEmpty() == true) {
updateStateAttachmentList()
this.attachmentList.clear()
try {
for (src in srcAttachments) {
if (src is TootAttachment) {
src.redraft = true
val pa = PostAttachment(src)
pa.status = PostAttachment.STATUS_UPLOADED
this.attachmentList.add(pa)
else -> {
et.setText(decodeOptions.decodeEmoji(choice.text))
++srcIndex
continue
}
}
} catch (ex: Throwable) {
log.trace(ex)
}
et.setText("")
}
}
}
} catch (ex: Throwable) {
log.trace(ex)
}
}
private fun initializeFromScheduledStatus(account: SavedAccount, jsonText: String) {
try {
val item = parseItem(
::TootScheduled,
TootParser(this@ActPost, account),
jsonText.decodeJsonObject(),
log
) ?: error("initializeFromScheduledStatus: parse failed.")
scheduledStatus = item
timeSchedule = item.timeScheduledAt
val text = item.text
etContent.setText(text)
val cw = item.spoilerText
if (cw?.isNotEmpty() == true) {
etContentWarning.setText(cw)
cbContentWarning.isChecked = true
} else {
cbContentWarning.isChecked = false
}
visibility = item.visibility
// 2019/1/7 どうも添付データを古い投稿から引き継げないようだ…。
// 2019/1/22 https://github.com/tootsuite/mastodon/pull/9894 で直った。
val srcAttachments = item.mediaAttachments
if (srcAttachments?.isNotEmpty() == true) {
updateStateAttachmentList()
this.attachmentList.clear()
try {
for (src in srcAttachments) {
if (src is TootAttachment) {
src.redraft = true
val pa = PostAttachment(src)
pa.status = PostAttachment.STATUS_UPLOADED
this.attachmentList.add(pa)
}
}
cbNSFW.isChecked = item.sensitive
} catch (ex: Throwable) {
log.trace(ex)
}
} catch (ex: Throwable) {
log.trace(ex)
}
cbNSFW.isChecked = item.sensitive
} catch (ex: Throwable) {
log.trace(ex)
}
afterUpdateText()
}
override fun onMyClickableSpanClicked(viewClicked: View, span: MyClickableSpan) {

View File

@ -216,7 +216,7 @@ class App1 : Application() {
"SubwayTooter/${BuildConfig.VERSION_NAME} Android/${Build.VERSION.RELEASE}"
private fun getUserAgent(): String {
val userAgentCustom = Pref.spUserAgent(pref)
val userAgentCustom = PrefS.spUserAgent(pref)
return when {
userAgentCustom.isNotEmpty() && !reNotAllowedInUserAgent.matcher(userAgentCustom)
.find() -> userAgentCustom
@ -384,7 +384,7 @@ class App1 : Application() {
.build()
// 内蔵メディアビューア用のHTTP設定はタイムアウトを調整可能
val mediaReadTimeout = max(3, Pref.spMediaReadTimeout.toInt(pref))
val mediaReadTimeout = max(3, PrefS.spMediaReadTimeout.toInt(pref))
ok_http_client_media_viewer = prepareOkHttp(mediaReadTimeout, mediaReadTimeout)
.cache(cache)
.build()
@ -513,7 +513,7 @@ class App1 : Application() {
prepare(activity.applicationContext, "setActivityTheme")
val theme_idx = Pref.ipUiTheme(pref)
val theme_idx = PrefI.ipUiTheme(pref)
activity.setTheme(
if (forceDark || theme_idx == 1) {
if (noActionBar) R.style.AppTheme_Dark_NoActionBar else R.style.AppTheme_Dark

View File

@ -269,7 +269,7 @@ object AppDataExporter {
continue
}
when (val prefItem = Pref.map[k]) {
when (val prefItem = BasePref.allPref[k]) {
is BooleanPref -> e.putBoolean(k, reader.nextBoolean())
is IntPref -> e.putInt(k, reader.nextInt())
is LongPref -> e.putLong(k, reader.nextLong())
@ -395,10 +395,10 @@ object AppDataExporter {
}
run {
val old_id = Pref.lpTabletTootDefaultAccount(app_state.pref)
val old_id = PrefL.lpTabletTootDefaultAccount(app_state.pref)
if (old_id != -1L) {
val new_id = account_id_map[old_id]
app_state.pref.edit().put(Pref.lpTabletTootDefaultAccount, new_id ?: -1L).apply()
app_state.pref.edit().put(PrefL.lpTabletTootDefaultAccount, new_id ?: -1L).apply()
}
}

View File

@ -206,15 +206,15 @@ val appSettingRoot = AppSettingItem(null, SettingType.Section, R.string.app_sett
group(R.string.notification_style_before_oreo) {
checkbox(Pref.bpNotificationSound, R.string.sound) {
checkbox(PrefB.bpNotificationSound, R.string.sound) {
enabled = Build.VERSION.SDK_INT < 26
}
checkbox(Pref.bpNotificationVibration, R.string.vibration) {
checkbox(PrefB.bpNotificationVibration, R.string.vibration) {
enabled = Build.VERSION.SDK_INT < 26
}
checkbox(Pref.bpNotificationLED, R.string.led) {
checkbox(PrefB.bpNotificationLED, R.string.led) {
enabled = Build.VERSION.SDK_INT < 26
}
@ -222,26 +222,26 @@ val appSettingRoot = AppSettingItem(null, SettingType.Section, R.string.app_sett
}
text(
Pref.spPullNotificationCheckInterval,
PrefS.spPullNotificationCheckInterval,
R.string.pull_notification_check_interval,
InputTypeEx.number
)
sw(Pref.bpShowAcctInSystemNotification, R.string.show_acct_in_system_notification)
sw(PrefB.bpShowAcctInSystemNotification, R.string.show_acct_in_system_notification)
sw(Pref.bpSeparateReplyNotificationGroup, R.string.separate_notification_group_for_reply) {
sw(PrefB.bpSeparateReplyNotificationGroup, R.string.separate_notification_group_for_reply) {
enabled = Build.VERSION.SDK_INT >= 26
}
sw(Pref.bpDivideNotification, R.string.divide_notification)
sw(PrefB.bpDivideNotification, R.string.divide_notification)
}
section(R.string.behavior) {
sw(Pref.bpDontConfirmBeforeCloseColumn, R.string.dont_confirm_before_close_column)
sw(PrefB.bpDontConfirmBeforeCloseColumn, R.string.dont_confirm_before_close_column)
spinner(
Pref.ipBackButtonAction,
PrefI.ipBackButtonAction,
R.string.back_button_action,
R.string.ask_always,
R.string.close_column,
@ -249,15 +249,15 @@ val appSettingRoot = AppSettingItem(null, SettingType.Section, R.string.app_sett
R.string.app_exit
)
sw(Pref.bpExitAppWhenCloseProtectedColumn, R.string.exit_app_when_close_protected_column)
sw(Pref.bpScrollTopFromColumnStrip, R.string.scroll_top_from_column_strip)
sw(Pref.bpDontScreenOff, R.string.dont_screen_off)
sw(Pref.bpDontUseCustomTabs, R.string.dont_use_custom_tabs)
sw(Pref.bpPriorChrome, R.string.prior_chrome_custom_tabs)
sw(PrefB.bpExitAppWhenCloseProtectedColumn, R.string.exit_app_when_close_protected_column)
sw(PrefB.bpScrollTopFromColumnStrip, R.string.scroll_top_from_column_strip)
sw(PrefB.bpDontScreenOff, R.string.dont_screen_off)
sw(PrefB.bpDontUseCustomTabs, R.string.dont_use_custom_tabs)
sw(PrefB.bpPriorChrome, R.string.prior_chrome_custom_tabs)
item(
SettingType.TextWithSelector,
Pref.spWebBrowser,
PrefS.spWebBrowser,
R.string.web_browser
) {
onClickEdit = {
@ -292,23 +292,23 @@ val appSettingRoot = AppSettingItem(null, SettingType.Section, R.string.app_sett
}
}
sw(Pref.bpAllowColumnDuplication, R.string.allow_column_duplication)
sw(Pref.bpForceGap, R.string.force_gap_when_refresh)
sw(PrefB.bpAllowColumnDuplication, R.string.allow_column_duplication)
sw(PrefB.bpForceGap, R.string.force_gap_when_refresh)
spinner(
Pref.ipGapHeadScrollPosition,
PrefI.ipGapHeadScrollPosition,
R.string.scroll_position_after_read_gap_from_head,
R.string.gap_head,
R.string.gap_tail,
)
spinner(
Pref.ipGapTailScrollPosition,
PrefI.ipGapTailScrollPosition,
R.string.scroll_position_after_read_gap_from_tail,
R.string.gap_head,
R.string.gap_tail,
)
text(Pref.spClientName, R.string.client_name, InputTypeEx.text)
text(PrefS.spClientName, R.string.client_name, InputTypeEx.text)
text(Pref.spUserAgent, R.string.user_agent, InputTypeEx.textMultiLine) {
text(PrefS.spUserAgent, R.string.user_agent, InputTypeEx.textMultiLine) {
hint = App1.userAgentDefault
filter = { it.replace(ActAppSetting.reLinefeed, " ").trim() }
getError = {
@ -320,13 +320,13 @@ val appSettingRoot = AppSettingItem(null, SettingType.Section, R.string.app_sett
}
}
sw(Pref.bpDontRemoveDeletedToot, R.string.dont_remove_deleted_toot_from_timeline)
sw(Pref.bpCustomEmojiSeparatorZwsp, R.string.custom_emoji_separator_zwsp)
sw(Pref.bpShowTranslateButton, R.string.show_translate_button)
sw(PrefB.bpDontRemoveDeletedToot, R.string.dont_remove_deleted_toot_from_timeline)
sw(PrefB.bpCustomEmojiSeparatorZwsp, R.string.custom_emoji_separator_zwsp)
sw(PrefB.bpShowTranslateButton, R.string.show_translate_button)
item(
SettingType.TextWithSelector,
Pref.spTranslateAppComponent,
PrefS.spTranslateAppComponent,
R.string.translation_app
) {
val target = CustomShareTarget.Translate
@ -337,7 +337,7 @@ val appSettingRoot = AppSettingItem(null, SettingType.Section, R.string.app_sett
item(
SettingType.TextWithSelector,
Pref.spCustomShare1,
PrefS.spCustomShare1,
R.string.custom_share_button_1
) {
val target = CustomShareTarget.CustomShare1
@ -348,7 +348,7 @@ val appSettingRoot = AppSettingItem(null, SettingType.Section, R.string.app_sett
item(
SettingType.TextWithSelector,
Pref.spCustomShare2,
PrefS.spCustomShare2,
R.string.custom_share_button_2
) {
val target = CustomShareTarget.CustomShare2
@ -358,7 +358,7 @@ val appSettingRoot = AppSettingItem(null, SettingType.Section, R.string.app_sett
}
item(
SettingType.TextWithSelector,
Pref.spCustomShare3,
PrefS.spCustomShare3,
R.string.custom_share_button_3
) {
val target = CustomShareTarget.CustomShare3
@ -368,22 +368,19 @@ val appSettingRoot = AppSettingItem(null, SettingType.Section, R.string.app_sett
}
spinner(
Pref.ipAdditionalButtonsPosition,
PrefI.ipAdditionalButtonsPosition,
R.string.additional_buttons_position,
R.string.top,
R.string.bottom,
R.string.start,
R.string.end
*(AdditionalButtonsPosition.values().sortedBy { it.idx }.map { it.captionId }.toIntArray())
)
sw(Pref.bpEnablePixelfed, R.string.enable_connect_to_pixelfed_server)
sw(Pref.bpShowFilteredWord, R.string.show_filtered_word)
sw(Pref.bpEnableDomainTimeline, R.string.enable_domain_timeline)
sw(PrefB.bpEnablePixelfed, R.string.enable_connect_to_pixelfed_server)
sw(PrefB.bpShowFilteredWord, R.string.show_filtered_word)
sw(PrefB.bpEnableDomainTimeline, R.string.enable_domain_timeline)
}
section(R.string.post) {
// spinner(Pref.ipResizeImage, R.string.resize_image) { activity ->
// spinner(PrefI.ipResizeImage, R.string.resize_image) { activity ->
// ActPost.resizeConfigList.map {
// when (it.type) {
// ResizeType.None -> activity.getString(R.string.dont_resize)
@ -397,67 +394,67 @@ val appSettingRoot = AppSettingItem(null, SettingType.Section, R.string.app_sett
// }
// }
//
// text(Pref.spMediaSizeMax, R.string.media_attachment_max_byte_size, InputTypeEx.number)
// text(Pref.spMovieSizeMax, R.string.media_attachment_max_byte_size_movie, InputTypeEx.number)
// text(PrefS.spMediaSizeMax, R.string.media_attachment_max_byte_size, InputTypeEx.number)
// text(PrefS.spMovieSizeMax, R.string.media_attachment_max_byte_size_movie, InputTypeEx.number)
// text(
// Pref.spMediaSizeMaxPixelfed,
// PrefS.spMediaSizeMaxPixelfed,
// R.string.media_attachment_max_byte_size_pixelfed,
// InputTypeEx.number
// )
spinner(
Pref.ipRefreshAfterToot,
PrefI.ipRefreshAfterToot,
R.string.refresh_after_toot,
R.string.refresh_scroll_to_toot,
R.string.refresh_no_scroll,
R.string.dont_refresh
)
sw(Pref.bpPostButtonBarTop, R.string.show_post_button_bar_top)
sw(PrefB.bpPostButtonBarTop, R.string.show_post_button_bar_top)
sw(
Pref.bpDontDuplicationCheck,
PrefB.bpDontDuplicationCheck,
R.string.dont_add_duplication_check_header
)
sw(Pref.bpQuickTootBar, R.string.show_quick_toot_bar)
sw(PrefB.bpQuickTootBar, R.string.show_quick_toot_bar)
sw(
Pref.bpDontUseActionButtonWithQuickTootBar,
PrefB.bpDontUseActionButtonWithQuickTootBar,
R.string.dont_use_action_button_with_quick_toot_bar
)
text(Pref.spQuoteNameFormat, R.string.format_of_quote_name, InputTypeEx.text) {
text(PrefS.spQuoteNameFormat, R.string.format_of_quote_name, InputTypeEx.text) {
filter = { it } // don't trim
}
sw(
Pref.bpAppendAttachmentUrlToContent,
PrefB.bpAppendAttachmentUrlToContent,
R.string.append_attachment_url_to_content
)
sw(
Pref.bpWarnHashtagAsciiAndNonAscii,
PrefB.bpWarnHashtagAsciiAndNonAscii,
R.string.warn_hashtag_ascii_and_non_ascii
)
sw(
Pref.bpEmojiPickerCloseOnSelected,
PrefB.bpEmojiPickerCloseOnSelected,
R.string.close_emoji_picker_when_selected
)
sw(Pref.bpIgnoreTextInSharedMedia, R.string.ignore_text_in_shared_media)
sw(PrefB.bpIgnoreTextInSharedMedia, R.string.ignore_text_in_shared_media)
}
section(R.string.tablet_mode) {
sw(Pref.bpDisableTabletMode, R.string.disable_tablet_mode)
sw(PrefB.bpDisableTabletMode, R.string.disable_tablet_mode)
text(Pref.spColumnWidth, R.string.minimum_column_width, InputTypeEx.number)
text(PrefS.spColumnWidth, R.string.minimum_column_width, InputTypeEx.number)
item(
SettingType.Spinner,
Pref.lpTabletTootDefaultAccount,
PrefL.lpTabletTootDefaultAccount,
R.string.toot_button_default_account
) {
val lp = pref.cast<LongPref>()!!
@ -474,12 +471,12 @@ val appSettingRoot = AppSettingItem(null, SettingType.Section, R.string.app_sett
}
sw(
Pref.bpQuickTootOmitAccountSelection,
PrefB.bpQuickTootOmitAccountSelection,
R.string.quick_toot_omit_account_selection
)
spinner(
Pref.ipJustifyWindowContentPortrait,
PrefI.ipJustifyWindowContentPortrait,
R.string.justify_window_content_portrait,
R.string.default_,
R.string.start,
@ -487,39 +484,39 @@ val appSettingRoot = AppSettingItem(null, SettingType.Section, R.string.app_sett
)
sw(
Pref.bpMultiWindowPost,
PrefB.bpMultiWindowPost,
R.string.multi_window_post
)
sw(
Pref.bpManyWindowPost,
PrefB.bpManyWindowPost,
R.string.many_window_post
)
}
section(R.string.media_attachment) {
sw(Pref.bpUseInternalMediaViewer, R.string.use_internal_media_viewer)
sw(Pref.bpPriorLocalURL, R.string.prior_local_url_when_open_attachment)
text(Pref.spMediaThumbHeight, R.string.media_thumbnail_height, InputTypeEx.number)
sw(Pref.bpDontCropMediaThumb, R.string.dont_crop_media_thumbnail)
sw(Pref.bpVerticalArrangeThumbnails, R.string.thumbnails_arrange_vertically)
sw(PrefB.bpUseInternalMediaViewer, R.string.use_internal_media_viewer)
sw(PrefB.bpPriorLocalURL, R.string.prior_local_url_when_open_attachment)
text(PrefS.spMediaThumbHeight, R.string.media_thumbnail_height, InputTypeEx.number)
sw(PrefB.bpDontCropMediaThumb, R.string.dont_crop_media_thumbnail)
sw(PrefB.bpVerticalArrangeThumbnails, R.string.thumbnails_arrange_vertically)
}
section(R.string.animation) {
sw(Pref.bpEnableGifAnimation, R.string.enable_gif_animation)
sw(Pref.bpDisableEmojiAnimation, R.string.disable_custom_emoji_animation)
sw(PrefB.bpEnableGifAnimation, R.string.enable_gif_animation)
sw(PrefB.bpDisableEmojiAnimation, R.string.disable_custom_emoji_animation)
}
section(R.string.appearance) {
sw(Pref.bpSimpleList, R.string.simple_list)
sw(Pref.bpShowFollowButtonInButtonBar, R.string.show_follow_button_in_button_bar)
sw(Pref.bpDontShowPreviewCard, R.string.dont_show_preview_card)
sw(Pref.bpShortAcctLocalUser, R.string.short_acct_local_user)
sw(Pref.bpMentionFullAcct, R.string.mention_full_acct)
sw(Pref.bpRelativeTimestamp, R.string.relative_timestamp)
sw(PrefB.bpSimpleList, R.string.simple_list)
sw(PrefB.bpShowFollowButtonInButtonBar, R.string.show_follow_button_in_button_bar)
sw(PrefB.bpDontShowPreviewCard, R.string.dont_show_preview_card)
sw(PrefB.bpShortAcctLocalUser, R.string.short_acct_local_user)
sw(PrefB.bpMentionFullAcct, R.string.mention_full_acct)
sw(PrefB.bpRelativeTimestamp, R.string.relative_timestamp)
item(
SettingType.Spinner,
Pref.spTimeZone,
PrefS.spTimeZone,
R.string.timezone
) {
val sp: StringPref = pref.cast()!!
@ -535,27 +532,27 @@ val appSettingRoot = AppSettingItem(null, SettingType.Section, R.string.app_sett
}
}
sw(Pref.bpShowAppName, R.string.always_show_application)
sw(Pref.bpShowLanguage, R.string.always_show_language)
text(Pref.spAutoCWLines, R.string.auto_cw, InputTypeEx.number)
text(Pref.spCardDescriptionLength, R.string.card_description_length, InputTypeEx.number)
sw(PrefB.bpShowAppName, R.string.always_show_application)
sw(PrefB.bpShowLanguage, R.string.always_show_language)
text(PrefS.spAutoCWLines, R.string.auto_cw, InputTypeEx.number)
text(PrefS.spCardDescriptionLength, R.string.card_description_length, InputTypeEx.number)
spinner(
Pref.ipRepliesCount,
PrefI.ipRepliesCount,
R.string.display_replies_count,
R.string.replies_count_simple,
R.string.replies_count_actual,
R.string.replies_count_none
)
spinner(
Pref.ipBoostsCount,
PrefI.ipBoostsCount,
R.string.display_boost_count,
R.string.replies_count_simple,
R.string.replies_count_actual,
R.string.replies_count_none
)
spinner(
Pref.ipFavouritesCount,
PrefI.ipFavouritesCount,
R.string.display_favourite_count,
R.string.replies_count_simple,
R.string.replies_count_actual,
@ -563,7 +560,7 @@ val appSettingRoot = AppSettingItem(null, SettingType.Section, R.string.app_sett
)
spinner(
Pref.ipVisibilityStyle,
PrefI.ipVisibilityStyle,
R.string.visibility_style,
R.string.visibility_style_by_account,
R.string.mastodon,
@ -572,7 +569,7 @@ val appSettingRoot = AppSettingItem(null, SettingType.Section, R.string.app_sett
AppSettingItem.TIMELINE_FONT = item(
SettingType.TextWithSelector,
Pref.spTimelineFont,
PrefS.spTimelineFont,
R.string.timeline_font
) {
val item = this
@ -593,7 +590,7 @@ val appSettingRoot = AppSettingItem(null, SettingType.Section, R.string.app_sett
AppSettingItem.TIMELINE_FONT_BOLD = item(
SettingType.TextWithSelector,
Pref.spTimelineFontBold,
PrefS.spTimelineFontBold,
R.string.timeline_font_bold
) {
val item = this
@ -613,7 +610,7 @@ val appSettingRoot = AppSettingItem(null, SettingType.Section, R.string.app_sett
}
AppSettingItem.FONT_SIZE_TIMELINE = textX(
Pref.fpTimelineFontSize,
PrefF.fpTimelineFontSize,
R.string.timeline_font_size,
InputTypeEx.numberDecimal
) {
@ -627,20 +624,20 @@ val appSettingRoot = AppSettingItem(null, SettingType.Section, R.string.app_sett
captionFontSize = {
val fv = fp(pref)
when {
!fv.isFinite() -> Pref.default_timeline_font_size
!fv.isFinite() -> PrefF.default_timeline_font_size
fv < 1f -> 1f
else -> fv
}
}
captionSpacing = {
Pref.spTimelineSpacing(pref).toFloatOrNull()
PrefS.spTimelineSpacing(pref).toFloatOrNull()
}
changed = {
findItemViewHolder(item)?.updateCaption()
}
}
textX(Pref.fpAcctFontSize, R.string.acct_font_size, InputTypeEx.numberDecimal) {
textX(PrefF.fpAcctFontSize, R.string.acct_font_size, InputTypeEx.numberDecimal) {
val item = this
val fp: FloatPref = item.pref.cast()!!
@ -650,7 +647,7 @@ val appSettingRoot = AppSettingItem(null, SettingType.Section, R.string.app_sett
captionFontSize = {
val fv = fp(pref)
when {
!fv.isFinite() -> Pref.default_acct_font_size
!fv.isFinite() -> PrefF.default_acct_font_size
fv < 1f -> 1f
else -> fv
}
@ -660,7 +657,7 @@ val appSettingRoot = AppSettingItem(null, SettingType.Section, R.string.app_sett
}
AppSettingItem.FONT_SIZE_NOTIFICATION_TL = textX(
Pref.fpNotificationTlFontSize,
PrefF.fpNotificationTlFontSize,
R.string.notification_tl_font_size,
InputTypeEx.numberDecimal
) {
@ -673,13 +670,13 @@ val appSettingRoot = AppSettingItem(null, SettingType.Section, R.string.app_sett
captionFontSize = {
val fv = fp(pref)
when {
!fv.isFinite() -> Pref.default_notification_tl_font_size
!fv.isFinite() -> PrefF.default_notification_tl_font_size
fv < 1f -> 1f
else -> fv
}
}
captionSpacing = {
Pref.spTimelineSpacing(pref).toFloatOrNull()
PrefS.spTimelineSpacing(pref).toFloatOrNull()
}
changed = {
findItemViewHolder(item)?.updateCaption()
@ -687,34 +684,34 @@ val appSettingRoot = AppSettingItem(null, SettingType.Section, R.string.app_sett
}
text(
Pref.spNotificationTlIconSize,
PrefS.spNotificationTlIconSize,
R.string.notification_tl_icon_size,
InputTypeEx.numberDecimal
)
text(Pref.spTimelineSpacing, R.string.timeline_line_spacing, InputTypeEx.numberDecimal) {
text(PrefS.spTimelineSpacing, R.string.timeline_line_spacing, InputTypeEx.numberDecimal) {
changed = {
findItemViewHolder(AppSettingItem.FONT_SIZE_TIMELINE)?.updateCaption()
findItemViewHolder(AppSettingItem.FONT_SIZE_NOTIFICATION_TL)?.updateCaption()
}
}
text(Pref.spBoostButtonSize, R.string.boost_button_size, InputTypeEx.numberDecimal)
text(PrefS.spBoostButtonSize, R.string.boost_button_size, InputTypeEx.numberDecimal)
spinner(
Pref.ipBoostButtonJustify,
PrefI.ipBoostButtonJustify,
R.string.boost_button_alignment,
R.string.start,
R.string.center,
R.string.end
)
text(Pref.spAvatarIconSize, R.string.avatar_icon_size, InputTypeEx.numberDecimal)
text(Pref.spRoundRatio, R.string.avatar_icon_round_ratio, InputTypeEx.numberDecimal)
sw(Pref.bpDontRound, R.string.avatar_icon_dont_round)
text(Pref.spReplyIconSize, R.string.reply_icon_size, InputTypeEx.numberDecimal)
text(Pref.spHeaderIconSize, R.string.header_icon_size, InputTypeEx.numberDecimal)
textX(Pref.fpHeaderTextSize, R.string.header_text_size, InputTypeEx.numberDecimal) {
text(PrefS.spAvatarIconSize, R.string.avatar_icon_size, InputTypeEx.numberDecimal)
text(PrefS.spRoundRatio, R.string.avatar_icon_round_ratio, InputTypeEx.numberDecimal)
sw(PrefB.bpDontRound, R.string.avatar_icon_dont_round)
text(PrefS.spReplyIconSize, R.string.reply_icon_size, InputTypeEx.numberDecimal)
text(PrefS.spHeaderIconSize, R.string.header_icon_size, InputTypeEx.numberDecimal)
textX(PrefF.fpHeaderTextSize, R.string.header_text_size, InputTypeEx.numberDecimal) {
val item = this
val fp: FloatPref = item.pref.cast()!!
@ -724,7 +721,7 @@ val appSettingRoot = AppSettingItem(null, SettingType.Section, R.string.app_sett
captionFontSize = {
val fv = fp(pref)
when {
!fv.isFinite() -> Pref.default_header_font_size
!fv.isFinite() -> PrefF.default_header_font_size
fv < 1f -> 1f
else -> fv
}
@ -735,95 +732,95 @@ val appSettingRoot = AppSettingItem(null, SettingType.Section, R.string.app_sett
}
}
text(Pref.spStripIconSize, R.string.strip_icon_size, InputTypeEx.numberDecimal)
text(PrefS.spStripIconSize, R.string.strip_icon_size, InputTypeEx.numberDecimal)
text(Pref.spScreenBottomPadding, R.string.screen_bottom_padding, InputTypeEx.numberDecimal)
text(PrefS.spScreenBottomPadding, R.string.screen_bottom_padding, InputTypeEx.numberDecimal)
sw(Pref.bpOpenSticker, R.string.show_open_sticker) {
sw(PrefB.bpOpenSticker, R.string.show_open_sticker) {
desc = R.string.powered_by_open_sticker
descClick = { openBrowser("https://github.com/cutls/OpenSticker") }
}
sw(Pref.bpLinksInContextMenu, R.string.show_links_in_context_menu)
sw(Pref.bpShowLinkUnderline, R.string.show_link_underline)
sw(PrefB.bpLinksInContextMenu, R.string.show_links_in_context_menu)
sw(PrefB.bpShowLinkUnderline, R.string.show_link_underline)
sw(
Pref.bpMoveNotificationsQuickFilter,
PrefB.bpMoveNotificationsQuickFilter,
R.string.move_notifications_quick_filter_to_column_setting
)
sw(Pref.bpShowSearchClear, R.string.show_clear_button_in_search_bar)
sw(PrefB.bpShowSearchClear, R.string.show_clear_button_in_search_bar)
sw(
Pref.bpDontShowColumnBackgroundImage,
PrefB.bpDontShowColumnBackgroundImage,
R.string.dont_show_column_background_image
)
group(R.string.show_in_directory) {
checkbox(Pref.bpDirectoryLastActive, R.string.last_active)
checkbox(Pref.bpDirectoryFollowers, R.string.followers)
checkbox(Pref.bpDirectoryTootCount, R.string.toot_count)
checkbox(Pref.bpDirectoryNote, R.string.note)
checkbox(PrefB.bpDirectoryLastActive, R.string.last_active)
checkbox(PrefB.bpDirectoryFollowers, R.string.followers)
checkbox(PrefB.bpDirectoryTootCount, R.string.toot_count)
checkbox(PrefB.bpDirectoryNote, R.string.note)
}
sw(
Pref.bpAlwaysExpandContextMenuItems,
PrefB.bpAlwaysExpandContextMenuItems,
R.string.always_expand_context_menu_sub_items
)
sw(Pref.bpShowBookmarkButton, R.string.show_bookmark_button)
sw(Pref.bpHideFollowCount, R.string.hide_followers_count)
sw(Pref.bpEmojioneShortcode, R.string.emojione_shortcode_support) {
sw(PrefB.bpShowBookmarkButton, R.string.show_bookmark_button)
sw(PrefB.bpHideFollowCount, R.string.hide_followers_count)
sw(PrefB.bpEmojioneShortcode, R.string.emojione_shortcode_support) {
desc = R.string.emojione_shortcode_support_desc
}
sw(Pref.bpInAppUnicodeEmoji, R.string.in_app_unicode_emoji)
sw(PrefB.bpInAppUnicodeEmoji, R.string.in_app_unicode_emoji)
sw(Pref.bpKeepReactionSpace, R.string.keep_reaction_space)
sw(PrefB.bpKeepReactionSpace, R.string.keep_reaction_space)
}
section(R.string.color) {
spinner(
Pref.ipUiTheme,
PrefI.ipUiTheme,
R.string.ui_theme,
R.string.theme_light,
R.string.theme_dark
)
colorAlpha(Pref.ipListDividerColor, R.string.list_divider_color)
colorAlpha(Pref.ipLinkColor, R.string.link_color)
colorAlpha(PrefI.ipListDividerColor, R.string.list_divider_color)
colorAlpha(PrefI.ipLinkColor, R.string.link_color)
group(R.string.toot_background_color) {
colorAlpha(Pref.ipTootColorUnlisted, R.string.unlisted_visibility)
colorAlpha(Pref.ipTootColorFollower, R.string.followers_visibility)
colorAlpha(Pref.ipTootColorDirectUser, R.string.direct_with_user_visibility)
colorAlpha(Pref.ipTootColorDirectMe, R.string.direct_only_me_visibility)
colorAlpha(PrefI.ipTootColorUnlisted, R.string.unlisted_visibility)
colorAlpha(PrefI.ipTootColorFollower, R.string.followers_visibility)
colorAlpha(PrefI.ipTootColorDirectUser, R.string.direct_with_user_visibility)
colorAlpha(PrefI.ipTootColorDirectMe, R.string.direct_only_me_visibility)
}
group(R.string.event_background_color) {
colorAlpha(Pref.ipEventBgColorBoost, R.string.boost)
colorAlpha(Pref.ipEventBgColorFavourite, R.string.favourites)
colorAlpha(Pref.ipEventBgColorBookmark, R.string.bookmarks)
colorAlpha(Pref.ipEventBgColorMention, R.string.reply)
colorAlpha(Pref.ipEventBgColorFollow, R.string.follow)
colorAlpha(Pref.ipEventBgColorUnfollow, R.string.unfollow_misskey)
colorAlpha(Pref.ipEventBgColorFollowRequest, R.string.follow_request)
colorAlpha(Pref.ipEventBgColorReaction, R.string.reaction)
colorAlpha(Pref.ipEventBgColorQuote, R.string.quote_renote)
colorAlpha(Pref.ipEventBgColorVote, R.string.vote_polls)
colorAlpha(Pref.ipEventBgColorStatus, R.string.status)
colorAlpha(PrefI.ipEventBgColorBoost, R.string.boost)
colorAlpha(PrefI.ipEventBgColorFavourite, R.string.favourites)
colorAlpha(PrefI.ipEventBgColorBookmark, R.string.bookmarks)
colorAlpha(PrefI.ipEventBgColorMention, R.string.reply)
colorAlpha(PrefI.ipEventBgColorFollow, R.string.follow)
colorAlpha(PrefI.ipEventBgColorUnfollow, R.string.unfollow_misskey)
colorAlpha(PrefI.ipEventBgColorFollowRequest, R.string.follow_request)
colorAlpha(PrefI.ipEventBgColorReaction, R.string.reaction)
colorAlpha(PrefI.ipEventBgColorQuote, R.string.quote_renote)
colorAlpha(PrefI.ipEventBgColorVote, R.string.vote_polls)
colorAlpha(PrefI.ipEventBgColorStatus, R.string.status)
colorAlpha(
Pref.ipConversationMainTootBgColor,
PrefI.ipConversationMainTootBgColor,
R.string.conversation_main_toot_background_color
)
colorAlpha(Pref.ipEventBgColorGap, R.string.gap)
colorAlpha(PrefI.ipEventBgColorGap, R.string.gap)
}
group(R.string.button_accent_color) {
colorAlpha(Pref.ipButtonBoostedColor, R.string.boost)
colorAlpha(Pref.ipButtonFavoritedColor, R.string.favourites)
colorAlpha(Pref.ipButtonBookmarkedColor, R.string.bookmarks)
colorAlpha(Pref.ipButtonFollowingColor, R.string.follow)
colorAlpha(Pref.ipButtonFollowRequestColor, R.string.follow_request)
colorAlpha(Pref.ipButtonReactionedColor, R.string.reaction)
colorAlpha(PrefI.ipButtonBoostedColor, R.string.boost)
colorAlpha(PrefI.ipButtonFavoritedColor, R.string.favourites)
colorAlpha(PrefI.ipButtonBookmarkedColor, R.string.bookmarks)
colorAlpha(PrefI.ipButtonFollowingColor, R.string.follow)
colorAlpha(PrefI.ipButtonFollowRequestColor, R.string.follow_request)
colorAlpha(PrefI.ipButtonReactionedColor, R.string.reaction)
}
group(R.string.column_color_default) {
@ -834,8 +831,8 @@ val appSettingRoot = AppSettingItem(null, SettingType.Section, R.string.app_sett
val ivColumnHeader: ImageView = viewRoot.findViewById(R.id.ivColumnHeader)
val tvColumnName: TextView = viewRoot.findViewById(R.id.tvColumnName)
val colorColumnHeaderBg = Pref.ipCcdHeaderBg(activity.pref)
val colorColumnHeaderFg = Pref.ipCcdHeaderFg(activity.pref)
val colorColumnHeaderBg = PrefI.ipCcdHeaderBg(activity.pref)
val colorColumnHeaderFg = PrefI.ipCcdHeaderFg(activity.pref)
val headerBg = when {
colorColumnHeaderBg != 0 -> colorColumnHeaderBg
@ -854,10 +851,10 @@ val appSettingRoot = AppSettingItem(null, SettingType.Section, R.string.app_sett
ivColumnHeader.imageTintList = ColorStateList.valueOf(headerFg)
}
colorOpaque(Pref.ipCcdHeaderBg, R.string.header_background_color) {
colorOpaque(PrefI.ipCcdHeaderBg, R.string.header_background_color) {
changed = { showSample(AppSettingItem.SAMPLE_CCD_HEADER) }
}
colorOpaque(Pref.ipCcdHeaderFg, R.string.header_foreground_color) {
colorOpaque(PrefI.ipCcdHeaderFg, R.string.header_foreground_color) {
changed = { showSample(AppSettingItem.SAMPLE_CCD_HEADER) }
}
@ -867,9 +864,9 @@ val appSettingRoot = AppSettingItem(null, SettingType.Section, R.string.app_sett
val tvSampleAcct: TextView = viewRoot.findViewById(R.id.tvSampleAcct)
val tvSampleContent: TextView = viewRoot.findViewById(R.id.tvSampleContent)
val colorColumnBg = Pref.ipCcdContentBg(activity.pref)
val colorColumnAcct = Pref.ipCcdContentAcct(activity.pref)
val colorColumnText = Pref.ipCcdContentText(activity.pref)
val colorColumnBg = PrefI.ipCcdContentBg(activity.pref)
val colorColumnAcct = PrefI.ipCcdContentAcct(activity.pref)
val colorColumnText = PrefI.ipCcdContentText(activity.pref)
flColumnBackground.setBackgroundColor(colorColumnBg) // may 0
@ -884,18 +881,18 @@ val appSettingRoot = AppSettingItem(null, SettingType.Section, R.string.app_sett
)
}
colorOpaque(Pref.ipCcdContentBg, R.string.content_background_color) {
colorOpaque(PrefI.ipCcdContentBg, R.string.content_background_color) {
changed = { showSample(AppSettingItem.SAMPLE_CCD_BODY) }
}
colorAlpha(Pref.ipCcdContentAcct, R.string.content_acct_color) {
colorAlpha(PrefI.ipCcdContentAcct, R.string.content_acct_color) {
changed = { showSample(AppSettingItem.SAMPLE_CCD_BODY) }
}
colorAlpha(Pref.ipCcdContentText, R.string.content_text_color) {
colorAlpha(PrefI.ipCcdContentText, R.string.content_text_color) {
changed = { showSample(AppSettingItem.SAMPLE_CCD_BODY) }
}
}
text(Pref.spBoostAlpha, R.string.boost_button_alpha, InputTypeEx.numberDecimal)
text(PrefS.spBoostAlpha, R.string.boost_button_alpha, InputTypeEx.numberDecimal)
group(R.string.footer_color) {
AppSettingItem.SAMPLE_FOOTER =
@ -908,11 +905,11 @@ val appSettingRoot = AppSettingItem(null, SettingType.Section, R.string.app_sett
val vFooterDivider2: View = viewRoot.findViewById(R.id.vFooterDivider2)
val vIndicator: View = viewRoot.findViewById(R.id.vIndicator)
val footerButtonBgColor = Pref.ipFooterButtonBgColor(pref)
val footerButtonFgColor = Pref.ipFooterButtonFgColor(pref)
val footerTabBgColor = Pref.ipFooterTabBgColor(pref)
val footerTabDividerColor = Pref.ipFooterTabDividerColor(pref)
val footerTabIndicatorColor = Pref.ipFooterTabIndicatorColor(pref)
val footerButtonBgColor = PrefI.ipFooterButtonBgColor(pref)
val footerButtonFgColor = PrefI.ipFooterButtonFgColor(pref)
val footerTabBgColor = PrefI.ipFooterTabBgColor(pref)
val footerTabDividerColor = PrefI.ipFooterTabDividerColor(pref)
val footerTabIndicatorColor = PrefI.ipFooterTabIndicatorColor(pref)
val colorColumnStripBackground = footerTabBgColor.notZero()
?: activity.attrColor(R.attr.colorColumnStripBackground)
@ -948,46 +945,46 @@ val appSettingRoot = AppSettingItem(null, SettingType.Section, R.string.app_sett
)
}
colorOpaque(Pref.ipFooterButtonBgColor, R.string.button_background_color) {
colorOpaque(PrefI.ipFooterButtonBgColor, R.string.button_background_color) {
changed = { showSample(AppSettingItem.SAMPLE_FOOTER) }
}
colorOpaque(Pref.ipFooterButtonFgColor, R.string.button_foreground_color) {
colorOpaque(PrefI.ipFooterButtonFgColor, R.string.button_foreground_color) {
changed = { showSample(AppSettingItem.SAMPLE_FOOTER) }
}
colorOpaque(Pref.ipFooterTabBgColor, R.string.quick_toot_bar_background_color) {
colorOpaque(PrefI.ipFooterTabBgColor, R.string.quick_toot_bar_background_color) {
changed = { showSample(AppSettingItem.SAMPLE_FOOTER) }
}
colorOpaque(Pref.ipFooterTabDividerColor, R.string.tab_divider_color) {
colorOpaque(PrefI.ipFooterTabDividerColor, R.string.tab_divider_color) {
changed = { showSample(AppSettingItem.SAMPLE_FOOTER) }
}
colorAlpha(Pref.ipFooterTabIndicatorColor, R.string.tab_indicator_color) {
colorAlpha(PrefI.ipFooterTabIndicatorColor, R.string.tab_indicator_color) {
changed = { showSample(AppSettingItem.SAMPLE_FOOTER) }
}
}
colorOpaque(Pref.ipSwitchOnColor, R.string.switch_button_color) {
colorOpaque(PrefI.ipSwitchOnColor, R.string.switch_button_color) {
changed = { setSwitchColor() }
}
colorOpaque(Pref.ipStatusBarColor, R.string.status_bar_color) {
colorOpaque(PrefI.ipStatusBarColor, R.string.status_bar_color) {
changed = { setStatusBarColor() }
}
colorOpaque(Pref.ipNavigationBarColor, R.string.navigation_bar_color) {
colorOpaque(PrefI.ipNavigationBarColor, R.string.navigation_bar_color) {
changed = { setStatusBarColor() }
}
colorOpaque(Pref.ipSearchBgColor, R.string.search_bar_background_color)
colorAlpha(Pref.ipAnnouncementsBgColor, R.string.announcement_background_color)
colorAlpha(Pref.ipVerifiedLinkBgColor, R.string.verified_link_background_color)
colorAlpha(Pref.ipVerifiedLinkFgColor, R.string.verified_link_foreground_color)
colorOpaque(PrefI.ipSearchBgColor, R.string.search_bar_background_color)
colorAlpha(PrefI.ipAnnouncementsBgColor, R.string.announcement_background_color)
colorAlpha(PrefI.ipVerifiedLinkBgColor, R.string.verified_link_background_color)
colorAlpha(PrefI.ipVerifiedLinkFgColor, R.string.verified_link_foreground_color)
}
section(R.string.performance) {
sw(Pref.bpShareViewPool, R.string.share_view_pool)
sw(Pref.bpDontUseStreaming, R.string.dont_use_streaming_api)
sw(Pref.bpDontRefreshOnResume, R.string.dont_refresh_on_activity_resume)
text(Pref.spMediaReadTimeout, R.string.timeout_for_embed_media_viewer, InputTypeEx.number)
sw(PrefB.bpShareViewPool, R.string.share_view_pool)
sw(PrefB.bpDontUseStreaming, R.string.dont_use_streaming_api)
sw(PrefB.bpDontRefreshOnResume, R.string.dont_refresh_on_activity_resume)
text(PrefS.spMediaReadTimeout, R.string.timeout_for_embed_media_viewer, InputTypeEx.number)
action(R.string.delete_custom_emoji_cache) {
action = {
App1.custom_emoji_cache.delete()
@ -996,9 +993,9 @@ val appSettingRoot = AppSettingItem(null, SettingType.Section, R.string.app_sett
}
section(R.string.developer_options) {
sw(Pref.bpCheckBetaVersion, R.string.check_beta_release)
sw(PrefB.bpCheckBetaVersion, R.string.check_beta_release)
sw(Pref.bpEmojiPickerCategoryOther, R.string.show_emoji_picker_other_category)
sw(PrefB.bpEmojiPickerCategoryOther, R.string.show_emoji_picker_other_category)
action(R.string.drawable_list) {
action = { startActivity(Intent(this, ActDrawableList::class.java)) }
}

View File

@ -90,22 +90,22 @@ class Column(
fun reloadDefaultColor(activity: AppCompatActivity, pref: SharedPreferences) {
defaultColorHeaderBg = Pref.ipCcdHeaderBg(pref).notZero()
defaultColorHeaderBg = PrefI.ipCcdHeaderBg(pref).notZero()
?: activity.attrColor(R.attr.color_column_header)
defaultColorHeaderName = Pref.ipCcdHeaderFg(pref).notZero()
defaultColorHeaderName = PrefI.ipCcdHeaderFg(pref).notZero()
?: activity.attrColor(R.attr.colorColumnHeaderName)
defaultColorHeaderPageNumber = Pref.ipCcdHeaderFg(pref).notZero()
defaultColorHeaderPageNumber = PrefI.ipCcdHeaderFg(pref).notZero()
?: activity.attrColor(R.attr.colorColumnHeaderPageNumber)
defaultColorContentBg = Pref.ipCcdContentBg(pref)
defaultColorContentBg = PrefI.ipCcdContentBg(pref)
// may zero
defaultColorContentAcct = Pref.ipCcdContentAcct(pref).notZero()
defaultColorContentAcct = PrefI.ipCcdContentAcct(pref).notZero()
?: activity.attrColor(R.attr.colorTimeSmall)
defaultColorContentText = Pref.ipCcdContentText(pref).notZero()
defaultColorContentText = PrefI.ipCcdContentText(pref).notZero()
?: activity.attrColor(R.attr.colorContentText)
}

View File

@ -239,7 +239,7 @@ fun Column.onActivityStart() {
if (!bRefreshLoading &&
canAutoRefresh() &&
!Pref.bpDontRefreshOnResume(appState.pref) &&
!PrefB.bpDontRefreshOnResume(appState.pref) &&
!dontAutoRefresh
) {
// リフレッシュしてからストリーミング開始
@ -279,7 +279,7 @@ fun Column.startLoading() {
initFilter()
Column.showOpenSticker = Pref.bpOpenSticker(appState.pref)
Column.showOpenSticker = PrefB.bpOpenSticker(appState.pref)
mRefreshLoadingErrorPopupState = 0
mRefreshLoadingError = ""

View File

@ -112,12 +112,11 @@ class ColumnTask_Gap(
return
}
val iv = if (isHead) {
Pref.ipGapHeadScrollPosition
} else {
Pref.ipGapTailScrollPosition
val iv = when {
isHead -> PrefI.ipGapHeadScrollPosition
else -> PrefI.ipGapTailScrollPosition
}.invoke(pref)
val scrollHead = iv == Pref.GSP_HEAD
val scrollHead = iv == PrefI.GSP_HEAD
if (scrollHead) {
// ギャップを頭から読んだ場合、スクロール位置の調整は不要

View File

@ -23,7 +23,7 @@ class ColumnTask_Loading(
override suspend fun background(): TootApiResult? {
ctStarted.set(true)
if (Pref.bpOpenSticker(pref)) {
if (PrefB.bpOpenSticker(pref)) {
OpenSticker.loadAndWait()
}

View File

@ -161,7 +161,7 @@ class ColumnTask_Refresh(
column.listData.addAll(0, listNew)
column.fireShowContent(reason = "refresh updated head", changeList = changeList)
if (statusIndex >= 0 && refreshAfterToot == Pref.RAT_REFRESH_SCROLL) {
if (statusIndex >= 0 && refreshAfterToot == PrefI.RAT_REFRESH_SCROLL) {
// 投稿後にその投稿にスクロールする
if (holder != null) {
holder.setScrollPosition(
@ -286,7 +286,7 @@ class ColumnTask_Refresh(
isCancelled -> false
listTmp?.isNotEmpty() != true -> false
willAddGap -> true
else -> Pref.bpForceGap(App1.pref)
else -> PrefB.bpForceGap(App1.pref)
}
if (doesAddGap()) {
@ -474,7 +474,7 @@ class ColumnTask_Refresh(
if (!isCancelled &&
listTmp?.isNotEmpty() == true &&
(willAddGap || Pref.bpForceGap(context))
(willAddGap || PrefB.bpForceGap(context))
) {
addOne(listTmp, TootGap.mayNull(maxId, lastSinceId), head = addToHead)
}
@ -561,7 +561,7 @@ class ColumnTask_Refresh(
if (!isCancelled &&
listTmp?.isNotEmpty() == true &&
(willAddGap || Pref.bpForceGap(context))
(willAddGap || PrefB.bpForceGap(context))
) {
addOne(listTmp, TootGap.mayNull(maxId, lastSinceId), head = addToHead)
}

File diff suppressed because it is too large Load Diff

View File

@ -61,22 +61,12 @@ fun ColumnViewHolder.showAnnouncements(force: Boolean = true) {
return
}
lastAnnouncementShown = SystemClock.elapsedRealtime()
fun clearExtras() {
for (invalidator in extraInvalidatorList) {
invalidator.register(null)
}
extraInvalidatorList.clear()
}
llAnnouncementExtra.removeAllViews()
clearExtras()
val listShown = TootAnnouncement.filterShown(column.announcements)
if (listShown?.isEmpty() != false) {
btnAnnouncements.vg(false)
llAnnouncementsBox.vg(false)
btnAnnouncementsBadge.vg(false)
llColumnHeader.invalidate()
showAnnouncementsEmpty()
return
}
@ -97,15 +87,41 @@ fun ColumnViewHolder.showAnnouncements(force: Boolean = true) {
return
}
val contentColor = column.getContentColor()
val item = listShown.find { it.id == column.announcementId }
?: listShown[0]
val itemIndex = listShown.indexOf(item)
val enablePaging = listShown.size > 1
val contentColor = column.getContentColor()
showAnnouncementColors(expand, enablePaging, contentColor)
showAnnouncementFonts()
tvAnnouncementsIndex.vg(expand)?.text =
activity.getString(R.string.announcements_index, itemIndex + 1, listShown.size)
llAnnouncements.vg(expand)
showAnnouncementContent(item, contentColor)
showReactionBox(column, item, contentColor)
}
private fun ColumnViewHolder.clearExtras() {
for (invalidator in extraInvalidatorList) {
invalidator.register(null)
}
extraInvalidatorList.clear()
}
private fun ColumnViewHolder.showAnnouncementsEmpty() {
btnAnnouncements.vg(false)
llAnnouncementsBox.vg(false)
btnAnnouncementsBadge.vg(false)
llColumnHeader.invalidate()
}
private fun ColumnViewHolder.showAnnouncementColors(expand: Boolean, enablePaging: Boolean, contentColor: Int) {
val alphaPrevNext = if (enablePaging) 1f else 0.3f
setIconDrawableId(
@ -134,7 +150,9 @@ fun ColumnViewHolder.showAnnouncements(force: Boolean = true) {
tvAnnouncementsCaption.textColor = contentColor
tvAnnouncementsIndex.textColor = contentColor
tvAnnouncementPeriod.textColor = contentColor
}
private fun ColumnViewHolder.showAnnouncementFonts() {
val f = activity.timelineFontSizeSp
if (!f.isNaN()) {
tvAnnouncementsCaption.textSize = f
@ -152,11 +170,9 @@ fun ColumnViewHolder.showAnnouncements(force: Boolean = true) {
tvAnnouncementsIndex.typeface = fontNormal
tvAnnouncementPeriod.typeface = fontNormal
tvAnnouncementContent.typeface = fontNormal
}
tvAnnouncementsIndex.vg(expand)?.text =
activity.getString(R.string.announcements_index, itemIndex + 1, listShown.size)
llAnnouncements.vg(expand)
private fun ColumnViewHolder.showAnnouncementContent(item: TootAnnouncement, contentColor: Int) {
var periods: StringBuilder? = null
fun String.appendPeriod() {
val sb = periods
@ -168,14 +184,9 @@ fun ColumnViewHolder.showAnnouncements(force: Boolean = true) {
}
}
val (strStart, strEnd) = TootStatus.formatTimeRange(
item.starts_at,
item.ends_at,
item.all_day
)
val (strStart, strEnd) = TootStatus.formatTimeRange(item.starts_at, item.ends_at, item.all_day)
when {
// no periods.
strStart == "" && strEnd == "" -> {
}
@ -199,19 +210,23 @@ fun ColumnViewHolder.showAnnouncements(force: Boolean = true) {
val sb = periods
tvAnnouncementPeriod.vg(sb != null)?.text = sb
tvAnnouncementContent.textColor = contentColor
tvAnnouncementContent.text = item.decoded_content
tvAnnouncementContent.tag = this@showAnnouncements
tvAnnouncementContent.tag = this
announcementContentInvalidator.register(item.decoded_content)
}
private fun ColumnViewHolder.showReactionBox(
column: Column,
item: TootAnnouncement,
contentColor: Int,
) {
// リアクションの表示
val density = activity.density
val buttonHeight = ActMain.boostButtonSize
val marginBetween = (buttonHeight.toFloat() * 0.2f + 0.5f).toInt()
val marginBottom = (buttonHeight.toFloat() * 0.2f + 0.5f).toInt()
val paddingH = (buttonHeight.toFloat() * 0.1f + 0.5f).toInt()
val paddingV = (buttonHeight.toFloat() * 0.1f + 0.5f).toInt()
@ -226,135 +241,155 @@ fun ColumnViewHolder.showAnnouncements(force: Boolean = true) {
topMargin = (0.5f + density * 3f).toInt()
}
}
// +ボタン
run {
val b = ImageButton(activity)
val blp = FlexboxLayout.LayoutParams(
buttonHeight,
buttonHeight
).apply {
bottomMargin = marginBottom
endMargin = marginBetween
}
b.layoutParams = blp
b.background = ContextCompat.getDrawable(
activity,
R.drawable.btn_bg_transparent_round6dp
)
b.contentDescription = activity.getString(R.string.reaction_add)
b.scaleType = ImageView.ScaleType.FIT_CENTER
b.padding = paddingV
b.setOnClickListener {
reactionAdd(item, null)
}
setIconDrawableId(
activity,
b,
R.drawable.ic_add,
color = contentColor,
alphaMultiplier = 1f
)
box.addView(b)
}
val reactions = item.reactions?.filter { it.count > 0L }?.notEmpty()
if (reactions != null) {
var lastButton: View? = null
val options = DecodeOptions(
activity,
column.accessInfo,
decodeEmoji = true,
enlargeEmoji = 1.5f,
mentionDefaultHostDomain = column.accessInfo
)
val actMain = activity
val disableEmojiAnimation = Pref.bpDisableEmojiAnimation(actMain.pref)
for (reaction in reactions) {
val url = if (disableEmojiAnimation) {
reaction.staticUrl.notEmpty() ?: reaction.url.notEmpty()
} else {
reaction.url.notEmpty() ?: reaction.staticUrl.notEmpty()
}
val b = Button(activity).also { btn ->
btn.layoutParams = FlexboxLayout.LayoutParams(
FlexboxLayout.LayoutParams.WRAP_CONTENT,
buttonHeight
).apply {
endMargin = marginBetween
bottomMargin = marginBottom
}
btn.minWidthCompat = buttonHeight
btn.allCaps = false
btn.tag = reaction
btn.background = if (reaction.me) {
getAdaptiveRippleDrawableRound(
actMain,
actMain.attrColor(R.attr.colorButtonBgCw),
actMain.attrColor(R.attr.colorRippleEffect)
)
} else {
ContextCompat.getDrawable(actMain, R.drawable.btn_bg_transparent_round6dp)
}
btn.setTextColor(contentColor)
btn.setPadding(paddingH, paddingV, paddingH, paddingV)
btn.text = if (url == null) {
EmojiDecoder.decodeEmoji(options, "${reaction.name} ${reaction.count}")
} else {
SpannableStringBuilder("${reaction.name} ${reaction.count}").also { sb ->
sb.setSpan(
NetworkEmojiSpan(url, scale = 1.5f),
0,
reaction.name.length,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
)
val invalidator =
NetworkEmojiInvalidator(actMain.handler, btn)
invalidator.register(sb)
extraInvalidatorList.add(invalidator)
}
}
btn.setOnClickListener {
if (reaction.me) {
reactionRemove(item, reaction.name)
} else {
reactionAdd(item, TootReaction.parseFedibird(jsonObject {
put("name", reaction.name)
put("count", 1)
put("me", true)
putNotNull("url", reaction.url)
putNotNull("static_url", reaction.staticUrl)
}))
}
}
}
box.addView(b)
lastButton = b
}
lastButton
?.layoutParams
?.cast<ViewGroup.MarginLayoutParams>()
?.endMargin = 0
}
showReactionPlus(box, item, buttonHeight, marginBetween, contentColor, paddingV)
item.reactions?.filter { it.count > 0L }
?.notEmpty()
?.let { showReactions(box, item, it, column, buttonHeight, marginBetween, contentColor, paddingH, paddingV) }
llAnnouncementExtra.addView(box)
}
private fun ColumnViewHolder.showReactionPlus(
box: FlexboxLayout,
item: TootAnnouncement,
buttonHeight: Int,
marginBetween: Int,
contentColor: Int,
paddingV: Int,
) {
val b = ImageButton(activity)
val blp = FlexboxLayout.LayoutParams(
buttonHeight,
buttonHeight
).apply {
bottomMargin = marginBottom
endMargin = marginBetween
}
b.layoutParams = blp
b.background = ContextCompat.getDrawable(
activity,
R.drawable.btn_bg_transparent_round6dp
)
b.contentDescription = activity.getString(R.string.reaction_add)
b.scaleType = ImageView.ScaleType.FIT_CENTER
b.padding = paddingV
b.setOnClickListener {
reactionAdd(item, null)
}
setIconDrawableId(
activity,
b,
R.drawable.ic_add,
color = contentColor,
alphaMultiplier = 1f
)
box.addView(b)
}
private fun ColumnViewHolder.showReactions(
box: FlexboxLayout,
item: TootAnnouncement,
reactions: List<TootReaction>,
column: Column,
buttonHeight: Int,
marginBetween: Int,
contentColor: Int,
paddingH: Int,
paddingV: Int,
) {
var lastButton: View? = null
val options = DecodeOptions(
activity,
column.accessInfo,
decodeEmoji = true,
enlargeEmoji = 1.5f,
mentionDefaultHostDomain = column.accessInfo
)
val actMain = activity
val disableEmojiAnimation = PrefB.bpDisableEmojiAnimation(actMain.pref)
for (reaction in reactions) {
val url = if (disableEmojiAnimation) {
reaction.staticUrl.notEmpty() ?: reaction.url.notEmpty()
} else {
reaction.url.notEmpty() ?: reaction.staticUrl.notEmpty()
}
val b = Button(activity).also { btn ->
btn.layoutParams = FlexboxLayout.LayoutParams(
FlexboxLayout.LayoutParams.WRAP_CONTENT,
buttonHeight
).apply {
endMargin = marginBetween
bottomMargin = marginBottom
}
btn.minWidthCompat = buttonHeight
btn.allCaps = false
btn.tag = reaction
btn.background = if (reaction.me) {
getAdaptiveRippleDrawableRound(
actMain,
actMain.attrColor(R.attr.colorButtonBgCw),
actMain.attrColor(R.attr.colorRippleEffect)
)
} else {
ContextCompat.getDrawable(actMain, R.drawable.btn_bg_transparent_round6dp)
}
btn.setTextColor(contentColor)
btn.setPadding(paddingH, paddingV, paddingH, paddingV)
btn.text = if (url == null) {
EmojiDecoder.decodeEmoji(options, "${reaction.name} ${reaction.count}")
} else {
SpannableStringBuilder("${reaction.name} ${reaction.count}").also { sb ->
sb.setSpan(
NetworkEmojiSpan(url, scale = 1.5f),
0,
reaction.name.length,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
)
val invalidator =
NetworkEmojiInvalidator(actMain.handler, btn)
invalidator.register(sb)
extraInvalidatorList.add(invalidator)
}
}
btn.setOnClickListener {
if (reaction.me) {
reactionRemove(item, reaction.name)
} else {
reactionAdd(item, TootReaction.parseFedibird(jsonObject {
put("name", reaction.name)
put("count", 1)
put("me", true)
putNotNull("url", reaction.url)
putNotNull("static_url", reaction.staticUrl)
}))
}
}
}
box.addView(b)
lastButton = b
}
lastButton
?.layoutParams
?.cast<ViewGroup.MarginLayoutParams>()
?.endMargin = 0
}
fun ColumnViewHolder.reactionAdd(item: TootAnnouncement, sample: TootReaction?) {
val column = column ?: return
if (sample == null) {

View File

@ -31,7 +31,7 @@ fun ColumnViewHolder.closeBitmaps() {
fun ColumnViewHolder.loadBackgroundImage(iv: ImageView, url: String?) {
try {
if (url == null || url.isEmpty() || Pref.bpDontShowColumnBackgroundImage(activity.pref)) {
if (url == null || url.isEmpty() || PrefB.bpDontShowColumnBackgroundImage(activity.pref)) {
// 指定がないなら閉じる
closeBitmaps()
return
@ -111,7 +111,7 @@ fun ColumnViewHolder.onPageCreate(column: Column, pageIdx: Int, pageCount: Int)
ColumnViewHolder.log.d("onPageCreate [$pageIdx] ${column.getColumnName(true)}")
val bSimpleList =
column.type != ColumnType.CONVERSATION && Pref.bpSimpleList(activity.pref)
column.type != ColumnType.CONVERSATION && PrefB.bpSimpleList(activity.pref)
tvColumnIndex.text = activity.getString(R.string.column_index, pageIdx + 1, pageCount)
tvColumnStatus.text = "?"
@ -203,7 +203,7 @@ fun ColumnViewHolder.onPageCreate(column: Column, pageIdx: Int, pageCount: Int)
btnEmojiAdd.vg(false)
etSearch.vg(true)
btnSearchClear.vg(Pref.bpShowSearchClear(activity.pref))
btnSearchClear.vg(PrefB.bpShowSearchClear(activity.pref))
cbResolve.vg(column.type == ColumnType.SEARCH)
}
@ -254,7 +254,7 @@ fun ColumnViewHolder.onPageCreate(column: Column, pageIdx: Int, pageCount: Int)
listView.adapter = statusAdapter
//XXX FastScrollerのサポートを諦める。ライブラリはいくつかあるんだけど、設定でON/OFFできなかったり頭文字バブルを無効にできなかったり
// listView.isFastScrollEnabled = ! Pref.bpDisableFastScroller(Pref.pref(activity))
// listView.isFastScrollEnabled = ! PrefB.bpDisableFastScroller(Pref.pref(activity))
column.addColumnViewHolder(this)
@ -263,7 +263,7 @@ fun ColumnViewHolder.onPageCreate(column: Column, pageIdx: Int, pageCount: Int)
fun dip(dp: Int): Int = (activity.density * dp + 0.5f).toInt()
val context = activity
val announcementsBgColor = Pref.ipAnnouncementsBgColor(App1.pref).notZero()
val announcementsBgColor = PrefI.ipAnnouncementsBgColor(App1.pref).notZero()
?: context.attrColor(R.attr.colorSearchFormBackground)
btnAnnouncementsCutout.apply {
@ -276,7 +276,7 @@ fun ColumnViewHolder.onPageCreate(column: Column, pageIdx: Int, pageCount: Int)
setPadding(0, padV, 0, padV)
}
val searchBgColor = Pref.ipSearchBgColor(App1.pref).notZero()
val searchBgColor = PrefI.ipSearchBgColor(App1.pref).notZero()
?: context.attrColor(R.attr.colorSearchFormBackground)
llSearch.apply {

View File

@ -27,7 +27,7 @@ fun ColumnViewHolder.showQuickFilter() {
btnQuickFilterReaction.vg(column.isMisskey)
btnQuickFilterFavourite.vg(!column.isMisskey)
val insideColumnSetting = Pref.bpMoveNotificationsQuickFilter(activity.pref)
val insideColumnSetting = PrefB.bpMoveNotificationsQuickFilter(activity.pref)
val showQuickFilterButton: (btn: View, iconId: Int, selected: Boolean) -> Unit

View File

@ -138,7 +138,7 @@ internal fun ColumnViewHolder.showContent(
refreshLayout.isRefreshing = false
showRefreshError()
}
procRestorescrollposition.run()
procRestoreScrollPosition.run()
}
fun ColumnViewHolder.showColumnSetting(show: Boolean): Boolean {

View File

@ -266,7 +266,7 @@ internal class DlgContextMenu(
} else {
val statusByMe = accessInfo.isMe(status.account)
if (Pref.bpLinksInContextMenu(activity.pref) && contentTextView != null) {
if (PrefB.bpLinksInContextMenu(activity.pref) && contentTextView != null) {
var insPos = 0
@ -344,11 +344,11 @@ internal class DlgContextMenu(
llNotification.vg(notification != null)
val colorButtonAccent =
Pref.ipButtonFollowingColor(activity.pref).notZero()
PrefI.ipButtonFollowingColor(activity.pref).notZero()
?: activity.attrColor(R.attr.colorImageButtonAccent)
val colorButtonError =
Pref.ipButtonFollowRequestColor(activity.pref).notZero()
PrefI.ipButtonFollowRequestColor(activity.pref).notZero()
?: activity.attrColor(R.attr.colorRegexFilterError)
val colorButtonNormal =
@ -426,7 +426,7 @@ internal class DlgContextMenu(
)
btnDomainTimeline.vg(
Pref.bpEnableDomainTimeline(activity.pref) &&
PrefB.bpEnableDomainTimeline(activity.pref) &&
!accessInfo.isPseudo &&
!accessInfo.isMisskey
)
@ -563,7 +563,7 @@ internal class DlgContextMenu(
}
when {
Pref.bpAlwaysExpandContextMenuItems(activity.pref) -> {
PrefB.bpAlwaysExpandContextMenuItems(activity.pref) -> {
group.vg(true)
btn.background = null
}
@ -638,7 +638,7 @@ internal class DlgContextMenu(
private fun ActMain.onClickUser(v: View, pos: Int, who: TootAccount, whoRef: TootAccountRef): Boolean {
when (v.id) {
R.id.btnReportUser -> userReportForm(accessInfo, who)
R.id.btnFollow -> clickFollow(pos, accessInfo, who, whoRef, relation)
R.id.btnFollow -> clickFollow(pos, accessInfo, whoRef, relation)
R.id.btnMute -> clickMute(accessInfo, who, relation)
R.id.btnBlock -> clickBlock(accessInfo, who, relation)
R.id.btnAccountText -> launchActText(ActText.createIntent(activity, accessInfo, who))
@ -763,7 +763,7 @@ internal class DlgContextMenu(
activity.mentionFromAnotherAccount(accessInfo, who)
}
R.id.btnMute -> activity.userMuteFromAnotherAccount(who, accessInfo)
R.id.btnMute -> activity.userMuteFromAnotherAccount(who, accessInfo)
R.id.btnBlock -> activity.userBlockFromAnotherAccount(who, accessInfo)
R.id.btnQuoteAnotherAccount -> activity.quoteFromAnotherAccount(accessInfo, status)
R.id.btnQuoteTootBT -> activity.quoteFromAnotherAccount(accessInfo, status?.reblogParent)

File diff suppressed because it is too large Load Diff

View File

@ -1,18 +1,15 @@
package jp.juggler.subwaytooter
import android.view.View
import androidx.appcompat.app.AlertDialog
import jp.juggler.subwaytooter.action.*
import jp.juggler.subwaytooter.api.entity.*
import jp.juggler.subwaytooter.dialog.ActionsDialog
import jp.juggler.subwaytooter.dialog.DlgConfirm
import jp.juggler.subwaytooter.table.AcctColor
import jp.juggler.subwaytooter.table.ContentWarning
import jp.juggler.subwaytooter.table.MediaShown
import jp.juggler.subwaytooter.util.openCustomTab
import jp.juggler.util.encodePercent
import jp.juggler.util.cast
import jp.juggler.util.notEmpty
import jp.juggler.util.showToast
import jp.juggler.util.vg
val defaultBoostedAction: ItemViewHolder.() -> Unit = {
val pos = activity.nextPosition(column)
@ -26,446 +23,108 @@ val defaultBoostedAction: ItemViewHolder.() -> Unit = {
}
}
fun ItemViewHolder.openConversationSummary() {
val cs = item as? TootConversationSummary ?: return
if (activity.conversationUnreadClear(accessInfo, cs)) {
listAdapter.notifyChange(
reason = "ConversationSummary reset unread",
reset = true
)
}
activity.conversation(
activity.nextPosition(column),
accessInfo,
cs.last_status
)
}
fun ItemViewHolder.openFilterMenu(item: TootFilter) {
val ad = ActionsDialog()
ad.addAction(activity.getString(R.string.edit)) {
ActKeywordFilter.open(activity, accessInfo, item.id)
}
ad.addAction(activity.getString(R.string.delete)) {
activity.filterDelete(accessInfo, item)
}
ad.show(activity, activity.getString(R.string.filter_of, item.phrase))
}
fun ItemViewHolder.onClickImpl(v: View?) {
v ?: return
val pos = activity.nextPosition(column)
val item = this.item
val notification = (item as? TootNotification)
when (v) {
with(activity) {
when (v) {
ivMedia1 -> clickMedia(0)
ivMedia2 -> clickMedia(1)
ivMedia3 -> clickMedia(2)
ivMedia4 -> clickMedia(3)
btnHideMedia, btnCardImageHide -> showHideMediaViews(false)
btnShowMedia, btnCardImageShow -> showHideMediaViews(true)
btnContentWarning -> toggleContentWarning()
ivAvatar -> clickAvatar(pos)
llBoosted -> boostedAction()
llReply -> clickReplyInfo(pos, accessInfo, column.type, statusReply, statusShowing)
btnHideMedia, btnCardImageHide -> {
fun hideViews() {
llMedia.visibility = View.GONE
btnShowMedia.visibility = View.VISIBLE
llCardImage.visibility = View.GONE
btnCardImageShow.visibility = View.VISIBLE
llFollow -> clickFollowInfo(pos, accessInfo, followAccount) { whoRef ->
DlgContextMenu(this, column, whoRef, null, (item as? TootNotification), tvContent).show()
}
statusShowing?.let { status ->
MediaShown.save(status, false)
hideViews()
}
if (item is TootScheduled) {
MediaShown.save(item.uri, false)
hideViews()
btnFollow -> clickFollowInfo(pos, accessInfo, followAccount, forceMenu = true) { whoRef ->
DlgContextMenu(this, column, whoRef, null, (item as? TootNotification), tvContent).show()
}
btnGapHead -> column.startGap(item.cast(), isHead = true)
btnGapTail -> column.startGap(item.cast(), isHead = false)
btnSearchTag, llTrendTag -> clickTag(pos, item)
btnListTL -> clickListTl(pos, accessInfo, item)
btnListMore -> clickListMoreButton(pos, accessInfo, item)
btnFollowRequestAccept -> clickFollowRequestAccept(accessInfo, followAccount, accept = true)
btnFollowRequestDeny -> clickFollowRequestAccept(accessInfo, followAccount, accept = false)
llFilter -> openFilterMenu(accessInfo, item.cast())
ivCardImage -> clickCardImage(pos, accessInfo, statusShowing?.card)
llConversationIcons -> clickConversation(pos, accessInfo, listAdapter, summary = item.cast())
}
btnShowMedia, btnCardImageShow -> {
fun showViews() {
llMedia.visibility = View.VISIBLE
btnShowMedia.visibility = View.GONE
llCardImage.visibility = View.VISIBLE
btnCardImageShow.visibility = View.GONE
}
statusShowing?.let { status ->
MediaShown.save(status, true)
showViews()
}
if (item is TootScheduled) {
MediaShown.save(item.uri, true)
showViews()
}
}
ivMedia1 -> clickMedia(0)
ivMedia2 -> clickMedia(1)
ivMedia3 -> clickMedia(2)
ivMedia4 -> clickMedia(3)
btnContentWarning -> {
statusShowing?.let { status ->
val newShown = llContents.visibility == View.GONE
ContentWarning.save(status, newShown)
// 1個だけ開閉するのではなく、例えば通知TLにある複数の要素をまとめて開閉するなどある
listAdapter.notifyChange(reason = "ContentWarning onClick", reset = true)
}
if (item is TootScheduled) {
val newShown = llContents.visibility == View.GONE
ContentWarning.save(item.uri, newShown)
// 1個だけ開閉するのではなく、例えば通知TLにある複数の要素をまとめて開閉するなどある
listAdapter.notifyChange(reason = "ContentWarning onClick", reset = true)
}
}
ivThumbnail -> statusAccount?.let { whoRef ->
when {
accessInfo.isNA -> DlgContextMenu(
activity,
column,
whoRef,
null,
notification,
tvContent
).show()
// 2018/12/26 疑似アカウントでもプロフカラムを表示する https://github.com/tootsuite/mastodon/commit/108b2139cd87321f6c0aec63ef93db85ce30bfec
else -> activity.userProfileLocal(
pos,
accessInfo,
whoRef.get()
)
}
}
llBoosted -> boostedAction()
llReply -> {
val s = statusReply
when {
s != null -> activity.conversation(pos, accessInfo, s)
// tootsearchは返信元のIDを取得するのにひと手間必要
column.type == ColumnType.SEARCH_TS ||
column.type == ColumnType.SEARCH_NOTESTOCK ->
activity.conversationFromTootsearch(pos, statusShowing)
else -> {
val id = statusShowing?.in_reply_to_id
if (id != null) {
activity.conversationLocal(pos, accessInfo, id)
}
}
}
}
llFollow -> followAccount?.let { whoRef ->
if (accessInfo.isPseudo) {
DlgContextMenu(activity, column, whoRef, null, notification, tvContent).show()
} else {
activity.userProfileLocal(pos, accessInfo, whoRef.get())
}
}
btnFollow -> followAccount?.let { who ->
DlgContextMenu(activity, column, who, null, notification, tvContent).show()
}
btnGapHead -> when (item) {
is TootGap -> column.startGap(item, isHead = true)
}
btnGapTail -> when (item) {
is TootGap -> column.startGap(item, isHead = false)
}
btnSearchTag, llTrendTag -> when (item) {
is TootConversationSummary -> openConversationSummary()
is TootGap -> when {
column.type.gapDirection(column, true) ->
column.startGap(item, isHead = true)
column.type.gapDirection(column, false) ->
column.startGap(item, isHead = false)
else ->
activity.showToast(true, "This column can't support gap reading.")
}
is TootSearchGap -> column.startGap(item, isHead = true)
is TootDomainBlock -> {
AlertDialog.Builder(activity)
.setMessage(
activity.getString(
R.string.confirm_unblock_domain,
item.domain.pretty
)
)
.setNegativeButton(R.string.cancel, null)
.setPositiveButton(R.string.ok) { _, _ ->
activity.domainBlock(
accessInfo,
item.domain,
bBlock = false
)
}
.show()
}
is TootTag -> {
activity.tagTimeline(
activity.nextPosition(column),
accessInfo,
item.name // #を含まない
)
}
is TootScheduled -> {
ActionsDialog()
.addAction(activity.getString(R.string.edit)) {
activity.scheduledPostEdit(accessInfo, item)
}
.addAction(activity.getString(R.string.delete)) {
activity.scheduledPostDelete(accessInfo, item) {
column.onScheduleDeleted(item)
activity.showToast(false, R.string.scheduled_post_deleted)
}
}
.show(activity)
}
}
btnListTL -> if (item is TootList) {
activity.addColumn(pos, accessInfo, ColumnType.LIST_TL, item.id)
} else if (item is MisskeyAntenna) {
// TODO
activity.addColumn(pos, accessInfo, ColumnType.MISSKEY_ANTENNA_TL, item.id)
}
btnListMore -> when (item) {
is TootList -> {
ActionsDialog()
.addAction(activity.getString(R.string.list_timeline)) {
activity.addColumn(pos, accessInfo, ColumnType.LIST_TL, item.id)
}
.addAction(activity.getString(R.string.list_member)) {
activity.addColumn(
false,
pos,
accessInfo,
ColumnType.LIST_MEMBER,
item.id
)
}
.addAction(activity.getString(R.string.rename)) {
activity.listRename(accessInfo, item)
}
.addAction(activity.getString(R.string.delete)) {
activity.listDelete(accessInfo, item)
}
.show(activity, item.title)
}
is MisskeyAntenna -> {
// TODO
}
}
btnFollowRequestAccept -> followAccount?.let { whoRef ->
val who = whoRef.get()
DlgConfirm.openSimple(
activity,
activity.getString(
R.string.follow_accept_confirm,
AcctColor.getNickname(accessInfo, who)
)
) {
activity.followRequestAuthorize(accessInfo, whoRef, true)
}
}
btnFollowRequestDeny -> followAccount?.let { whoRef ->
val who = whoRef.get()
DlgConfirm.openSimple(
activity,
activity.getString(
R.string.follow_deny_confirm,
AcctColor.getNickname(accessInfo, who)
)
) {
activity.followRequestAuthorize(accessInfo, whoRef, false)
}
}
llFilter -> if (item is TootFilter) {
openFilterMenu(item)
}
ivCardImage -> statusShowing?.card?.let { card ->
val originalStatus = card.originalStatus
if (originalStatus != null) {
activity.conversation(
activity.nextPosition(column),
accessInfo,
originalStatus
)
} else {
val url = card.url
if (url?.isNotEmpty() == true) {
openCustomTab(
activity,
pos,
url,
accessInfo = accessInfo
)
}
}
}
llConversationIcons -> openConversationSummary()
}
}
fun ItemViewHolder.onLongClickImpl(v: View?): Boolean {
v ?: return false
val notification = (item as? TootNotification)
with(activity) {
when (v) {
val pos = activity.nextPosition(column)
when (v) {
ivAvatar ->
clickAvatar(pos, longClick = true)
ivThumbnail -> {
statusAccount?.let { who ->
DlgContextMenu(
activity,
column,
who,
null,
notification,
tvContent
).show()
}
return true
}
llBoosted ->
longClickBoostedInfo(boostAccount)
llBoosted -> {
boostAccount?.let { who ->
DlgContextMenu(
activity,
column,
who,
null,
notification,
tvContent
).show()
}
return true
}
llReply -> {
val s = statusReply
when {
// 返信元のstatusがあるならコンテキストメニュー
s != null -> DlgContextMenu(
activity,
column,
s.accountRef,
s,
notification,
tvContent
).show()
// それ以外はコンテキストメニューではなく会話を開く
// tootsearchは返信元のIDを取得するのにひと手間必要
column.type == ColumnType.SEARCH_TS ||
column.type == ColumnType.SEARCH_NOTESTOCK ->
activity.conversationFromTootsearch(
activity.nextPosition(column),
statusShowing
)
else -> {
val id = statusShowing?.in_reply_to_id
if (id != null) {
activity.conversationLocal(
activity.nextPosition(column),
accessInfo,
id
)
}
llReply ->
clickReplyInfo(pos, accessInfo, column.type, statusReply, statusShowing, longClick = true) { status ->
DlgContextMenu(this, column, status.accountRef, status, item.cast(), tvContent).show()
}
}
}
llFollow -> {
followAccount?.let { whoRef ->
DlgContextMenu(
activity,
column,
whoRef,
null,
notification
).show()
}
return true
}
btnFollow -> {
followAccount?.let { whoRef ->
activity.followFromAnotherAccount(
activity.nextPosition(column),
accessInfo,
whoRef.get()
)
}
return true
}
ivCardImage -> activity.conversationOtherInstance(
activity.nextPosition(column),
statusShowing?.card?.originalStatus
)
btnSearchTag, llTrendTag -> {
when (val item = this.item) {
// is TootGap -> column.startGap(item)
//
// is TootDomainBlock -> {
// val domain = item.domain
// AlertDialog.Builder(activity)
// .setMessage(activity.getString(R.string.confirm_unblock_domain, domain))
// .setNegativeButton(R.string.cancel, null)
// .setPositiveButton(R.string.ok) { _, _ -> Action_Instance.blockDomain(activity, access_info, domain, false) }
// .show()
// }
is TootTag -> {
// search_tag は#を含まない
val tagEncoded = item.name.encodePercent()
val url = "https://${accessInfo.apiHost.ascii}/tags/$tagEncoded"
activity.tagTimelineFromAccount(
pos = activity.nextPosition(column),
url = url,
host = accessInfo.apiHost,
tagWithoutSharp = item.name
)
llFollow ->
followAccount?.let {
DlgContextMenu(activity, column, it, null, item.cast(), tvContent).show()
}
}
return true
btnFollow ->
followAccount?.get()?.let { followFromAnotherAccount(pos, accessInfo, it) }
ivCardImage ->
clickCardImage(pos, accessInfo, statusShowing?.card, longClick = true)
btnSearchTag, llTrendTag ->
longClickTag(pos, item)
else ->
return false
}
}
return false
return true
}
fun ItemViewHolder.clickMedia(i: Int) {
private fun ItemViewHolder.longClickBoostedInfo(who: TootAccountRef?) {
who ?: return
DlgContextMenu(activity, column, who, null, item.cast(), tvContent).show()
}
private fun ItemViewHolder.longClickTag(pos: Int, item: TimelineItem?): Boolean {
when (item) {
// is TootGap -> column.startGap(item)
//
// is TootDomainBlock -> {
// val domain = item.domain
// AlertDialog.Builder(activity)
// .setMessage(activity.getString(R.string.confirm_unblock_domain, domain))
// .setNegativeButton(R.string.cancel, null)
// .setPositiveButton(R.string.ok) { _, _ -> Action_Instance.blockDomain(activity, access_info, domain, false) }
// .show()
// }
is TootTag -> activity.longClickTootTag(pos, accessInfo, item)
}
return true
}
private fun ItemViewHolder.clickMedia(i: Int) {
try {
val mediaAttachments =
statusShowing?.media_attachments ?: (item as? TootScheduled)?.mediaAttachments
@ -487,7 +146,7 @@ fun ItemViewHolder.clickMedia(i: Int) {
item.type == TootAttachmentType.Unknown && mediaAttachments.size == 1 -> {
// https://github.com/tateisu/SubwayTooter/pull/119
// メディアタイプがunknownの場合、そのほとんどはリモートから来たURLである
// Pref.bpPriorLocalURL の状態に関わらずリモートURLがあればそれをブラウザで開く
// PrefB.bpPriorLocalURL の状態に関わらずリモートURLがあればそれをブラウザで開く
when (val remoteUrl = item.remote_url.notEmpty()) {
null -> activity.openCustomTab(item)
else -> activity.openCustomTab(remoteUrl)
@ -495,7 +154,7 @@ fun ItemViewHolder.clickMedia(i: Int) {
}
// 内蔵メディアビューアを使う
Pref.bpUseInternalMediaViewer(App1.pref) ->
PrefB.bpUseInternalMediaViewer(App1.pref) ->
ActMediaViewer.open(
activity,
when (accessInfo.isMisskey) {
@ -514,3 +173,57 @@ fun ItemViewHolder.clickMedia(i: Int) {
ItemViewHolder.log.trace(ex)
}
}
private fun ItemViewHolder.showHideMediaViews(show: Boolean) {
llMedia.vg(show)
llCardImage.vg(show)
btnShowMedia.vg(!show)
btnCardImageShow.vg(!show)
statusShowing?.let { MediaShown.save(it, show) }
item.cast<TootScheduled>()?.let { MediaShown.save(it.uri, show) }
}
private fun ItemViewHolder.toggleContentWarning() {
// トグル動作
val show = llContents.visibility == View.GONE
statusShowing?.let { ContentWarning.save(it, show) }
item.cast<TootScheduled>()?.let { ContentWarning.save(it.uri, show) }
// 1個だけ開閉するのではなく、例えば通知TLにある複数の要素をまとめて開閉するなどある
listAdapter.notifyChange(reason = "ContentWarning onClick", reset = true)
}
private fun ItemViewHolder.clickAvatar(pos: Int, longClick: Boolean = false) {
statusAccount?.let { whoRef ->
when {
longClick || accessInfo.isNA ->
DlgContextMenu(activity, column, whoRef, null, item.cast(), tvContent).show()
// 2018/12/26 疑似アカウントでもプロフカラムを表示する https://github.com/tootsuite/mastodon/commit/108b2139cd87321f6c0aec63ef93db85ce30bfec
else -> activity.userProfileLocal(pos, accessInfo, whoRef.get())
}
}
}
private fun ItemViewHolder.clickTag(pos: Int, item: TimelineItem?) {
with(activity) {
when (item) {
is TootTag -> tagTimeline(pos, accessInfo, item.name)
is TootSearchGap -> column.startGap(item, isHead = true)
is TootConversationSummary -> clickConversation(pos, accessInfo, listAdapter, summary = item)
is TootGap -> clickTootGap(column, item)
is TootDomainBlock -> clickDomainBlock(accessInfo, item)
is TootScheduled -> clickScheduledToot(accessInfo, item, column)
}
}
}
fun ActMain.clickTootGap(column: Column, item: TootGap) {
when {
column.type.gapDirection(column, true) -> column.startGap(item, isHead = true)
column.type.gapDirection(column, false) -> column.startGap(item, isHead = false)
else -> showToast(true, "This column can't support gap reading.")
}
}

View File

@ -43,7 +43,7 @@ private fun addLinkAndCaption(
fun ItemViewHolder.showPreviewCard(status: TootStatus) {
if (Pref.bpDontShowPreviewCard(activity.pref)) return
if (PrefB.bpDontShowPreviewCard(activity.pref)) return
val card = status.card ?: return
@ -107,7 +107,7 @@ fun ItemViewHolder.showPreviewCard(status: TootStatus) {
if (description != null && description.isNotEmpty()) {
if (sb.isNotEmpty()) sb.append("<br>")
val limit = Pref.spCardDescriptionLength.toInt(activity.pref)
val limit = PrefS.spCardDescriptionLength.toInt(activity.pref)
sb.append(
HTMLDecoder.encodeEntity(

View File

@ -23,7 +23,7 @@ import org.jetbrains.anko.dip
fun ItemViewHolder.makeReactionsView(status: TootStatus) {
val reactionSet = status.reactionSet
if (reactionSet?.hasReaction() != true) {
if (!TootReaction.canReaction(accessInfo) || !Pref.bpKeepReactionSpace(activity.pref)) return
if (!TootReaction.canReaction(accessInfo) || !PrefB.bpKeepReactionSpace(activity.pref)) return
}
val density = activity.density
@ -87,7 +87,7 @@ fun ItemViewHolder.makeReactionsView(status: TootStatus) {
// 自分がリアクションしたやつは背景を変える
getAdaptiveRippleDrawableRound(
act,
Pref.ipButtonReactionedColor(act.pref).notZero() ?: act.attrColor(R.attr.colorImageButtonAccent),
PrefI.ipButtonReactionedColor(act.pref).notZero() ?: act.attrColor(R.attr.colorImageButtonAccent),
act.attrColor(R.attr.colorRippleEffect),
roundNormal = true
)

View File

@ -6,10 +6,8 @@ import android.os.SystemClock
import android.text.Spannable
import android.text.SpannableString
import android.text.SpannableStringBuilder
import android.view.Gravity
import android.view.View
import android.widget.Button
import android.widget.ImageView
import android.widget.TextView
import androidx.annotation.StringRes
import jp.juggler.subwaytooter.api.TootParser
@ -19,7 +17,6 @@ import jp.juggler.subwaytooter.span.MyClickableSpan
import jp.juggler.subwaytooter.table.*
import jp.juggler.subwaytooter.util.Benchmark
import jp.juggler.subwaytooter.util.DecodeOptions
import jp.juggler.subwaytooter.util.OpenSticker
import jp.juggler.subwaytooter.view.CountImageButton
import jp.juggler.subwaytooter.view.MyNetworkImageView
import jp.juggler.util.*
@ -197,14 +194,14 @@ fun ItemViewHolder.bind(
item.isQuoteToot -> {
// 引用Renote
val colorBg = Pref.ipEventBgColorBoost(activity.pref)
val colorBg = PrefI.ipEventBgColorBoost(activity.pref)
showReply(reblog, R.drawable.ic_repeat, R.string.quote_to)
showStatus(item, colorBg)
}
else -> {
// 引用なしブースト
val colorBg = Pref.ipEventBgColorBoost(activity.pref)
val colorBg = PrefI.ipEventBgColorBoost(activity.pref)
showBoost(
item.accountRef,
item.time_created_at,
@ -218,32 +215,20 @@ fun ItemViewHolder.bind(
}
is TootAccountRef -> showAccount(item)
is TootNotification -> showNotification(item)
is TootGap -> showGap()
is TootSearchGap -> showSearchGap(item)
is TootDomainBlock -> showDomainBlock(item)
is TootList -> showList(item)
is MisskeyAntenna -> showAntenna(item)
is TootMessageHolder -> showMessageHolder(item)
is TootTag -> showSearchTag(item)
is TootFilter -> showFilter(item)
is TootConversationSummary -> {
showStatusOrReply(item.last_status)
showConversationIcons(item)
}
is TootScheduled -> {
showScheduled(item)
}
else -> {
}
is TootScheduled -> showScheduled(item)
}
b.report()
}
@ -363,284 +348,12 @@ fun ItemViewHolder.showBoost(
setAcct(tvBoostedAcct, accessInfo, who)
}
fun ItemViewHolder.showStatusOrReply(item: TootStatus, colorBgArg: Int = 0) {
var colorBg = colorBgArg
val reply = item.reply
val inReplyToId = item.in_reply_to_id
val inReplyToAccountId = item.in_reply_to_account_id
when {
reply != null -> {
showReply(reply, R.drawable.ic_reply, R.string.reply_to)
if (colorBgArg == 0) colorBg = Pref.ipEventBgColorMention(activity.pref)
}
inReplyToId != null && inReplyToAccountId != null -> {
showReply(item, inReplyToAccountId)
if (colorBgArg == 0) colorBg = Pref.ipEventBgColorMention(activity.pref)
}
}
showStatus(item, colorBg)
}
fun ItemViewHolder.showMessageHolder(item: TootMessageHolder) {
tvMessageHolder.visibility = View.VISIBLE
tvMessageHolder.text = item.text
tvMessageHolder.gravity = item.gravity
}
fun ItemViewHolder.showNotification(n: TootNotification) {
val nStatus = n.status
val nAccountRef = n.accountRef
val nAccount = nAccountRef?.get()
fun showNotificationStatus(item: TootStatus, colorBgDefault: Int) {
val reblog = item.reblog
when {
reblog == null -> showStatusOrReply(item, colorBgDefault)
item.isQuoteToot -> {
// 引用Renote
showReply(reblog, R.drawable.ic_repeat, R.string.quote_to)
showStatus(item, Pref.ipEventBgColorQuote(activity.pref))
}
else -> {
// 通常のブースト。引用なしブースト。
// ブースト表示は通知イベントと被るのでしない
showStatusOrReply(reblog, Pref.ipEventBgColorBoost(activity.pref))
}
}
}
when (n.type) {
TootNotification.TYPE_FAVOURITE -> {
val colorBg = Pref.ipEventBgColorFavourite(activity.pref)
if (nAccount != null) showBoost(
nAccountRef,
n.time_created_at,
if (accessInfo.isNicoru(nAccount)) R.drawable.ic_nicoru else R.drawable.ic_star,
R.string.display_name_favourited_by
)
if (nStatus != null) {
showNotificationStatus(nStatus, colorBg)
}
}
TootNotification.TYPE_REBLOG -> {
val colorBg = Pref.ipEventBgColorBoost(activity.pref)
if (nAccount != null) showBoost(
nAccountRef,
n.time_created_at,
R.drawable.ic_repeat,
R.string.display_name_boosted_by,
boostStatus = nStatus
)
if (nStatus != null) {
showNotificationStatus(nStatus, colorBg)
}
}
TootNotification.TYPE_RENOTE -> {
// 引用のないreblog
val colorBg = Pref.ipEventBgColorBoost(activity.pref)
if (nAccount != null) showBoost(
nAccountRef,
n.time_created_at,
R.drawable.ic_repeat,
R.string.display_name_boosted_by,
boostStatus = nStatus
)
if (nStatus != null) {
showNotificationStatus(nStatus, colorBg)
}
}
TootNotification.TYPE_FOLLOW -> {
val colorBg = Pref.ipEventBgColorFollow(activity.pref)
if (nAccount != null) {
showBoost(
nAccountRef,
n.time_created_at,
R.drawable.ic_follow_plus,
R.string.display_name_followed_by
)
showAccount(nAccountRef)
if (colorBg != 0) this.viewRoot.backgroundColor = colorBg
}
}
TootNotification.TYPE_UNFOLLOW -> {
val colorBg = Pref.ipEventBgColorUnfollow(activity.pref)
if (nAccount != null) {
showBoost(
nAccountRef,
n.time_created_at,
R.drawable.ic_follow_cross,
R.string.display_name_unfollowed_by
)
showAccount(nAccountRef)
if (colorBg != 0) this.viewRoot.backgroundColor = colorBg
}
}
TootNotification.TYPE_MENTION,
TootNotification.TYPE_REPLY,
-> {
val colorBg = Pref.ipEventBgColorMention(activity.pref)
if (!bSimpleList && !accessInfo.isMisskey) {
when {
nAccount == null -> {
//
}
nStatus?.in_reply_to_id != null || nStatus?.reply != null -> {
// トゥート内部に「~への返信」を表示するので、
// 通知イベントの「~からの返信」は表示しない
}
else -> // 返信ではなくメンションの場合は「~からの返信」を表示する
showBoost(
nAccountRef,
n.time_created_at,
R.drawable.ic_reply,
R.string.display_name_mentioned_by
)
}
}
if (nStatus != null) {
showNotificationStatus(nStatus, colorBg)
}
}
TootNotification.TYPE_EMOJI_REACTION_PLEROMA,
TootNotification.TYPE_EMOJI_REACTION,
TootNotification.TYPE_REACTION,
-> {
val colorBg = Pref.ipEventBgColorReaction(activity.pref)
if (nAccount != null) showBoost(
nAccountRef,
n.time_created_at,
R.drawable.ic_face,
R.string.display_name_reaction_by,
reaction = n.reaction ?: TootReaction.UNKNOWN,
boostStatus = nStatus
)
if (nStatus != null) {
showNotificationStatus(nStatus, colorBg)
}
}
TootNotification.TYPE_QUOTE -> {
val colorBg = Pref.ipEventBgColorQuote(activity.pref)
if (nAccount != null) showBoost(
nAccountRef,
n.time_created_at,
R.drawable.ic_repeat,
R.string.display_name_quoted_by
)
if (nStatus != null) {
showNotificationStatus(nStatus, colorBg)
}
}
TootNotification.TYPE_STATUS -> {
val colorBg = Pref.ipEventBgColorStatus(activity.pref)
if (nAccount != null) showBoost(
nAccountRef,
n.time_created_at,
if (nStatus == null) {
R.drawable.ic_question
} else {
Styler.getVisibilityIconId(accessInfo.isMisskey, nStatus.visibility)
},
R.string.display_name_posted_by
)
if (nStatus != null) {
showNotificationStatus(nStatus, colorBg)
}
}
TootNotification.TYPE_FOLLOW_REQUEST,
TootNotification.TYPE_FOLLOW_REQUEST_MISSKEY,
-> {
val colorBg = Pref.ipEventBgColorFollowRequest(activity.pref)
if (nAccount != null) {
showBoost(
nAccountRef,
n.time_created_at,
R.drawable.ic_follow_wait,
R.string.display_name_follow_request_by
)
if (colorBg != 0) this.viewRoot.backgroundColor = colorBg
boostedAction = {
activity.addColumn(
activity.nextPosition(column), accessInfo, ColumnType.FOLLOW_REQUESTS
)
}
}
}
TootNotification.TYPE_FOLLOW_REQUEST_ACCEPTED_MISSKEY -> {
val colorBg = Pref.ipEventBgColorFollow(activity.pref)
if (nAccount != null) {
showBoost(
nAccountRef,
n.time_created_at,
R.drawable.ic_follow_plus,
R.string.display_name_follow_request_accepted_by
)
showAccount(nAccountRef)
if (colorBg != 0) this.viewRoot.backgroundColor = colorBg
}
}
TootNotification.TYPE_VOTE,
TootNotification.TYPE_POLL_VOTE_MISSKEY,
-> {
val colorBg = Pref.ipEventBgColorVote(activity.pref)
if (nAccount != null) showBoost(
nAccountRef,
n.time_created_at,
R.drawable.ic_vote,
R.string.display_name_voted_by
)
if (nStatus != null) {
showNotificationStatus(nStatus, colorBg)
}
}
TootNotification.TYPE_POLL -> {
val colorBg = 0
if (nAccount != null) showBoost(
nAccountRef,
n.time_created_at,
R.drawable.ic_vote,
R.string.end_of_polling_from
)
if (nStatus != null) {
showNotificationStatus(nStatus, colorBg)
}
}
else -> {
val colorBg = 0
if (nAccount != null) showBoost(
nAccountRef,
n.time_created_at,
R.drawable.ic_question,
R.string.unknown_notification_from
)
if (nStatus != null) {
showNotificationStatus(nStatus, colorBg)
}
tvMessageHolder.visibility = View.VISIBLE
tvMessageHolder.text = "notification type is ${n.type}"
tvMessageHolder.gravity = Gravity.CENTER
}
}
}
fun ItemViewHolder.showList(list: TootList) {
llList.visibility = View.VISIBLE
btnListTL.text = list.title
@ -705,7 +418,7 @@ fun ItemViewHolder.showGap() {
btnGapTail.vg(column.type.gapDirection(column, false))
?.imageTintList = contentColorCsl
val c = Pref.ipEventBgColorGap(App1.pref)
val c = PrefI.ipEventBgColorGap(App1.pref)
if (c != 0) this.viewRoot.backgroundColor = c
}
@ -764,281 +477,6 @@ fun ItemViewHolder.showReply(reply: TootStatus, accountId: EntityId) {
// tootsearchではどのタンスから読んだか分からないのでin_reply_toのIDも信用できない
}
fun ItemViewHolder.showStatus(status: TootStatus, colorBg: Int = 0) {
val filteredWord = status.filteredWord
if (filteredWord != null) {
showMessageHolder(
TootMessageHolder(
if (Pref.bpShowFilteredWord(activity.pref)) {
"${activity.getString(R.string.filtered)} / $filteredWord"
} else {
activity.getString(R.string.filtered)
}
)
)
return
}
this.statusShowing = status
llStatus.visibility = View.VISIBLE
if (status.conversation_main) {
val conversationMainBgColor =
Pref.ipConversationMainTootBgColor(activity.pref).notZero()
?: (activity.attrColor(R.attr.colorImageButtonAccent) and 0xffffff) or 0x20000000
this.viewRoot.setBackgroundColor(conversationMainBgColor)
} else {
val c = colorBg.notZero()
?: when (status.bookmarked) {
true -> Pref.ipEventBgColorBookmark(App1.pref)
false -> 0
}.notZero()
?: when (status.getBackgroundColorType(accessInfo)) {
TootVisibility.UnlistedHome -> ItemViewHolder.toot_color_unlisted
TootVisibility.PrivateFollowers -> ItemViewHolder.toot_color_follower
TootVisibility.DirectSpecified -> ItemViewHolder.toot_color_direct_user
TootVisibility.DirectPrivate -> ItemViewHolder.toot_color_direct_me
// TODO add color setting for limited?
TootVisibility.Limited -> ItemViewHolder.toot_color_follower
else -> 0
}
if (c != 0) {
this.viewRoot.backgroundColor = c
}
}
showStatusTime(activity, tvTime, who = status.account, status = status)
val whoRef = status.accountRef
val who = whoRef.get()
this.statusAccount = whoRef
setAcct(tvAcct, accessInfo, who)
// if(who == null) {
// tvName.text = "?"
// name_invalidator.register(null)
// ivThumbnail.setImageUrl(activity.pref, 16f, null, null)
// } else {
tvName.text = whoRef.decoded_display_name
nameInvalidator.register(whoRef.decoded_display_name)
ivThumbnail.setImageUrl(
activity.pref,
Styler.calcIconRound(ivThumbnail.layoutParams),
accessInfo.supplyBaseUrl(who.avatar_static),
accessInfo.supplyBaseUrl(who.avatar)
)
// }
showOpenSticker(who)
var content = status.decoded_content
// ニコフレのアンケートの表示
val enquete = status.enquete
when {
enquete == null -> {
}
enquete.pollType == TootPollsType.FriendsNico && enquete.type != TootPolls.TYPE_ENQUETE -> {
// フレニコの投票の結果表示は普通にテキストを表示するだけでよい
}
else -> {
// アンケートの本文を上書きする
val question = enquete.decoded_question
if (question.isNotBlank()) content = question
showEnqueteItems(status, enquete)
}
}
showPreviewCard(status)
// if( status.decoded_tags == null ){
// tvTags.setVisibility( View.GONE );
// }else{
// tvTags.setVisibility( View.VISIBLE );
// tvTags.setText( status.decoded_tags );
// }
if (status.decoded_mentions.isEmpty()) {
tvMentions.visibility = View.GONE
} else {
tvMentions.visibility = View.VISIBLE
tvMentions.text = status.decoded_mentions
}
if (status.time_deleted_at > 0L) {
val s = SpannableStringBuilder()
.append('(')
.append(
activity.getString(
R.string.deleted_at,
TootStatus.formatTime(activity, status.time_deleted_at, true)
)
)
.append(')')
content = s
}
tvContent.text = content
contentInvalidator.register(content)
activity.checkAutoCW(status, content)
val r = status.auto_cw
tvContent.minLines = r?.originalLineCount ?: -1
val decodedSpoilerText = status.decoded_spoiler_text
when {
decodedSpoilerText.isNotEmpty() -> {
// 元データに含まれるContent Warning を使う
llContentWarning.visibility = View.VISIBLE
tvContentWarning.text = status.decoded_spoiler_text
spoilerInvalidator.register(status.decoded_spoiler_text)
val cwShown = ContentWarning.isShown(status, accessInfo.expand_cw)
showContent(cwShown)
}
r?.decodedSpoilerText != null -> {
// 自動CW
llContentWarning.visibility = View.VISIBLE
tvContentWarning.text = r.decodedSpoilerText
spoilerInvalidator.register(r.decodedSpoilerText)
val cwShown = ContentWarning.isShown(status, accessInfo.expand_cw)
showContent(cwShown)
}
else -> {
// CWしない
llContentWarning.visibility = View.GONE
llContents.visibility = View.VISIBLE
}
}
val mediaAttachments = status.media_attachments
if (mediaAttachments == null || mediaAttachments.isEmpty()) {
flMedia.visibility = View.GONE
llMedia.visibility = View.GONE
btnShowMedia.visibility = View.GONE
} else {
flMedia.visibility = View.VISIBLE
// hide sensitive media
val defaultShown = when {
column.hideMediaDefault -> false
accessInfo.dont_hide_nsfw -> true
else -> !status.sensitive
}
val isShown = MediaShown.isShown(status, defaultShown)
btnShowMedia.visibility = if (!isShown) View.VISIBLE else View.GONE
llMedia.visibility = if (!isShown) View.GONE else View.VISIBLE
val sb = StringBuilder()
setMedia(mediaAttachments, sb, ivMedia1, 0)
setMedia(mediaAttachments, sb, ivMedia2, 1)
setMedia(mediaAttachments, sb, ivMedia3, 2)
setMedia(mediaAttachments, sb, ivMedia4, 3)
val m0 =
if (mediaAttachments.isEmpty()) null else mediaAttachments[0] as? TootAttachment
btnShowMedia.blurhash = m0?.blurhash
if (sb.isNotEmpty()) {
tvMediaDescription.visibility = View.VISIBLE
tvMediaDescription.text = sb
}
setIconDrawableId(
activity,
btnHideMedia,
R.drawable.ic_close,
color = contentColor,
alphaMultiplier = Styler.boostAlpha
)
}
makeReactionsView(status)
buttonsForStatus?.bind(status, (item as? TootNotification))
var sb: StringBuilder? = null
fun prepareSb(): StringBuilder =
sb?.append(", ") ?: StringBuilder().also { sb = it }
val application = status.application
if (application != null &&
(column.type == ColumnType.CONVERSATION || Pref.bpShowAppName(activity.pref))
) {
prepareSb().append(activity.getString(R.string.application_is, application.name ?: ""))
}
val language = status.language
if (language != null &&
(column.type == ColumnType.CONVERSATION || Pref.bpShowLanguage(activity.pref))
) {
prepareSb().append(activity.getString(R.string.language_is, language))
}
tvApplication.vg(sb != null)?.text = sb
}
fun ItemViewHolder.showOpenSticker(who: TootAccount) {
try {
if (!Column.showOpenSticker) return
val host = who.apDomain
// LTLでホスト名が同じならTickerを表示しない
@Suppress("NON_EXHAUSTIVE_WHEN")
when (column.type) {
ColumnType.LOCAL, ColumnType.LOCAL_AROUND -> {
if (host == accessInfo.apDomain) return
}
}
val item = OpenSticker.lastList[host.ascii] ?: return
tvOpenSticker.text = item.name
tvOpenSticker.textColor = item.fontColor
val density = activity.density
val lp = ivOpenSticker.layoutParams
lp.height = (density * 16f + 0.5f).toInt()
lp.width = (density * item.imageWidth + 0.5f).toInt()
ivOpenSticker.layoutParams = lp
ivOpenSticker.setImageUrl(activity.pref, 0f, item.favicon)
val colorBg = item.bgColor
when (colorBg.size) {
1 -> {
val c = colorBg.first()
tvOpenSticker.setBackgroundColor(c)
ivOpenSticker.setBackgroundColor(c)
}
else -> {
ivOpenSticker.setBackgroundColor(colorBg.last())
tvOpenSticker.background = colorBg.getGradation()
}
}
llOpenSticker.visibility = View.VISIBLE
llOpenSticker.requestLayout()
} catch (ex: Throwable) {
ItemViewHolder.log.trace(ex)
}
}
fun ItemViewHolder.showStatusTime(
activity: ActMain,
tv: TextView,
@ -1238,9 +676,9 @@ fun ItemViewHolder.showScheduled(item: TootScheduled) {
tvName.text = whoRef.decoded_display_name
nameInvalidator.register(whoRef.decoded_display_name)
ivThumbnail.setImageUrl(
ivAvatar.setImageUrl(
activity.pref,
Styler.calcIconRound(ivThumbnail.layoutParams),
Styler.calcIconRound(ivAvatar.layoutParams),
accessInfo.supplyBaseUrl(who.avatar_static),
accessInfo.supplyBaseUrl(who.avatar)
)
@ -1320,20 +758,6 @@ fun ItemViewHolder.showScheduled(item: TootScheduled) {
TootStatus.formatTime(activity, item.timeScheduledAt, true)
}
fun ItemViewHolder.showContent(shown: Boolean) {
llContents.visibility = if (shown) View.VISIBLE else View.GONE
btnContentWarning.setText(if (shown) R.string.hide else R.string.show)
statusShowing?.let { status ->
val r = status.auto_cw
tvContent.minLines = r?.originalLineCount ?: -1
if (r?.decodedSpoilerText != null) {
// 自動CWの場合はContentWarningのテキストを切り替える
tvContentWarning.text =
if (shown) activity.getString(R.string.auto_cw_prefix) else r.decodedSpoilerText
}
}
}
fun ItemViewHolder.showConversationIcons(cs: TootConversationSummary) {
val lastAccountId = cs.last_status.account.id
@ -1384,7 +808,7 @@ fun ItemViewHolder.setAcct(tv: TextView, accessInfo: SavedAccount, who: TootAcco
val ac = AcctColor.load(accessInfo, who)
tv.text = when {
AcctColor.hasNickname(ac) -> ac.nickname
Pref.bpShortAcctLocalUser(App1.pref) -> "@${who.acct.pretty}"
PrefB.bpShortAcctLocalUser(App1.pref) -> "@${who.acct.pretty}"
else -> "@${ac.nickname}"
}
tv.textColor = ac.color_fg.notZero() ?: this.acctColor
@ -1392,97 +816,3 @@ fun ItemViewHolder.setAcct(tv: TextView, accessInfo: SavedAccount, who: TootAcco
tv.setBackgroundColor(ac.color_bg) // may 0
tv.setPaddingRelative(activity.acctPadLr, 0, activity.acctPadLr, 0)
}
fun ItemViewHolder.setMedia(
mediaAttachments: ArrayList<TootAttachmentLike>,
sbDesc: StringBuilder,
iv: MyNetworkImageView,
idx: Int,
) {
val ta = if (idx < mediaAttachments.size) mediaAttachments[idx] else null
if (ta == null) {
iv.visibility = View.GONE
return
}
iv.visibility = View.VISIBLE
iv.setFocusPoint(ta.focusX, ta.focusY)
if (Pref.bpDontCropMediaThumb(App1.pref)) {
iv.scaleType = ImageView.ScaleType.FIT_CENTER
} else {
iv.setScaleTypeForMedia()
}
val showUrl: Boolean
when (ta.type) {
TootAttachmentType.Audio -> {
iv.setMediaType(0)
iv.setDefaultImage(Styler.defaultColorIcon(activity, R.drawable.wide_music))
iv.setImageUrl(activity.pref, 0f, ta.urlForThumbnail(activity.pref))
showUrl = true
}
TootAttachmentType.Unknown -> {
iv.setMediaType(0)
iv.setDefaultImage(Styler.defaultColorIcon(activity, R.drawable.wide_question))
iv.setImageUrl(activity.pref, 0f, null)
showUrl = true
}
else -> when (val urlThumbnail = ta.urlForThumbnail(activity.pref)) {
null, "" -> {
iv.setMediaType(0)
iv.setDefaultImage(Styler.defaultColorIcon(activity, R.drawable.wide_question))
iv.setImageUrl(activity.pref, 0f, null)
showUrl = true
}
else -> {
iv.setMediaType(
when (ta.type) {
TootAttachmentType.Video -> R.drawable.media_type_video
TootAttachmentType.GIFV -> R.drawable.media_type_gifv
else -> 0
}
)
iv.setDefaultImage(null)
iv.setImageUrl(
activity.pref,
0f,
accessInfo.supplyBaseUrl(urlThumbnail),
accessInfo.supplyBaseUrl(urlThumbnail)
)
showUrl = false
}
}
}
fun appendDescription(s: String) {
// val lp = LinearLayout.LayoutParams(
// LinearLayout.LayoutParams.MATCH_PARENT,
// LinearLayout.LayoutParams.WRAP_CONTENT
// )
// lp.topMargin = (0.5f + activity.density * 3f).toInt()
//
// val tv = MyTextView(activity)
// tv.layoutParams = lp
// //
// tv.movementMethod = MyLinkMovementMethod
// if(! activity.timeline_font_size_sp.isNaN()) {
// tv.textSize = activity.timeline_font_size_sp
// }
// tv.setTextColor(content_color)
if (sbDesc.isNotEmpty()) sbDesc.append("\n")
val desc = activity.getString(R.string.media_description, idx + 1, s)
sbDesc.append(desc)
}
when (val description = ta.description.notEmpty()) {
null -> if (showUrl) ta.urlForDescription.notEmpty()?.let { appendDescription(it) }
else -> appendDescription(description)
}
}

View File

@ -0,0 +1,225 @@
package jp.juggler.subwaytooter
import android.view.Gravity
import android.view.View
import jp.juggler.subwaytooter.api.entity.TootAccountRef
import jp.juggler.subwaytooter.api.entity.TootNotification
import jp.juggler.subwaytooter.api.entity.TootReaction
import jp.juggler.subwaytooter.api.entity.TootStatus
import jp.juggler.util.notZero
import org.jetbrains.anko.backgroundColor
fun ItemViewHolder.showNotification(n: TootNotification) {
val nStatus = n.status
val nAccountRef = n.accountRef
when (n.type) {
TootNotification.TYPE_FAVOURITE ->
showNotificationFavourite(n, nAccountRef, nStatus)
TootNotification.TYPE_REBLOG ->
showNotificationReblog(n, nAccountRef, nStatus)
TootNotification.TYPE_RENOTE ->
showNotificationRenote(n, nAccountRef, nStatus)
TootNotification.TYPE_FOLLOW ->
showNotificationFollow(n, nAccountRef)
TootNotification.TYPE_UNFOLLOW ->
showNotificationUnfollow(n, nAccountRef)
TootNotification.TYPE_MENTION,
TootNotification.TYPE_REPLY,
-> showNotificationMention(n, nAccountRef, nStatus)
TootNotification.TYPE_EMOJI_REACTION_PLEROMA,
TootNotification.TYPE_EMOJI_REACTION,
TootNotification.TYPE_REACTION,
-> showNotificationReaction(n, nAccountRef, nStatus)
TootNotification.TYPE_QUOTE ->
showNotificationQuote(n, nAccountRef, nStatus)
TootNotification.TYPE_STATUS ->
showNotificationPost(n, nAccountRef, nStatus)
TootNotification.TYPE_FOLLOW_REQUEST,
TootNotification.TYPE_FOLLOW_REQUEST_MISSKEY,
-> showNotificationFollowRequest(n, nAccountRef)
TootNotification.TYPE_FOLLOW_REQUEST_ACCEPTED_MISSKEY ->
showNotificationFollowRequestAccepted(n, nAccountRef)
TootNotification.TYPE_VOTE,
TootNotification.TYPE_POLL_VOTE_MISSKEY,
-> showNotificationVote(n, nAccountRef, nStatus)
TootNotification.TYPE_POLL ->
showNotificationPoll(n, nAccountRef, nStatus)
else ->
showNotificationUnknown(n, nAccountRef, nStatus)
}
}
private fun ItemViewHolder.showNotificationFollow(n: TootNotification, nAccountRef: TootAccountRef?) {
val colorBg = PrefI.ipEventBgColorFollow(activity.pref)
colorBg.notZero()?.let { viewRoot.backgroundColor = it }
nAccountRef?.let {
showBoost(it, n.time_created_at, R.drawable.ic_follow_plus, R.string.display_name_followed_by)
showAccount(it)
}
}
private fun ItemViewHolder.showNotificationUnfollow(n: TootNotification, nAccountRef: TootAccountRef?) {
val colorBg = PrefI.ipEventBgColorUnfollow(activity.pref)
colorBg.notZero()?.let { viewRoot.backgroundColor = it }
nAccountRef?.let {
showBoost(it, n.time_created_at, R.drawable.ic_follow_cross, R.string.display_name_unfollowed_by)
showAccount(it)
}
}
private fun ItemViewHolder.showNotificationFollowRequest(n: TootNotification, nAccountRef: TootAccountRef?) {
val colorBg = PrefI.ipEventBgColorFollowRequest(activity.pref)
colorBg.notZero()?.let { viewRoot.backgroundColor = it }
nAccountRef?.let {
showBoost(it, n.time_created_at, R.drawable.ic_follow_wait, R.string.display_name_follow_request_by)
showAccount(it)
}
boostedAction = {
activity.addColumn(activity.nextPosition(column), accessInfo, ColumnType.FOLLOW_REQUESTS)
}
}
private fun ItemViewHolder.showNotificationFollowRequestAccepted(n: TootNotification, nAccountRef: TootAccountRef?) {
val colorBg = PrefI.ipEventBgColorFollow(activity.pref)
colorBg.notZero()?.let { viewRoot.backgroundColor = it }
nAccountRef?.let {
showBoost(it, n.time_created_at, R.drawable.ic_follow_plus, R.string.display_name_follow_request_accepted_by)
showAccount(it)
}
}
private fun ItemViewHolder.showNotificationPost(n: TootNotification, nAccountRef: TootAccountRef?, nStatus: TootStatus?) {
val colorBg = PrefI.ipEventBgColorStatus(activity.pref)
val iconId = when (nStatus) {
null -> R.drawable.ic_question
else -> Styler.getVisibilityIconId(accessInfo.isMisskey, nStatus.visibility)
}
nAccountRef?.let { showBoost(it, n.time_created_at, iconId, R.string.display_name_posted_by) }
nStatus?.let { showNotificationStatus(it, colorBg) }
}
private fun ItemViewHolder.showNotificationReaction(n: TootNotification, nAccountRef: TootAccountRef?, nStatus: TootStatus?) {
val colorBg = PrefI.ipEventBgColorReaction(activity.pref)
nAccountRef?.let {
showBoost(
it, n.time_created_at,
R.drawable.ic_face,
R.string.display_name_reaction_by,
reaction = n.reaction ?: TootReaction.UNKNOWN,
boostStatus = nStatus
)
}
nStatus?.let { showNotificationStatus(it, colorBg) }
}
private fun ItemViewHolder.showNotificationFavourite(n: TootNotification, nAccountRef: TootAccountRef?, nStatus: TootStatus?) {
nAccountRef?.let {
val iconId = if (accessInfo.isNicoru(it.get())) R.drawable.ic_nicoru else R.drawable.ic_star
showBoost(it, n.time_created_at, iconId, R.string.display_name_favourited_by)
}
val colorBg = PrefI.ipEventBgColorFavourite(activity.pref)
nStatus?.let { showNotificationStatus(it, colorBg) }
}
private fun ItemViewHolder.showNotificationReblog(n: TootNotification, nAccountRef: TootAccountRef?, nStatus: TootStatus?) {
nAccountRef?.let {
showBoost(
it,
n.time_created_at,
R.drawable.ic_repeat,
R.string.display_name_boosted_by,
boostStatus = nStatus
)
}
val colorBg = PrefI.ipEventBgColorBoost(activity.pref)
nStatus?.let { showNotificationStatus(it, colorBg) }
}
private fun ItemViewHolder.showNotificationRenote(n: TootNotification, nAccountRef: TootAccountRef?, nStatus: TootStatus?) {
// 引用のないreblog
nAccountRef?.let {
showBoost(it, n.time_created_at, R.drawable.ic_repeat, R.string.display_name_boosted_by, boostStatus = nStatus)
}
val colorBg = PrefI.ipEventBgColorBoost(activity.pref)
nStatus?.let { showNotificationStatus(it, colorBg) }
}
private fun ItemViewHolder.showNotificationMention(n: TootNotification, nAccountRef: TootAccountRef?, nStatus: TootStatus?) {
// メンション通知に「~~からの返信」を表示するカラムなのかどうか
fun willShowReplyInfo(status: TootStatus?): Boolean = when {
// メンションではなく返信の場合、トゥート内部に「~への返信」を表示するので
// 通知イベントの「~からの返信」を表示しない
status.let { it?.in_reply_to_id != null && it.reply != null } -> false
// XXX: 簡略表示だったりMisskeyだったりが影響してた時期もあったが、今後どうしようか…
else -> true
}
if (willShowReplyInfo(nStatus)) {
nAccountRef?.let { showBoost(it, n.time_created_at, R.drawable.ic_reply, R.string.display_name_mentioned_by) }
}
val colorBg = PrefI.ipEventBgColorMention(activity.pref)
nStatus?.let { showNotificationStatus(it, colorBg) }
}
private fun ItemViewHolder.showNotificationQuote(n: TootNotification, nAccountRef: TootAccountRef?, nStatus: TootStatus?) {
nAccountRef?.let { showBoost(it, n.time_created_at, R.drawable.ic_repeat, R.string.display_name_quoted_by) }
val colorBg = PrefI.ipEventBgColorQuote(activity.pref)
nStatus?.let { showNotificationStatus(it, colorBg) }
}
private fun ItemViewHolder.showNotificationVote(n: TootNotification, nAccountRef: TootAccountRef?, nStatus: TootStatus?) {
nAccountRef?.let { showBoost(it, n.time_created_at, R.drawable.ic_vote, R.string.display_name_voted_by) }
val colorBg = PrefI.ipEventBgColorVote(activity.pref)
nStatus?.let { showNotificationStatus(it, colorBg) }
}
private fun ItemViewHolder.showNotificationPoll(n: TootNotification, nAccountRef: TootAccountRef?, nStatus: TootStatus?) {
nAccountRef?.let { showBoost(it, n.time_created_at, R.drawable.ic_vote, R.string.end_of_polling_from) }
val colorBg = 0
nStatus?.let { showNotificationStatus(it, colorBg) }
}
private fun ItemViewHolder.showNotificationUnknown(n: TootNotification, nAccountRef: TootAccountRef?, nStatus: TootStatus?) {
nAccountRef?.let { showBoost(it, n.time_created_at, R.drawable.ic_question, R.string.unknown_notification_from) }
val colorBg = 0
nStatus?.let { showNotificationStatus(it, colorBg) }
tvMessageHolder.visibility = View.VISIBLE
tvMessageHolder.text = "notification type is ${n.type}"
tvMessageHolder.gravity = Gravity.CENTER
}
private fun ItemViewHolder.showNotificationStatus(item: TootStatus, colorBgDefault: Int) {
val reblog = item.reblog
when {
reblog == null -> showStatusOrReply(item, colorBgDefault)
item.isQuoteToot -> {
// 引用Renote
showReply(reblog, R.drawable.ic_repeat, R.string.quote_to)
showStatus(item, PrefI.ipEventBgColorQuote(activity.pref))
}
else -> {
// 通常のブースト。引用なしブースト。
// ブースト表示は通知イベントと被るのでしない
showStatusOrReply(reblog, PrefI.ipEventBgColorBoost(activity.pref))
}
}
}

View File

@ -0,0 +1,415 @@
package jp.juggler.subwaytooter
import android.text.SpannableStringBuilder
import android.view.View
import android.widget.ImageView
import jp.juggler.subwaytooter.api.entity.*
import jp.juggler.subwaytooter.table.ContentWarning
import jp.juggler.subwaytooter.table.MediaShown
import jp.juggler.subwaytooter.util.OpenSticker
import jp.juggler.subwaytooter.view.MyNetworkImageView
import jp.juggler.util.*
import org.jetbrains.anko.backgroundColor
import org.jetbrains.anko.textColor
fun ItemViewHolder.showStatusOrReply(item: TootStatus, colorBgArg: Int = 0) {
var colorBg = colorBgArg
val reply = item.reply
val inReplyToId = item.in_reply_to_id
val inReplyToAccountId = item.in_reply_to_account_id
when {
reply != null -> {
showReply(reply, R.drawable.ic_reply, R.string.reply_to)
if (colorBgArg == 0) colorBg = PrefI.ipEventBgColorMention(activity.pref)
}
inReplyToId != null && inReplyToAccountId != null -> {
showReply(item, inReplyToAccountId)
if (colorBgArg == 0) colorBg = PrefI.ipEventBgColorMention(activity.pref)
}
}
showStatus(item, colorBg)
}
fun ItemViewHolder.showStatus(status: TootStatus, colorBg: Int = 0) {
val filteredWord = status.filteredWord
if (filteredWord != null) {
showMessageHolder(
TootMessageHolder(
if (PrefB.bpShowFilteredWord(activity.pref)) {
"${activity.getString(R.string.filtered)} / $filteredWord"
} else {
activity.getString(R.string.filtered)
}
)
)
return
}
this.statusShowing = status
llStatus.visibility = View.VISIBLE
if (status.conversation_main) {
val conversationMainBgColor =
PrefI.ipConversationMainTootBgColor(activity.pref).notZero()
?: (activity.attrColor(R.attr.colorImageButtonAccent) and 0xffffff) or 0x20000000
this.viewRoot.setBackgroundColor(conversationMainBgColor)
} else {
val c = colorBg.notZero()
?: when (status.bookmarked) {
true -> PrefI.ipEventBgColorBookmark(App1.pref)
false -> 0
}.notZero()
?: when (status.getBackgroundColorType(accessInfo)) {
TootVisibility.UnlistedHome -> ItemViewHolder.toot_color_unlisted
TootVisibility.PrivateFollowers -> ItemViewHolder.toot_color_follower
TootVisibility.DirectSpecified -> ItemViewHolder.toot_color_direct_user
TootVisibility.DirectPrivate -> ItemViewHolder.toot_color_direct_me
// TODO add color setting for limited?
TootVisibility.Limited -> ItemViewHolder.toot_color_follower
else -> 0
}
if (c != 0) {
this.viewRoot.backgroundColor = c
}
}
showStatusTime(activity, tvTime, who = status.account, status = status)
val whoRef = status.accountRef
val who = whoRef.get()
this.statusAccount = whoRef
setAcct(tvAcct, accessInfo, who)
// if(who == null) {
// tvName.text = "?"
// name_invalidator.register(null)
// ivThumbnail.setImageUrl(activity.pref, 16f, null, null)
// } else {
tvName.text = whoRef.decoded_display_name
nameInvalidator.register(whoRef.decoded_display_name)
ivAvatar.setImageUrl(
activity.pref,
Styler.calcIconRound(ivAvatar.layoutParams),
accessInfo.supplyBaseUrl(who.avatar_static),
accessInfo.supplyBaseUrl(who.avatar)
)
// }
showOpenSticker(who)
var content = status.decoded_content
// ニコフレのアンケートの表示
val enquete = status.enquete
when {
enquete == null -> {
}
enquete.pollType == TootPollsType.FriendsNico && enquete.type != TootPolls.TYPE_ENQUETE -> {
// フレニコの投票の結果表示は普通にテキストを表示するだけでよい
}
else -> {
// アンケートの本文を上書きする
val question = enquete.decoded_question
if (question.isNotBlank()) content = question
showEnqueteItems(status, enquete)
}
}
showPreviewCard(status)
// if( status.decoded_tags == null ){
// tvTags.setVisibility( View.GONE );
// }else{
// tvTags.setVisibility( View.VISIBLE );
// tvTags.setText( status.decoded_tags );
// }
if (status.decoded_mentions.isEmpty()) {
tvMentions.visibility = View.GONE
} else {
tvMentions.visibility = View.VISIBLE
tvMentions.text = status.decoded_mentions
}
if (status.time_deleted_at > 0L) {
val s = SpannableStringBuilder()
.append('(')
.append(
activity.getString(
R.string.deleted_at,
TootStatus.formatTime(activity, status.time_deleted_at, true)
)
)
.append(')')
content = s
}
tvContent.text = content
contentInvalidator.register(content)
activity.checkAutoCW(status, content)
val r = status.auto_cw
tvContent.minLines = r?.originalLineCount ?: -1
val decodedSpoilerText = status.decoded_spoiler_text
when {
decodedSpoilerText.isNotEmpty() -> {
// 元データに含まれるContent Warning を使う
llContentWarning.visibility = View.VISIBLE
tvContentWarning.text = status.decoded_spoiler_text
spoilerInvalidator.register(status.decoded_spoiler_text)
val cwShown = ContentWarning.isShown(status, accessInfo.expand_cw)
showContent(cwShown)
}
r?.decodedSpoilerText != null -> {
// 自動CW
llContentWarning.visibility = View.VISIBLE
tvContentWarning.text = r.decodedSpoilerText
spoilerInvalidator.register(r.decodedSpoilerText)
val cwShown = ContentWarning.isShown(status, accessInfo.expand_cw)
showContent(cwShown)
}
else -> {
// CWしない
llContentWarning.visibility = View.GONE
llContents.visibility = View.VISIBLE
}
}
val mediaAttachments = status.media_attachments
if (mediaAttachments == null || mediaAttachments.isEmpty()) {
flMedia.visibility = View.GONE
llMedia.visibility = View.GONE
btnShowMedia.visibility = View.GONE
} else {
flMedia.visibility = View.VISIBLE
// hide sensitive media
val defaultShown = when {
column.hideMediaDefault -> false
accessInfo.dont_hide_nsfw -> true
else -> !status.sensitive
}
val isShown = MediaShown.isShown(status, defaultShown)
btnShowMedia.visibility = if (!isShown) View.VISIBLE else View.GONE
llMedia.visibility = if (!isShown) View.GONE else View.VISIBLE
val sb = StringBuilder()
setMedia(mediaAttachments, sb, ivMedia1, 0)
setMedia(mediaAttachments, sb, ivMedia2, 1)
setMedia(mediaAttachments, sb, ivMedia3, 2)
setMedia(mediaAttachments, sb, ivMedia4, 3)
val m0 =
if (mediaAttachments.isEmpty()) null else mediaAttachments[0] as? TootAttachment
btnShowMedia.blurhash = m0?.blurhash
if (sb.isNotEmpty()) {
tvMediaDescription.visibility = View.VISIBLE
tvMediaDescription.text = sb
}
setIconDrawableId(
activity,
btnHideMedia,
R.drawable.ic_close,
color = contentColor,
alphaMultiplier = Styler.boostAlpha
)
}
makeReactionsView(status)
buttonsForStatus?.bind(status, (item as? TootNotification))
var sb: StringBuilder? = null
fun prepareSb(): StringBuilder =
sb?.append(", ") ?: StringBuilder().also { sb = it }
val application = status.application
if (application != null &&
(column.type == ColumnType.CONVERSATION || PrefB.bpShowAppName(activity.pref))
) {
prepareSb().append(activity.getString(R.string.application_is, application.name ?: ""))
}
val language = status.language
if (language != null &&
(column.type == ColumnType.CONVERSATION || PrefB.bpShowLanguage(activity.pref))
) {
prepareSb().append(activity.getString(R.string.language_is, language))
}
tvApplication.vg(sb != null)?.text = sb
}
fun ItemViewHolder.showOpenSticker(who: TootAccount) {
try {
if (!Column.showOpenSticker) return
val host = who.apDomain
// LTLでホスト名が同じならTickerを表示しない
@Suppress("NON_EXHAUSTIVE_WHEN")
when (column.type) {
ColumnType.LOCAL, ColumnType.LOCAL_AROUND -> {
if (host == accessInfo.apDomain) return
}
}
val item = OpenSticker.lastList[host.ascii] ?: return
tvOpenSticker.text = item.name
tvOpenSticker.textColor = item.fontColor
val density = activity.density
val lp = ivOpenSticker.layoutParams
lp.height = (density * 16f + 0.5f).toInt()
lp.width = (density * item.imageWidth + 0.5f).toInt()
ivOpenSticker.layoutParams = lp
ivOpenSticker.setImageUrl(activity.pref, 0f, item.favicon)
val colorBg = item.bgColor
when (colorBg.size) {
1 -> {
val c = colorBg.first()
tvOpenSticker.setBackgroundColor(c)
ivOpenSticker.setBackgroundColor(c)
}
else -> {
ivOpenSticker.setBackgroundColor(colorBg.last())
tvOpenSticker.background = colorBg.getGradation()
}
}
llOpenSticker.visibility = View.VISIBLE
llOpenSticker.requestLayout()
} catch (ex: Throwable) {
ItemViewHolder.log.trace(ex)
}
}
fun ItemViewHolder.showContent(shown: Boolean) {
llContents.visibility = if (shown) View.VISIBLE else View.GONE
btnContentWarning.setText(if (shown) R.string.hide else R.string.show)
statusShowing?.let { status ->
val r = status.auto_cw
tvContent.minLines = r?.originalLineCount ?: -1
if (r?.decodedSpoilerText != null) {
// 自動CWの場合はContentWarningのテキストを切り替える
tvContentWarning.text =
if (shown) activity.getString(R.string.auto_cw_prefix) else r.decodedSpoilerText
}
}
}
fun ItemViewHolder.setMedia(
mediaAttachments: ArrayList<TootAttachmentLike>,
sbDesc: StringBuilder,
iv: MyNetworkImageView,
idx: Int,
) {
val ta = if (idx < mediaAttachments.size) mediaAttachments[idx] else null
if (ta == null) {
iv.visibility = View.GONE
return
}
iv.visibility = View.VISIBLE
iv.setFocusPoint(ta.focusX, ta.focusY)
if (PrefB.bpDontCropMediaThumb(App1.pref)) {
iv.scaleType = ImageView.ScaleType.FIT_CENTER
} else {
iv.setScaleTypeForMedia()
}
val showUrl: Boolean
when (ta.type) {
TootAttachmentType.Audio -> {
iv.setMediaType(0)
iv.setDefaultImage(Styler.defaultColorIcon(activity, R.drawable.wide_music))
iv.setImageUrl(activity.pref, 0f, ta.urlForThumbnail(activity.pref))
showUrl = true
}
TootAttachmentType.Unknown -> {
iv.setMediaType(0)
iv.setDefaultImage(Styler.defaultColorIcon(activity, R.drawable.wide_question))
iv.setImageUrl(activity.pref, 0f, null)
showUrl = true
}
else -> when (val urlThumbnail = ta.urlForThumbnail(activity.pref)) {
null, "" -> {
iv.setMediaType(0)
iv.setDefaultImage(Styler.defaultColorIcon(activity, R.drawable.wide_question))
iv.setImageUrl(activity.pref, 0f, null)
showUrl = true
}
else -> {
iv.setMediaType(
when (ta.type) {
TootAttachmentType.Video -> R.drawable.media_type_video
TootAttachmentType.GIFV -> R.drawable.media_type_gifv
else -> 0
}
)
iv.setDefaultImage(null)
iv.setImageUrl(
activity.pref,
0f,
accessInfo.supplyBaseUrl(urlThumbnail),
accessInfo.supplyBaseUrl(urlThumbnail)
)
showUrl = false
}
}
}
fun appendDescription(s: String) {
// val lp = LinearLayout.LayoutParams(
// LinearLayout.LayoutParams.MATCH_PARENT,
// LinearLayout.LayoutParams.WRAP_CONTENT
// )
// lp.topMargin = (0.5f + activity.density * 3f).toInt()
//
// val tv = MyTextView(activity)
// tv.layoutParams = lp
// //
// tv.movementMethod = MyLinkMovementMethod
// if(! activity.timeline_font_size_sp.isNaN()) {
// tv.textSize = activity.timeline_font_size_sp
// }
// tv.setTextColor(content_color)
if (sbDesc.isNotEmpty()) sbDesc.append("\n")
val desc = activity.getString(R.string.media_description, idx + 1, s)
sbDesc.append(desc)
}
when (val description = ta.description.notEmpty()) {
null -> if (showUrl) ta.urlForDescription.notEmpty()?.let { appendDescription(it) }
else -> appendDescription(description)
}
}

View File

@ -12,12 +12,17 @@ fun Context.pref(): SharedPreferences =
@Suppress("EqualsOrHashCode")
abstract class BasePref<T>(val key: String, val defVal: T) {
companion object {
// キー名と設定項目のマップ。インポートやアプリ設定で使う
val allPref = HashMap<String, BasePref<*>>()
}
init {
when {
Pref.map[key] != null -> error("Preference key duplicate: $key")
allPref[key] != null -> error("Preference key duplicate: $key")
else -> {
@Suppress("LeakingThis")
Pref.map[key] = this
allPref[key] = this
}
}
}
@ -122,11 +127,7 @@ fun SharedPreferences.Editor.put(item: LongPref, v: Long) =
fun SharedPreferences.Editor.put(item: FloatPref, v: Float) =
this.apply { item.put(this, v) }
object Pref {
// キー名と設定項目のマップ。インポートやアプリ設定で使う
val map = HashMap<String, BasePref<*>>()
object PrefB {
// boolean
val bpDisableEmojiAnimation = BooleanPref(
@ -461,7 +462,9 @@ object Pref {
"ManyWindowPost",
false
)
}
object PrefI {
// int
val ipBackButtonAction = IntPref("back_button_action", 0)
@ -481,6 +484,7 @@ object Pref {
@Suppress("unused")
const val RC_NONE = 2
val ipRepliesCount = IntPref("RepliesCount", RC_SIMPLE)
val ipBoostsCount = IntPref("BoostsCount", RC_ACTUAL)
val ipFavouritesCount = IntPref("FavouritesCount", RC_ACTUAL)
@ -498,13 +502,7 @@ object Pref {
const val VS_MISSKEY = 2
val ipVisibilityStyle = IntPref("ipVisibilityStyle", VS_BY_ACCOUNT)
const val ABP_TOP = 0
@Suppress("unused")
const val ABP_BOTTOM = 1
const val ABP_START = 2
const val ABP_END = 3
val ipAdditionalButtonsPosition = IntPref("AdditionalButtonsPosition", ABP_END)
val ipAdditionalButtonsPosition = IntPref("AdditionalButtonsPosition", AdditionalButtonsPosition.End.idx)
val ipFooterButtonBgColor = IntPref("footer_button_bg_color", 0)
val ipFooterButtonFgColor = IntPref("footer_button_fg_color", 0)
@ -574,6 +572,9 @@ object Pref {
// val ipTrendTagCountShowing = IntPref("TrendTagCountShowing", 0)
// const val TTCS_WEEKLY = 0
// const val TTCS_DAILY = 1
}
object PrefS {
// string
val spColumnWidth = StringPref("ColumnWidth", "")
@ -620,17 +621,21 @@ object Pref {
val spWebBrowser = StringPref("WebBrowser", "")
val spTimelineSpacing = StringPref("TimelineSpacing", "")
}
object PrefL {
// long
val lpTabletTootDefaultAccount = LongPref("tablet_toot_default_account", -1L)
}
object PrefF {
// float
val fpTimelineFontSize = FloatPref("timeline_font_size", Float.NaN)
val fpAcctFontSize = FloatPref("acct_font_size", Float.NaN)
val fpNotificationTlFontSize = FloatPref("notification_tl_font_size", Float.NaN)
val fpHeaderTextSize = FloatPref("HeaderTextSize", Float.NaN)
internal const val default_timeline_font_size = 14f
internal const val default_acct_font_size = 12f
internal const val default_notification_tl_font_size = 14f

View File

@ -72,7 +72,7 @@ class SideMenuAdapter(
)
)
val newRelease = releaseInfo?.jsonObject(
if (Pref.bpCheckBetaVersion(App1.pref)) "beta" else "stable"
if (PrefB.bpCheckBetaVersion(App1.pref)) "beta" else "stable"
)
val newVersion =

File diff suppressed because it is too large Load Diff

View File

@ -36,9 +36,9 @@ object Styler {
}
fun getVisibilityIconId(isMisskeyData: Boolean, visibility: TootVisibility): Int {
val isMisskey = when (Pref.ipVisibilityStyle(App1.pref)) {
Pref.VS_MASTODON -> false
Pref.VS_MISSKEY -> true
val isMisskey = when (PrefI.ipVisibilityStyle(App1.pref)) {
PrefI.VS_MASTODON -> false
PrefI.VS_MISSKEY -> true
else -> isMisskeyData
}
return when {
@ -84,9 +84,9 @@ object Styler {
isMisskeyData: Boolean,
visibility: TootVisibility
): String {
val isMisskey = when (Pref.ipVisibilityStyle(App1.pref)) {
Pref.VS_MASTODON -> false
Pref.VS_MISSKEY -> true
val isMisskey = when (PrefI.ipVisibilityStyle(App1.pref)) {
PrefI.VS_MASTODON -> false
PrefI.VS_MISSKEY -> true
else -> isMisskeyData
}
return context.getString(
@ -174,11 +174,11 @@ object Styler {
alphaMultiplier: Float
) {
fun colorAccent() =
Pref.ipButtonFollowingColor(context.pref()).notZero()
PrefI.ipButtonFollowingColor(context.pref()).notZero()
?: context.attrColor(R.attr.colorImageButtonAccent)
fun colorError() =
Pref.ipButtonFollowRequestColor(context.pref()).notZero()
PrefI.ipButtonFollowRequestColor(context.pref()).notZero()
?: context.attrColor(R.attr.colorRegexFilterError)
// 被フォロー状態
@ -299,13 +299,13 @@ object Styler {
val widthDp = dm.widthPixels / dm.density
if (widthDp >= 640f && v.resources?.configuration?.orientation == Configuration.ORIENTATION_PORTRAIT) {
val pad_lr = (0.5f + dpDelta * dm.density).toInt()
when (Pref.ipJustifyWindowContentPortrait(App1.pref)) {
Pref.JWCP_START -> {
when (PrefI.ipJustifyWindowContentPortrait(App1.pref)) {
PrefI.JWCP_START -> {
v.setPaddingRelative(pad_lr, pad_t, pad_lr + dm.widthPixels / 2, pad_b)
return
}
Pref.JWCP_END -> {
PrefI.JWCP_END -> {
v.setPaddingRelative(pad_lr + dm.widthPixels / 2, pad_t, pad_lr, pad_b)
return
}
@ -328,14 +328,14 @@ object Styler {
log.d("fixHorizontalMargin: orientation=$orientationString, w=${widthDp}dp, h=${dm.heightPixels / dm.density}")
if (widthDp >= 640f && v.resources?.configuration?.orientation == Configuration.ORIENTATION_PORTRAIT) {
when (Pref.ipJustifyWindowContentPortrait(App1.pref)) {
Pref.JWCP_START -> {
when (PrefI.ipJustifyWindowContentPortrait(App1.pref)) {
PrefI.JWCP_START -> {
lp.marginStart = 0
lp.marginEnd = dm.widthPixels / 2
return
}
Pref.JWCP_END -> {
PrefI.JWCP_END -> {
lp.marginStart = dm.widthPixels / 2
lp.marginEnd = 0
return

View File

@ -29,9 +29,22 @@ import org.jetbrains.anko.textColor
internal class ViewHolderHeaderProfile(
activity: ActMain,
viewRoot: View
viewRoot: View,
) : ViewHolderHeaderBase(activity, viewRoot), View.OnClickListener, View.OnLongClickListener {
companion object {
private fun SpannableStringBuilder.appendSpan(text: String, span: Any) {
val start = length
append(text)
setSpan(
span,
start,
length,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
)
}
}
private val ivBackground: MyNetworkImageView
private val tvCreated: TextView
private val tvLastStatusAt: TextView
@ -73,6 +86,9 @@ internal class ViewHolderHeaderProfile(
private val tvPersonalNotes: TextView
private val btnPersonalNotesEdit: ImageButton
private var contentColor = 0
private var relation: UserRelation? = null
init {
ivBackground = viewRoot.findViewById(R.id.ivBackground)
llProfile = viewRoot.findViewById(R.id.llProfile)
@ -136,6 +152,38 @@ internal class ViewHolderHeaderProfile(
ivBackground.measureProfileBg = true
}
override fun getAccount(): TootAccountRef? = whoRef
override fun onViewRecycled() {
}
// fun updateRelativeTime() {
// val who = whoRef?.get()
// if(who != null) {
// tvCreated.text = TootStatus.formatTime(tvCreated.context, who.time_created_at, true)
// }
// }
override fun bindData(column: Column) {
super.bindData(column)
bindFonts()
bindColors()
llMoved.visibility = View.GONE
tvMoved.visibility = View.GONE
llFields.visibility = View.GONE
llFields.removeAllViews()
val whoRef = column.whoAccount
this.whoRef = whoRef
when (val who = whoRef?.get()) {
null -> bindAccountNull()
else -> bindAccount(who, whoRef)
}
}
// カラム設定から戻った際に呼ばれる
override fun showColor() {
llProfile.setBackgroundColor(
when (val c = column.columnBgColor) {
@ -145,36 +193,8 @@ internal class ViewHolderHeaderProfile(
)
}
private var contentColor = 0
private var relation: UserRelation? = null
override fun bindData(column: Column) {
super.bindData(column)
var f: Float
f = activity.timelineFontSizeSp
if (!f.isNaN()) {
tvMovedName.textSize = f
tvMoved.textSize = f
tvPersonalNotes.textSize = f
tvFeaturedTags.textSize = f
}
f = activity.acctFontSizeSp
if (!f.isNaN()) {
tvMovedAcct.textSize = f
tvCreated.textSize = f
tvLastStatusAt.textSize = f
}
val spacing = activity.timelineSpacing
if (spacing != null) {
tvMovedName.setLineSpacing(0f, spacing)
tvMoved.setLineSpacing(0f, spacing)
}
// bind時に呼ばれる
private fun bindColors() {
val contentColor = column.getContentColor()
this.contentColor = contentColor
@ -210,313 +230,140 @@ internal class ViewHolderHeaderProfile(
tvMovedAcct.textColor = acctColor
tvLastStatusAt.textColor = acctColor
val whoRef = column.whoAccount
this.whoRef = whoRef
val who = whoRef?.get()
showColor()
}
private fun bindFonts() {
var f: Float
f = activity.timelineFontSizeSp
if (!f.isNaN()) {
tvMovedName.textSize = f
tvMoved.textSize = f
tvPersonalNotes.textSize = f
tvFeaturedTags.textSize = f
}
f = activity.acctFontSizeSp
if (!f.isNaN()) {
tvMovedAcct.textSize = f
tvCreated.textSize = f
tvLastStatusAt.textSize = f
}
val spacing = activity.timelineSpacing
if (spacing != null) {
tvMovedName.setLineSpacing(0f, spacing)
tvMoved.setLineSpacing(0f, spacing)
}
}
private fun bindAccountNull() {
relation = null
tvCreated.text = ""
tvLastStatusAt.vg(false)
tvFeaturedTags.vg(false)
ivBackground.setImageDrawable(null)
ivAvatar.setImageDrawable(null)
tvAcct.text = "@"
tvDisplayName.text = ""
nameInvalidator1.register(null)
tvNote.text = ""
tvMisskeyExtra.text = ""
noteInvalidator.register(null)
btnStatusCount.text = activity.getString(R.string.statuses) + "\n" + "?"
btnFollowing.text = activity.getString(R.string.following) + "\n" + "?"
btnFollowers.text = activity.getString(R.string.followers) + "\n" + "?"
btnFollow.setImageDrawable(null)
tvRemoteProfileWarning.visibility = View.GONE
}
private fun bindAccount(who: TootAccount, whoRef: TootAccountRef) {
// Misskeyの場合はNote中のUserエンティティと /api/users/show の情報量がかなり異なる
val whoDetail = if (who == null) {
null
} else {
MisskeyAccountDetailMap.get(accessInfo, who.id)
val whoDetail = MisskeyAccountDetailMap.get(accessInfo, who.id)
tvCreated.text =
TootStatus.formatTime(tvCreated.context, (whoDetail ?: who).time_created_at, true)
who.setAccountExtra(
accessInfo,
tvLastStatusAt,
invalidator = null,
fromProfileHeader = true
)
val featuredTagsText = formatFeaturedTags()
tvFeaturedTags.vg(featuredTagsText != null)?.let {
it.text = featuredTagsText!!
it.movementMethod = MyLinkMovementMethod
}
showColor()
ivBackground.setImageUrl(activity.pref, 0f, accessInfo.supplyBaseUrl(who.header_static))
llMoved.visibility = View.GONE
tvMoved.visibility = View.GONE
llFields.visibility = View.GONE
llFields.removeAllViews()
ivAvatar.setImageUrl(
activity.pref,
Styler.calcIconRound(ivAvatar.layoutParams),
accessInfo.supplyBaseUrl(who.avatar_static),
accessInfo.supplyBaseUrl(who.avatar)
)
if (who == null) {
relation = null
tvCreated.text = ""
tvLastStatusAt.vg(false)
tvFeaturedTags.vg(false)
ivBackground.setImageDrawable(null)
ivAvatar.setImageDrawable(null)
val name = whoDetail?.decodeDisplayName(activity) ?: whoRef.decoded_display_name
tvDisplayName.text = name
nameInvalidator1.register(name)
tvAcct.text = "@"
tvRemoteProfileWarning.vg(column.accessInfo.isRemoteUser(who))
tvDisplayName.text = ""
nameInvalidator1.register(null)
tvAcct.text = encodeAcctText(who, whoDetail)
tvNote.text = ""
tvMisskeyExtra.text = ""
noteInvalidator.register(null)
val note = whoRef.decoded_note
tvNote.text = note
noteInvalidator.register(note)
btnStatusCount.text = activity.getString(R.string.statuses) + "\n" + "?"
btnFollowing.text = activity.getString(R.string.following) + "\n" + "?"
btnFollowers.text = activity.getString(R.string.followers) + "\n" + "?"
tvMisskeyExtra.text = encodeMisskeyExtra(whoDetail)
tvMisskeyExtra.vg(tvMisskeyExtra.text.isNotEmpty())
btnFollow.setImageDrawable(null)
tvRemoteProfileWarning.visibility = View.GONE
} else {
tvCreated.text =
TootStatus.formatTime(tvCreated.context, (whoDetail ?: who).time_created_at, true)
btnStatusCount.text =
"${activity.getString(R.string.statuses)}\n${
whoDetail?.statuses_count ?: who.statuses_count
}"
who.setAccountExtra(
accessInfo,
tvLastStatusAt,
invalidator = null,
fromProfileHeader = true
)
val hideFollowCount = PrefB.bpHideFollowCount(activity.pref)
val featuredTagsText = column.whoFeaturedTags?.notEmpty()?.let { tagList ->
SpannableStringBuilder().apply {
append(activity.getString(R.string.featured_hashtags))
append(":")
tagList.forEach { tag ->
append(" ")
val tagWithSharp = "#" + tag.name
val start = length
append(tagWithSharp)
val end = length
tag.url?.notEmpty()?.let { url ->
val span = MyClickableSpan(
LinkInfo(url = url, tag = tag.name, caption = tagWithSharp)
)
setSpan(span, start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
}
}
}
}
tvFeaturedTags.vg(featuredTagsText != null)?.let {
it.text = featuredTagsText!!
it.movementMethod = MyLinkMovementMethod
}
ivBackground.setImageUrl(
activity.pref,
0f,
accessInfo.supplyBaseUrl(who.header_static)
)
ivAvatar.setImageUrl(
activity.pref,
Styler.calcIconRound(ivAvatar.layoutParams),
accessInfo.supplyBaseUrl(who.avatar_static),
accessInfo.supplyBaseUrl(who.avatar)
)
val name = whoDetail?.decodeDisplayName(activity) ?: whoRef.decoded_display_name
tvDisplayName.text = name
nameInvalidator1.register(name)
tvRemoteProfileWarning.visibility =
if (column.accessInfo.isRemoteUser(who)) View.VISIBLE else View.GONE
fun SpannableStringBuilder.appendSpan(text: String, span: Any) {
val start = length
append(text)
setSpan(
span,
start,
length,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
)
}
tvAcct.text = SpannableStringBuilder().apply {
append("@")
append(accessInfo.getFullAcct(who).pretty)
if (whoDetail?.locked ?: who.locked) {
append(" ")
val emoji = EmojiMap.shortNameMap["lock"]
if (emoji != null) {
appendSpan("locked", emoji.createSpan(activity))
} else {
append("locked")
}
}
if (who.bot) {
append(" ")
val emoji = EmojiMap.shortNameMap["robot_face"]
if (emoji != null) {
appendSpan("bot", emoji.createSpan(activity))
} else {
append("bot")
}
}
if (who.suspended) {
append(" ")
val emoji = EmojiMap.shortNameMap["cross_mark"]
if (emoji != null) {
appendSpan("suspended", emoji.createSpan(activity))
} else {
append("suspended")
}
}
}
val note = whoRef.decoded_note
tvNote.text = note
noteInvalidator.register(note)
tvMisskeyExtra.text = SpannableStringBuilder().apply {
var s = whoDetail?.location
if (s?.isNotEmpty() == true) {
if (isNotEmpty()) append('\n')
appendSpan(
activity.getString(R.string.location),
EmojiImageSpan(
activity,
R.drawable.ic_location,
useColorShader = true
)
)
append(' ')
append(s)
}
s = whoDetail?.birthday
if (s?.isNotEmpty() == true) {
if (isNotEmpty()) append('\n')
appendSpan(
activity.getString(R.string.birthday),
EmojiImageSpan(
activity,
R.drawable.ic_cake,
useColorShader = true
)
)
append(' ')
append(s)
}
}
tvMisskeyExtra.vg(tvMisskeyExtra.text.isNotEmpty())
btnStatusCount.text =
"${activity.getString(R.string.statuses)}\n${
whoDetail?.statuses_count
?: who.statuses_count
}"
if (Pref.bpHideFollowCount(activity.pref)) {
btnFollowing.text = activity.getString(R.string.following)
btnFollowers.text = activity.getString(R.string.followers)
} else {
btnFollowing.text =
"${activity.getString(R.string.following)}\n${
whoDetail?.following_count ?: who.following_count
}"
btnFollowers.text =
"${activity.getString(R.string.followers)}\n${
whoDetail?.followers_count ?: who.followers_count
}"
}
val relation = UserRelation.load(accessInfo.db_id, who.id)
this.relation = relation
Styler.setFollowIcon(
activity,
btnFollow,
ivFollowedBy,
relation,
who,
contentColor,
alphaMultiplier = Styler.boostAlpha
)
tvPersonalNotes.text = relation.note ?: ""
showMoved(who, who.movedRef)
val fields = whoDetail?.fields ?: who.fields
if (fields != null) {
llFields.visibility = View.VISIBLE
// fieldsのnameにはカスタム絵文字が適用されるようになった
// https://github.com/tootsuite/mastodon/pull/11350
// fieldsのvalueはMisskeyならMFM、MastodonならHTML
val fieldDecodeOptions = DecodeOptions(
context = activity,
decodeEmoji = true,
linkHelper = accessInfo,
short = true,
emojiMapCustom = who.custom_emojis,
emojiMapProfile = who.profile_emojis,
mentionDefaultHostDomain = who
)
val nameTypeface = ActMain.timeline_font_bold
val valueTypeface = ActMain.timelineFont
for (item in fields) {
//
val nameView = MyTextView(activity)
val nameLp = LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT
)
val nameText = fieldDecodeOptions.decodeEmoji(item.name)
val nameInvalidator = NetworkEmojiInvalidator(activity.handler, nameView)
nameInvalidator.register(nameText)
nameLp.topMargin = (density * 6f).toInt()
nameView.layoutParams = nameLp
nameView.text = nameText
nameView.setTextColor(contentColor)
nameView.typeface = nameTypeface
nameView.movementMethod = MyLinkMovementMethod
llFields.addView(nameView)
// 値の方はHTMLエンコードされている
val valueView = MyTextView(activity)
val valueLp = LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT
)
val valueText = fieldDecodeOptions.decodeHTML(item.value)
if (item.verified_at > 0L) {
valueText.append('\n')
val start = valueText.length
valueText.append(activity.getString(R.string.verified_at))
valueText.append(": ")
valueText.append(TootStatus.formatTime(activity, item.verified_at, false))
val end = valueText.length
val linkFgColor = Pref.ipVerifiedLinkFgColor(activity.pref).notZero()
?: (Color.BLACK or 0x7fbc99)
valueText.setSpan(
ForegroundColorSpan(linkFgColor),
start,
end,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
)
}
val valueInvalidator = NetworkEmojiInvalidator(activity.handler, valueView)
valueInvalidator.register(valueText)
valueLp.startMargin = (density * 32f).toInt()
valueView.layoutParams = valueLp
valueView.text = valueText
valueView.setTextColor(contentColor)
valueView.typeface = valueTypeface
valueView.movementMethod = MyLinkMovementMethod
if (item.verified_at > 0L) {
val linkBgColor = Pref.ipVerifiedLinkBgColor(activity.pref).notZero()
?: (0x337fbc99)
valueView.setBackgroundColor(linkBgColor)
}
llFields.addView(valueView)
}
}
var caption = activity.getString(R.string.following)
btnFollowing.text = when {
hideFollowCount -> caption
else -> "${caption}\n${whoDetail?.following_count ?: who.following_count}"
}
caption = activity.getString(R.string.followers)
btnFollowers.text = when {
hideFollowCount -> caption
else -> "${caption}\n${whoDetail?.followers_count ?: who.followers_count}"
}
val relation = UserRelation.load(accessInfo.db_id, who.id)
this.relation = relation
Styler.setFollowIcon(
activity,
btnFollow,
ivFollowedBy,
relation,
who,
contentColor,
alphaMultiplier = Styler.boostAlpha
)
tvPersonalNotes.text = relation.note ?: ""
showMoved(who, who.movedRef)
(whoDetail?.fields ?: who.fields)?.notEmpty()?.let { showFields(who, it) }
}
private fun showMoved(who: TootAccount, movedRef: TootAccountRef?) {
@ -557,20 +404,6 @@ internal class ViewHolderHeaderProfile(
)
}
private fun setAcct(tv: TextView, accessInfo: SavedAccount, who: TootAccount) {
val ac = AcctColor.load(accessInfo, who)
tv.text = when {
AcctColor.hasNickname(ac) -> ac.nickname
Pref.bpShortAcctLocalUser(App1.pref) -> "@${who.acct.pretty}"
else -> "@${ac.nickname}"
}
tv.textColor = ac.color_fg.notZero() ?: column.getAcctColor()
tv.setBackgroundColor(ac.color_bg) // may 0
tv.setPaddingRelative(activity.acctPadLr, 0, activity.acctPadLr, 0)
}
override fun onClick(v: View) {
when (v.id) {
@ -695,15 +528,190 @@ internal class ViewHolderHeaderProfile(
return false
}
override fun onViewRecycled() {
private fun setAcct(tv: TextView, accessInfo: SavedAccount, who: TootAccount) {
val ac = AcctColor.load(accessInfo, who)
tv.text = when {
AcctColor.hasNickname(ac) -> ac.nickname
PrefB.bpShortAcctLocalUser(App1.pref) -> "@${who.acct.pretty}"
else -> "@${ac.nickname}"
}
tv.textColor = ac.color_fg.notZero() ?: column.getAcctColor()
tv.setBackgroundColor(ac.color_bg) // may 0
tv.setPaddingRelative(activity.acctPadLr, 0, activity.acctPadLr, 0)
}
// fun updateRelativeTime() {
// val who = whoRef?.get()
// if(who != null) {
// tvCreated.text = TootStatus.formatTime(tvCreated.context, who.time_created_at, true)
// }
// }
private fun formatFeaturedTags() = column.whoFeaturedTags?.notEmpty()?.let { tagList ->
SpannableStringBuilder().apply {
append(activity.getString(R.string.featured_hashtags))
append(":")
tagList.forEach { tag ->
append(" ")
val tagWithSharp = "#" + tag.name
val start = length
append(tagWithSharp)
val end = length
tag.url?.notEmpty()?.let { url ->
val span = MyClickableSpan(
LinkInfo(url = url, tag = tag.name, caption = tagWithSharp)
)
setSpan(span, start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
}
}
}
}
override fun getAccount(): TootAccountRef? = whoRef
private fun encodeAcctText(who: TootAccount, whoDetail: TootAccount?) = SpannableStringBuilder().apply {
append("@")
append(accessInfo.getFullAcct(who).pretty)
if (whoDetail?.locked ?: who.locked) {
append(" ")
val emoji = EmojiMap.shortNameMap["lock"]
if (emoji != null) {
appendSpan("locked", emoji.createSpan(activity))
} else {
append("locked")
}
}
if (who.bot) {
append(" ")
val emoji = EmojiMap.shortNameMap["robot_face"]
if (emoji != null) {
appendSpan("bot", emoji.createSpan(activity))
} else {
append("bot")
}
}
if (who.suspended) {
append(" ")
val emoji = EmojiMap.shortNameMap["cross_mark"]
if (emoji != null) {
appendSpan("suspended", emoji.createSpan(activity))
} else {
append("suspended")
}
}
}
private fun encodeMisskeyExtra(whoDetail: TootAccount?) = SpannableStringBuilder().apply {
var s = whoDetail?.location
if (s?.isNotEmpty() == true) {
if (isNotEmpty()) append('\n')
appendSpan(
activity.getString(R.string.location),
EmojiImageSpan(
activity,
R.drawable.ic_location,
useColorShader = true
)
)
append(' ')
append(s)
}
s = whoDetail?.birthday
if (s?.isNotEmpty() == true) {
if (isNotEmpty()) append('\n')
appendSpan(
activity.getString(R.string.birthday),
EmojiImageSpan(
activity,
R.drawable.ic_cake,
useColorShader = true
)
)
append(' ')
append(s)
}
}
private fun showFields(who: TootAccount, fields: List<TootAccount.Field>) {
llFields.visibility = View.VISIBLE
// fieldsのnameにはカスタム絵文字が適用されるようになった
// https://github.com/tootsuite/mastodon/pull/11350
// fieldsのvalueはMisskeyならMFM、MastodonならHTML
val fieldDecodeOptions = DecodeOptions(
context = activity,
decodeEmoji = true,
linkHelper = accessInfo,
short = true,
emojiMapCustom = who.custom_emojis,
emojiMapProfile = who.profile_emojis,
mentionDefaultHostDomain = who
)
val nameTypeface = ActMain.timeline_font_bold
val valueTypeface = ActMain.timelineFont
for (item in fields) {
//
val nameView = MyTextView(activity)
val nameLp = LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT
)
val nameText = fieldDecodeOptions.decodeEmoji(item.name)
val nameInvalidator = NetworkEmojiInvalidator(activity.handler, nameView)
nameInvalidator.register(nameText)
nameLp.topMargin = (density * 6f).toInt()
nameView.layoutParams = nameLp
nameView.text = nameText
nameView.setTextColor(contentColor)
nameView.typeface = nameTypeface
nameView.movementMethod = MyLinkMovementMethod
llFields.addView(nameView)
// 値の方はHTMLエンコードされている
val valueView = MyTextView(activity)
val valueLp = LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT
)
val valueText = fieldDecodeOptions.decodeHTML(item.value)
if (item.verified_at > 0L) {
valueText.append('\n')
val start = valueText.length
valueText.append(activity.getString(R.string.verified_at))
valueText.append(": ")
valueText.append(TootStatus.formatTime(activity, item.verified_at, false))
val end = valueText.length
val linkFgColor = PrefI.ipVerifiedLinkFgColor(activity.pref).notZero()
?: (Color.BLACK or 0x7fbc99)
valueText.setSpan(
ForegroundColorSpan(linkFgColor),
start,
end,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
)
}
val valueInvalidator = NetworkEmojiInvalidator(activity.handler, valueView)
valueInvalidator.register(valueText)
valueLp.startMargin = (density * 32f).toInt()
valueView.layoutParams = valueLp
valueView.text = valueText
valueView.setTextColor(contentColor)
valueView.typeface = valueTypeface
valueView.movementMethod = MyLinkMovementMethod
if (item.verified_at > 0L) {
val linkBgColor = PrefI.ipVerifiedLinkBgColor(activity.pref).notZero()
?: (0x337fbc99)
valueView.setBackgroundColor(linkBgColor)
}
llFields.addView(valueView)
}
}
}

View File

@ -116,10 +116,10 @@ fun ActMain.accountAdd() {
when (action) {
LoginForm.Action.Existing ->
client.authentication1(Pref.spClientName(pref))
client.authentication1(PrefS.spClientName(pref))
LoginForm.Action.Create ->
client.createUser1(Pref.spClientName(pref))
client.createUser1(PrefS.spClientName(pref))
LoginForm.Action.Pseudo, LoginForm.Action.Token -> {
val (ti, ri) = TootInstance.get(client)
@ -204,8 +204,8 @@ fun AppCompatActivity.accountRemove(account: SavedAccount) {
// if account is default account of tablet mode,
// reset default.
val pref = pref()
if (account.db_id == Pref.lpTabletTootDefaultAccount(pref)) {
pref.edit().put(Pref.lpTabletTootDefaultAccount, -1L).apply()
if (account.db_id == PrefL.lpTabletTootDefaultAccount(pref)) {
pref.edit().put(PrefL.lpTabletTootDefaultAccount, -1L).apply()
}
account.delete()

View File

@ -0,0 +1,419 @@
package jp.juggler.subwaytooter.action
import androidx.appcompat.app.AlertDialog
import jp.juggler.subwaytooter.*
import jp.juggler.subwaytooter.api.*
import jp.juggler.subwaytooter.api.entity.Acct
import jp.juggler.subwaytooter.api.entity.EntityId
import jp.juggler.subwaytooter.api.entity.TootStatus
import jp.juggler.subwaytooter.api.entity.TootVisibility
import jp.juggler.subwaytooter.dialog.DlgConfirm
import jp.juggler.subwaytooter.dialog.pickAccount
import jp.juggler.subwaytooter.table.AcctColor
import jp.juggler.subwaytooter.table.SavedAccount
import jp.juggler.subwaytooter.util.emptyCallback
import jp.juggler.util.JsonObject
import jp.juggler.util.launchMain
import jp.juggler.util.showToast
import jp.juggler.util.toPostRequestBuilder
import java.util.ArrayList
import kotlin.math.max
private class BoostImpl(
val activity: ActMain,
val accessInfo: SavedAccount,
val statusArg: TootStatus,
val statusOwner: Acct,
val crossAccountMode: CrossAccountMode,
val bSet: Boolean = true,
val visibility: TootVisibility? = null,
val callback: () -> Unit,
) {
// Mastodonは非公開トゥートをブーストできるのは本人だけ
val isPrivateToot = accessInfo.isMastodon &&
statusArg.visibility == TootVisibility.PrivateFollowers
var bConfirmed = false
fun preCheck(): Boolean {
// アカウントからステータスにブースト操作を行っているなら、何もしない
if (activity.appState.isBusyBoost(accessInfo, statusArg)) {
activity.showToast(false, R.string.wait_previous_operation)
return false
}
if (isPrivateToot && accessInfo.acct != statusOwner) {
activity.showToast(false, R.string.boost_private_toot_not_allowed)
return false
}
// DMとかのブーストはAPI側がエラーを出すだろう
return true
}
fun confirm(): Boolean {
if (bConfirmed) return true
DlgConfirm.open(
activity,
activity.getString(
when {
!bSet -> R.string.confirm_unboost_from
isPrivateToot -> R.string.confirm_boost_private_from
visibility == TootVisibility.PrivateFollowers -> R.string.confirm_private_boost_from
else -> R.string.confirm_boost_from
},
AcctColor.getNickname(accessInfo)
),
object : DlgConfirm.Callback {
override fun onOK() {
bConfirmed = true
run()
}
override var isConfirmEnabled: Boolean
get() = when (bSet) {
true -> accessInfo.confirm_boost
else -> accessInfo.confirm_unboost
}
set(value) {
when (bSet) {
true -> accessInfo.confirm_boost = value
else -> accessInfo.confirm_unboost = value
}
accessInfo.saveSetting()
activity.reloadAccountSetting(accessInfo)
}
})
return false
}
fun run() {
if (!preCheck()) return
if (!confirm()) return
activity.appState.setBusyBoost(accessInfo, statusArg)
// ブースト表示を更新中にする
activity.showColumnMatchAccount(accessInfo)
// misskeyは非公開トゥートをブーストできないっぽい
launchMain {
var resultStatus: TootStatus? = null
var resultUnrenoteId: EntityId? = null
val result = activity.runApiTask(accessInfo, progressStyle = ApiTask.PROGRESS_NONE) { client ->
val parser = TootParser(this, accessInfo)
val targetStatus = if (crossAccountMode.isRemote) {
val (result, status) = client.syncStatus(accessInfo, statusArg)
if (status == null) return@runApiTask result
if (status.reblogged) {
return@runApiTask TootApiResult(getString(R.string.already_boosted))
}
status
} else {
// 既に自タンスのステータスがある
statusArg
}
if (accessInfo.isMisskey) {
if (!bSet) {
val myRenoteId = targetStatus.myRenoteId
?: return@runApiTask TootApiResult("missing renote id.")
client.request(
"/api/notes/delete",
accessInfo.putMisskeyApiToken().apply {
put("noteId", myRenoteId.toString())
put("renoteId", targetStatus.id.toString())
}
.toPostRequestBuilder()
)
?.also {
if (it.response?.code == 204) {
resultUnrenoteId = myRenoteId
}
}
} else {
client.request(
"/api/notes/create",
accessInfo.putMisskeyApiToken().apply {
put("renoteId", targetStatus.id.toString())
}
.toPostRequestBuilder()
)
?.also { result ->
val jsonObject = result.jsonObject
if (jsonObject != null) {
val outerStatus = parser.status(
jsonObject.jsonObject("createdNote")
?: jsonObject
)
val innerStatus = outerStatus?.reblog ?: outerStatus
if (outerStatus != null && innerStatus != null && outerStatus != innerStatus) {
innerStatus.myRenoteId = outerStatus.id
innerStatus.reblogged = true
}
// renoteそのものではなくrenoteされた元noteが欲しい
resultStatus = innerStatus
}
}
}
} else {
val b = JsonObject().apply {
if (visibility != null) put("visibility", visibility.strMastodon)
}.toPostRequestBuilder()
client.request(
"/api/v1/statuses/${targetStatus.id}/${if (bSet) "reblog" else "unreblog"}",
b
)?.also { result ->
// reblogはreblogを表すStatusを返す
// unreblogはreblogしたStatusを返す
val s = parser.status(result.jsonObject)
resultStatus = s?.reblog ?: s
}
}
}
activity.appState.resetBusyBoost(accessInfo, statusArg)
if (result != null) {
val unrenoteId = resultUnrenoteId
val newStatus = resultStatus
when {
// Misskeyでunrenoteに成功した
unrenoteId != null -> {
// 星を外したのにカウントが下がらないのは違和感あるので、表示をいじる
// 0未満にはならない
val count = max(0, (statusArg.reblogs_count ?: 1) - 1)
for (column in activity.appState.columnList) {
column.findStatus(
accessInfo.apDomain,
statusArg.id
) { account, status ->
// 同タンス別アカウントでもカウントは変化する
status.reblogs_count = count
// 同アカウントならreblogged状態を変化させる
if (accessInfo == account && status.myRenoteId == unrenoteId) {
status.myRenoteId = null
status.reblogged = false
}
true
}
}
callback()
}
// 処理に成功した
newStatus != null -> {
// カウント数は遅延があるみたいなので、恣意的に表示を変更する
// ブーストカウント数を加工する
val oldCount = statusArg.reblogs_count
val newCount = newStatus.reblogs_count
if (oldCount != null && newCount != null) {
if (bSet && newStatus.reblogged && newCount <= oldCount) {
// 星をつけたのにカウントが上がらないのは違和感あるので、表示をいじる
newStatus.reblogs_count = oldCount + 1
} else if (!bSet && !newStatus.reblogged && newCount >= oldCount) {
// 星を外したのにカウントが下がらないのは違和感あるので、表示をいじる
// 0未満にはならない
newStatus.reblogs_count = if (oldCount < 1) 0 else oldCount - 1
}
}
for (column in activity.appState.columnList) {
column.findStatus(
accessInfo.apDomain,
newStatus.id
) { account, status ->
// 同タンス別アカウントでもカウントは変化する
status.reblogs_count = newStatus.reblogs_count
if (accessInfo == account) {
// 同アカウントならreblog状態を変化させる
when {
accessInfo.isMastodon ->
status.reblogged = newStatus.reblogged
bSet && status.myRenoteId == null -> {
status.myRenoteId = newStatus.myRenoteId
status.reblogged = true
}
}
// Misskey のunrenote時はここを通らない
}
true
}
}
callback()
}
else -> activity.showToast(true, result.error)
}
}
// 結果に関わらず、更新中状態から復帰させる
activity.showColumnMatchAccount(accessInfo)
}
}
}
fun ActMain.boost(
accessInfo: SavedAccount,
statusArg: TootStatus,
statusOwner: Acct,
crossAccountMode: CrossAccountMode,
bSet: Boolean = true,
visibility: TootVisibility? = null,
callback: () -> Unit,
) {
BoostImpl(
activity = this,
accessInfo = accessInfo,
statusArg = statusArg,
statusOwner = statusOwner,
crossAccountMode = crossAccountMode,
bSet = bSet,
visibility = visibility,
callback = callback,
).run()
}
fun ActMain.boostFromAnotherAccount(
timelineAccount: SavedAccount,
status: TootStatus?,
) {
status ?: return
launchMain {
val statusOwner = timelineAccount.getFullAcct(status.account)
val isPrivateToot = timelineAccount.isMastodon &&
status.visibility == TootVisibility.PrivateFollowers
if (isPrivateToot) {
val list = ArrayList<SavedAccount>()
for (a in SavedAccount.loadAccountList(applicationContext)) {
if (a.acct == statusOwner) list.add(a)
}
if (list.isEmpty()) {
showToast(false, R.string.boost_private_toot_not_allowed)
return@launchMain
}
pickAccount(
bAllowPseudo = false,
bAuto = false,
message = getString(R.string.account_picker_boost),
accountListArg = list
)?.let { action_account ->
boost(
action_account,
status,
statusOwner,
calcCrossAccountMode(timelineAccount, action_account),
callback = boostCompleteCallback
)
}
} else {
pickAccount(
bAllowPseudo = false,
bAuto = false,
message = getString(R.string.account_picker_boost),
accountListArg = accountListNonPseudo(timelineAccount.apDomain)
)?.let { action_account ->
boost(
action_account,
status,
statusOwner,
calcCrossAccountMode(timelineAccount, action_account),
callback = boostCompleteCallback
)
}
}
}
}
fun ActMain.clickBoostWithVisibility(
accessInfo: SavedAccount,
status: TootStatus?,
) {
status ?: return
val list = if (accessInfo.isMisskey) {
arrayOf(
TootVisibility.Public,
TootVisibility.UnlistedHome,
TootVisibility.PrivateFollowers,
TootVisibility.LocalPublic,
TootVisibility.LocalHome,
TootVisibility.LocalFollowers,
TootVisibility.DirectSpecified,
TootVisibility.DirectPrivate
)
} else {
arrayOf(
TootVisibility.Public,
TootVisibility.UnlistedHome,
TootVisibility.PrivateFollowers
)
}
val captionList = list
.map { Styler.getVisibilityCaption(this, accessInfo.isMisskey, it) }
.toTypedArray()
AlertDialog.Builder(this)
.setTitle(R.string.choose_visibility)
.setItems(captionList) { _, which ->
if (which in list.indices) {
boost(
accessInfo,
status,
accessInfo.getFullAcct(status.account),
CrossAccountMode.SameAccount,
visibility = list[which],
callback = boostCompleteCallback,
)
}
}
.setNegativeButton(R.string.cancel, null)
.show()
}
fun ActMain.clickBoostBy(
pos: Int,
accessInfo: SavedAccount,
status: TootStatus?,
columnType: ColumnType = ColumnType.BOOSTED_BY,
) {
status ?: return
addColumn(false, pos, accessInfo, columnType, status.id)
}
fun ActMain.clickBoost(accessInfo: SavedAccount, status: TootStatus, willToast: Boolean) {
if (accessInfo.isPseudo) {
boostFromAnotherAccount(accessInfo, status)
} else {
// トグル動作
val bSet = !status.reblogged
boost(
accessInfo,
status,
accessInfo.getFullAcct(status.account),
CrossAccountMode.SameAccount,
bSet = bSet,
callback = when {
!willToast -> emptyCallback
// 簡略表示なら結果をトースト表示
bSet -> boostCompleteCallback
else -> unboostCompleteCallback
},
)
}
}

View File

@ -1,16 +1,10 @@
package jp.juggler.subwaytooter.action
import android.content.Context
import jp.juggler.subwaytooter.ActMain
import jp.juggler.subwaytooter.ColumnType
import jp.juggler.subwaytooter.R
import jp.juggler.subwaytooter.*
import jp.juggler.subwaytooter.api.*
import jp.juggler.subwaytooter.api.entity.EntityId
import jp.juggler.subwaytooter.api.entity.Host
import jp.juggler.subwaytooter.api.entity.TootConversationSummary
import jp.juggler.subwaytooter.api.entity.TootStatus
import jp.juggler.subwaytooter.api.entity.*
import jp.juggler.subwaytooter.dialog.ActionsDialog
import jp.juggler.subwaytooter.findStatus
import jp.juggler.subwaytooter.table.AcctColor
import jp.juggler.subwaytooter.table.SavedAccount
import jp.juggler.subwaytooter.util.matchHost
@ -20,6 +14,77 @@ import java.util.*
private val log = LogCategory("Action_Conversation")
fun ActMain.clickConversation(
pos: Int,
accessInfo: SavedAccount,
// optional. 未読表示のクリアに使う
listAdapter: ItemListAdapter? = null,
// どちらか非nullであること
status: TootStatus? = null,
summary: TootConversationSummary? = null,
) {
// 未読クリアと表示の更新
(summary ?: status?.conversationSummary)?.let {
if (conversationUnreadClear(accessInfo, it)) {
listAdapter?.notifyChange(
reason = "ConversationSummary reset unread",
reset = true
)
}
}
// 会話カラムを開く
(status ?: summary?.last_status)?.let {
conversation(pos, accessInfo, it)
}
}
// プレビューカードのイメージは返信かもしれない
fun ActMain.clickCardImage(pos: Int, accessInfo: SavedAccount, card: TootCard?, longClick: Boolean = false) {
card ?: return
card.originalStatus?.let {
if (longClick) {
conversationOtherInstance(pos, it)
} else {
conversation(pos, accessInfo, it)
}
return
}
card.url?.notEmpty()?.let {
openCustomTab(this, pos, it, accessInfo = accessInfo)
}
}
// 「~からの返信」の表記を押した
fun ActMain.clickReplyInfo(
pos: Int,
accessInfo: SavedAccount,
columnType: ColumnType,
statusReply: TootStatus?,
statusShowing: TootStatus?,
longClick: Boolean = false,
contextMenuOpener: ActMain.(status: TootStatus) -> Unit = {},
) {
when {
statusReply != null ->
if (longClick) {
contextMenuOpener(this, statusReply)
} else {
conversation(pos, accessInfo, statusReply)
}
// tootsearchは返信元のIDを取得するのにひと手間必要
columnType == ColumnType.SEARCH_TS ||
columnType == ColumnType.SEARCH_NOTESTOCK ->
conversationFromTootsearch(pos, statusShowing)
else ->
statusShowing?.in_reply_to_id
?.let { conversationLocal(pos, accessInfo, it) }
}
}
/////////////////////////////////////////////////////////////////////////////////////
// open conversation

View File

@ -1,9 +1,11 @@
package jp.juggler.subwaytooter.action
import jp.juggler.subwaytooter.ActKeywordFilter
import jp.juggler.subwaytooter.ActMain
import jp.juggler.subwaytooter.R
import jp.juggler.subwaytooter.api.entity.TootFilter
import jp.juggler.subwaytooter.api.runApiTask
import jp.juggler.subwaytooter.dialog.ActionsDialog
import jp.juggler.subwaytooter.dialog.DlgConfirm
import jp.juggler.subwaytooter.onFilterDeleted
import jp.juggler.subwaytooter.table.SavedAccount
@ -13,6 +15,19 @@ import okhttp3.Request
// private val log = LogCategory("Action_Filter")
fun ActMain.openFilterMenu(accessInfo: SavedAccount, item: TootFilter?) {
item ?: return
val ad = ActionsDialog()
ad.addAction(getString(R.string.edit)) {
ActKeywordFilter.open(this, accessInfo, item.id)
}
ad.addAction(getString(R.string.delete)) {
filterDelete(accessInfo, item)
}
ad.show(this, getString(R.string.filter_of, item.phrase))
}
fun ActMain.filterDelete(
accessInfo: SavedAccount,
filter: TootFilter,

View File

@ -11,41 +11,58 @@ import jp.juggler.subwaytooter.table.SavedAccount
import jp.juggler.subwaytooter.table.UserRelation
import jp.juggler.util.*
fun ActMain.clickFollowInfo(
pos: Int,
accessInfo: SavedAccount,
whoRef: TootAccountRef?,
forceMenu: Boolean = false,
contextMenuOpener: ActMain.(whoRef: TootAccountRef) -> Unit,
) {
whoRef ?: return
if (forceMenu || accessInfo.isPseudo) {
contextMenuOpener(this, whoRef)
} else {
userProfileLocal(pos, accessInfo, whoRef.get())
}
}
fun ActMain.clickFollow(
pos: Int,
accessInfo: SavedAccount,
who: TootAccount,
whoRef: TootAccountRef,
relation: UserRelation,
relation: UserRelation?,
) {
relation ?: return
val who = whoRef.get()
when {
accessInfo.isPseudo ->
followFromAnotherAccount(pos, accessInfo, who)
accessInfo.isMisskey &&
relation.getRequested(who) &&
!relation.getFollowing(who) ->
followRequestDelete(
pos, accessInfo, whoRef,
callback = cancelFollowRequestCompleteCallback
)
else -> {
val bSet = !(relation.getRequested(who) || relation.getFollowing(who))
follow(
pos,
accessInfo,
whoRef,
bFollow = bSet,
callback = when (bSet) {
true -> followCompleteCallback
else -> unfollowCompleteCallback
}
)
}
relation.blocking || relation.muting ->
Unit // 何もしない
accessInfo.isMisskey && relation.getRequested(who) && !relation.getFollowing(who) ->
followRequestDelete(pos, accessInfo, whoRef, callback = cancelFollowRequestCompleteCallback)
relation.getFollowing(who) || relation.getRequested(who) ->
follow(pos, accessInfo, whoRef, bFollow = false, callback = unfollowCompleteCallback)
else ->
follow(pos, accessInfo, whoRef, bFollow = true, callback = followCompleteCallback)
}
}
fun ActMain.clickFollowRequestAccept(accessInfo: SavedAccount, whoRef: TootAccountRef?, accept: Boolean) {
val who = whoRef?.get() ?: return
DlgConfirm.openSimple(
this,
getString(
if (accept) R.string.follow_accept_confirm else R.string.follow_deny_confirm,
AcctColor.getNickname(accessInfo, who)
)
) {
followRequestAuthorize(accessInfo, whoRef, accept)
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
fun ActMain.follow(
pos: Int,
accessInfo: SavedAccount,

View File

@ -1,19 +1,57 @@
package jp.juggler.subwaytooter.action
import android.app.Dialog
import jp.juggler.subwaytooter.ActMain
import jp.juggler.subwaytooter.R
import jp.juggler.subwaytooter.*
import jp.juggler.subwaytooter.api.*
import jp.juggler.subwaytooter.api.entity.MisskeyAntenna
import jp.juggler.subwaytooter.api.entity.TimelineItem
import jp.juggler.subwaytooter.api.entity.TootList
import jp.juggler.subwaytooter.api.entity.parseItem
import jp.juggler.subwaytooter.dialog.ActionsDialog
import jp.juggler.subwaytooter.dialog.DlgConfirm
import jp.juggler.subwaytooter.dialog.DlgTextInput
import jp.juggler.subwaytooter.onListListUpdated
import jp.juggler.subwaytooter.onListNameUpdated
import jp.juggler.subwaytooter.table.SavedAccount
import jp.juggler.util.*
import okhttp3.Request
fun ActMain.clickListTl(pos: Int, accessInfo: SavedAccount, item: TimelineItem?) {
when (item) {
is TootList -> addColumn(pos, accessInfo, ColumnType.LIST_TL, item.id)
is MisskeyAntenna -> addColumn(pos, accessInfo, ColumnType.MISSKEY_ANTENNA_TL, item.id)
}
}
fun ActMain.clickListMoreButton(pos: Int, accessInfo: SavedAccount, item: TimelineItem?) {
when (item) {
is TootList -> {
ActionsDialog()
.addAction(getString(R.string.list_timeline)) {
addColumn(pos, accessInfo, ColumnType.LIST_TL, item.id)
}
.addAction(getString(R.string.list_member)) {
addColumn(
false,
pos,
accessInfo,
ColumnType.LIST_MEMBER,
item.id
)
}
.addAction(getString(R.string.rename)) {
listRename(accessInfo, item)
}
.addAction(getString(R.string.delete)) {
listDelete(accessInfo, item)
}
.show(this, item.title)
}
is MisskeyAntenna -> {
// XXX
}
}
}
fun interface ListOnCreatedCallback {
fun onCreated(list: TootList)
}

View File

@ -12,10 +12,6 @@ import jp.juggler.util.*
import okhttp3.Request
import java.util.regex.Pattern
fun interface ListOnListMemberUpdatedCallback {
fun onListMemberUpdated(willRegistered: Boolean, bSuccess: Boolean)
}
private val reFollowError422 = "follow".asciiPattern(Pattern.CASE_INSENSITIVE)
private val reFollowError404 = "Record not found".asciiPattern(Pattern.CASE_INSENSITIVE)
@ -24,7 +20,7 @@ fun ActMain.listMemberAdd(
listId: EntityId,
localWho: TootAccount,
bFollow: Boolean = false,
callback: ListOnListMemberUpdatedCallback?,
onListMemberUpdated: (willRegistered: Boolean, bSuccess: Boolean) -> Unit = { _, _ -> },
) {
launchMain {
runApiTask(accessInfo) { client ->
@ -143,7 +139,7 @@ fun ActMain.listMemberAdd(
listId,
localWho,
bFollow = true,
callback = callback
onListMemberUpdated = onListMemberUpdated
)
}
} else {
@ -158,7 +154,7 @@ fun ActMain.listMemberAdd(
else -> showToast(true, error)
}
} finally {
callback?.onListMemberUpdated(true, bSuccess)
onListMemberUpdated(true, bSuccess)
}
}
}
@ -168,7 +164,7 @@ fun ActMain.listMemberDelete(
accessInfo: SavedAccount,
listId: EntityId,
localWho: TootAccount,
callback: ListOnListMemberUpdatedCallback?,
onListMemberDeleted: (willRegistered: Boolean, bSuccess: Boolean) -> Unit = { _, _ -> },
) {
launchMain {
runApiTask(accessInfo) { client ->
@ -208,7 +204,7 @@ fun ActMain.listMemberDelete(
}
}
} finally {
callback?.onListMemberUpdated(false, bSuccess)
onListMemberDeleted(false, bSuccess)
}
}
}

View File

@ -68,8 +68,8 @@ fun ActMain.openActPostImpl(
scheduledStatus: TootScheduled? = null,
) {
val useManyWindow = Pref.bpManyWindowPost(pref)
val useMultiWindow = useManyWindow || Pref.bpMultiWindowPost(pref)
val useManyWindow = PrefB.bpManyWindowPost(pref)
val useMultiWindow = useManyWindow || PrefB.bpMultiWindowPost(pref)
val intent = ActPost.createIntent(
activity = this,
@ -246,7 +246,7 @@ fun ActMain.quoteFromAnotherAccount(
fun ActMain.quoteName(who: TootAccount) {
var sv = who.display_name
try {
val fmt = Pref.spQuoteNameFormat(pref)
val fmt = PrefS.spQuoteNameFormat(pref)
if (fmt.contains("%1\$s")) {
sv = String.format(Locale.getDefault(), fmt, sv)
}
@ -263,3 +263,19 @@ fun ActMain.shareText(text: String?) {
.setType("text/plain")
.startChooser()
}
fun ActMain.clickReply(accessInfo: SavedAccount, status: TootStatus) {
if (!accessInfo.isPseudo) {
reply(accessInfo, status)
} else {
replyFromAnotherAccount(accessInfo, status)
}
}
fun ActMain.clickQuote(accessInfo: SavedAccount, status: TootStatus) {
if (!accessInfo.isPseudo) {
reply(accessInfo, status, quote = true)
} else {
quoteFromAnotherAccount(accessInfo, status)
}
}

View File

@ -523,3 +523,20 @@ fun ActMain.reactionFromAnotherAccount(
}
}
}
fun ActMain.clickReaction(accessInfo: SavedAccount, column: Column, status: TootStatus) {
val canMultipleReaction = InstanceCapability.canMultipleReaction(accessInfo)
val hasMyReaction = status.reactionSet?.hasMyReaction() == true
val bRemoveButton = hasMyReaction && !canMultipleReaction
when {
!TootReaction.canReaction(accessInfo) ->
reactionFromAnotherAccount(
accessInfo,
status
)
bRemoveButton ->
reactionRemove(column, status)
else ->
reactionAdd(column, status)
}
}

View File

@ -2,10 +2,7 @@ package jp.juggler.subwaytooter.action
import androidx.appcompat.app.AlertDialog
import jp.juggler.subwaytooter.*
import jp.juggler.subwaytooter.api.entity.Host
import jp.juggler.subwaytooter.api.entity.InstanceType
import jp.juggler.subwaytooter.api.entity.TootAccount
import jp.juggler.subwaytooter.api.entity.TootInstance
import jp.juggler.subwaytooter.api.entity.*
import jp.juggler.subwaytooter.api.runApiTask
import jp.juggler.subwaytooter.dialog.pickAccount
import jp.juggler.subwaytooter.table.SavedAccount
@ -111,6 +108,18 @@ fun ActMain.serverInformation(
host
)
// ドメインブロック一覧から解除
fun ActMain.clickDomainBlock(accessInfo: SavedAccount, item: TootDomainBlock) {
AlertDialog.Builder(this)
.setMessage(getString(R.string.confirm_unblock_domain, item.domain.pretty))
.setNegativeButton(R.string.cancel, null)
.setPositiveButton(R.string.ok) { _, _ ->
domainBlock(accessInfo, item.domain, bBlock = false)
}
.show()
}
// ContextMenuからドメインブロックを追加
fun ActMain.clickDomainBlock(
accessInfo: SavedAccount,
who: TootAccount,

View File

@ -5,69 +5,15 @@ import androidx.appcompat.app.AlertDialog
import jp.juggler.subwaytooter.*
import jp.juggler.subwaytooter.api.*
import jp.juggler.subwaytooter.api.entity.*
import jp.juggler.subwaytooter.dialog.ActionsDialog
import jp.juggler.subwaytooter.dialog.DlgConfirm
import jp.juggler.subwaytooter.dialog.pickAccount
import jp.juggler.subwaytooter.table.AcctColor
import jp.juggler.subwaytooter.table.SavedAccount
import jp.juggler.subwaytooter.util.emptyCallback
import jp.juggler.util.*
import okhttp3.Request
import java.util.*
import kotlin.math.max
fun ActMain.clickBoostWithVisibility(
accessInfo: SavedAccount,
status: TootStatus?,
) {
status ?: return
val list = if (accessInfo.isMisskey) {
arrayOf(
TootVisibility.Public,
TootVisibility.UnlistedHome,
TootVisibility.PrivateFollowers,
TootVisibility.LocalPublic,
TootVisibility.LocalHome,
TootVisibility.LocalFollowers,
TootVisibility.DirectSpecified,
TootVisibility.DirectPrivate
)
} else {
arrayOf(
TootVisibility.Public,
TootVisibility.UnlistedHome,
TootVisibility.PrivateFollowers
)
}
val captionList = list
.map { Styler.getVisibilityCaption(this, accessInfo.isMisskey, it) }
.toTypedArray()
AlertDialog.Builder(this)
.setTitle(R.string.choose_visibility)
.setItems(captionList) { _, which ->
if (which in list.indices) {
boost(
accessInfo,
status,
accessInfo.getFullAcct(status.account),
CrossAccountMode.SameAccount,
visibility = list[which],
callback = boostCompleteCallback,
)
}
}
.setNegativeButton(R.string.cancel, null)
.show()
}
fun ActMain.clickBoostBy(
pos: Int,
accessInfo: SavedAccount,
status: TootStatus?,
columnType: ColumnType = ColumnType.BOOSTED_BY,
) {
status ?: return
addColumn(false, pos, accessInfo, columnType, status.id)
}
fun ActMain.clickStatusDelete(
accessInfo: SavedAccount,
@ -81,6 +27,64 @@ fun ActMain.clickStatusDelete(
.show()
}
fun ActMain.clickBookmark(accessInfo: SavedAccount, status: TootStatus, willToast: Boolean) {
if (accessInfo.isPseudo) {
bookmarkFromAnotherAccount(accessInfo, status)
} else {
// トグル動作
val bSet = !status.bookmarked
bookmark(
accessInfo,
status,
CrossAccountMode.SameAccount,
bSet = bSet,
callback = when {
!willToast -> emptyCallback
// 簡略表示なら結果をトースト表示
bSet -> bookmarkCompleteCallback
else -> unbookmarkCompleteCallback
},
)
}
}
fun ActMain.clickFavourite(accessInfo: SavedAccount, status: TootStatus, willToast: Boolean) {
if (accessInfo.isPseudo) {
favouriteFromAnotherAccount(accessInfo, status)
} else {
// トグル動作
val bSet = !status.favourited
favourite(
accessInfo,
status,
CrossAccountMode.SameAccount,
bSet = bSet,
callback = when {
!willToast -> emptyCallback
// 簡略表示なら結果をトースト表示
bSet -> favouriteCompleteCallback
else -> unfavouriteCompleteCallback
},
)
}
}
fun ActMain.clickScheduledToot(accessInfo: SavedAccount, item: TootScheduled, column: Column) {
ActionsDialog()
.addAction(getString(R.string.edit)) {
scheduledPostEdit(accessInfo, item)
}
.addAction(getString(R.string.delete)) {
scheduledPostDelete(accessInfo, item) {
column.onScheduleDeleted(item)
showToast(false, R.string.scheduled_post_deleted)
}
}
.show(this)
}
fun ActMain.launchActText(intent: Intent) = arActText.launch(intent)
///////////////////////////////////////////////////////////////
@ -394,304 +398,6 @@ fun ActMain.bookmarkFromAnotherAccount(
///////////////////////////////////////////////////////////////
fun ActMain.boost(
accessInfo: SavedAccount,
statusArg: TootStatus,
statusOwner: Acct,
crossAccountMode: CrossAccountMode,
bSet: Boolean = true,
bConfirmed: Boolean = false,
visibility: TootVisibility? = null,
callback: () -> Unit,
) {
// アカウントからステータスにブースト操作を行っているなら、何もしない
if (appState.isBusyBoost(accessInfo, statusArg)) {
showToast(false, R.string.wait_previous_operation)
return
}
// Mastodonは非公開トゥートをブーストできるのは本人だけ
val isPrivateToot = accessInfo.isMastodon &&
statusArg.visibility == TootVisibility.PrivateFollowers
if (isPrivateToot && accessInfo.acct != statusOwner) {
showToast(false, R.string.boost_private_toot_not_allowed)
return
}
// DMとかのブーストはAPI側がエラーを出すだろう
// 必要なら確認を出す
if (!bConfirmed) {
DlgConfirm.open(
this,
getString(
when {
!bSet -> R.string.confirm_unboost_from
isPrivateToot -> R.string.confirm_boost_private_from
visibility == TootVisibility.PrivateFollowers -> R.string.confirm_private_boost_from
else -> R.string.confirm_boost_from
},
AcctColor.getNickname(accessInfo)
),
object : DlgConfirm.Callback {
override fun onOK() {
boost(
accessInfo,
statusArg,
statusOwner,
crossAccountMode,
bSet = bSet,
bConfirmed = true,
visibility = visibility,
callback = callback,
)
}
override var isConfirmEnabled: Boolean
get() = when (bSet) {
true -> accessInfo.confirm_boost
else -> accessInfo.confirm_unboost
}
set(value) {
when (bSet) {
true -> accessInfo.confirm_boost = value
else -> accessInfo.confirm_unboost = value
}
accessInfo.saveSetting()
reloadAccountSetting(accessInfo)
}
})
return
}
appState.setBusyBoost(accessInfo, statusArg)
// ブースト表示を更新中にする
showColumnMatchAccount(accessInfo)
// misskeyは非公開トゥートをブーストできないっぽい
launchMain {
var resultStatus: TootStatus? = null
var resultUnrenoteId: EntityId? = null
val result = runApiTask(accessInfo, progressStyle = ApiTask.PROGRESS_NONE) { client ->
val parser = TootParser(this, accessInfo)
val targetStatus = if (crossAccountMode.isRemote) {
val (result, status) = client.syncStatus(accessInfo, statusArg)
if (status == null) return@runApiTask result
if (status.reblogged) {
return@runApiTask TootApiResult(getString(R.string.already_boosted))
}
status
} else {
// 既に自タンスのステータスがある
statusArg
}
if (accessInfo.isMisskey) {
if (!bSet) {
val myRenoteId = targetStatus.myRenoteId
?: return@runApiTask TootApiResult("missing renote id.")
client.request(
"/api/notes/delete",
accessInfo.putMisskeyApiToken().apply {
put("noteId", myRenoteId.toString())
put("renoteId", targetStatus.id.toString())
}
.toPostRequestBuilder()
)
?.also {
if (it.response?.code == 204) {
resultUnrenoteId = myRenoteId
}
}
} else {
client.request(
"/api/notes/create",
accessInfo.putMisskeyApiToken().apply {
put("renoteId", targetStatus.id.toString())
}
.toPostRequestBuilder()
)
?.also { result ->
val jsonObject = result.jsonObject
if (jsonObject != null) {
val outerStatus = parser.status(
jsonObject.jsonObject("createdNote")
?: jsonObject
)
val innerStatus = outerStatus?.reblog ?: outerStatus
if (outerStatus != null && innerStatus != null && outerStatus != innerStatus) {
innerStatus.myRenoteId = outerStatus.id
innerStatus.reblogged = true
}
// renoteそのものではなくrenoteされた元noteが欲しい
resultStatus = innerStatus
}
}
}
} else {
val b = JsonObject().apply {
if (visibility != null) put("visibility", visibility.strMastodon)
}.toPostRequestBuilder()
client.request(
"/api/v1/statuses/${targetStatus.id}/${if (bSet) "reblog" else "unreblog"}",
b
)?.also { result ->
// reblogはreblogを表すStatusを返す
// unreblogはreblogしたStatusを返す
val s = parser.status(result.jsonObject)
resultStatus = s?.reblog ?: s
}
}
}
appState.resetBusyBoost(accessInfo, statusArg)
if (result != null) {
val unrenoteId = resultUnrenoteId
val newStatus = resultStatus
when {
// Misskeyでunrenoteに成功した
unrenoteId != null -> {
// 星を外したのにカウントが下がらないのは違和感あるので、表示をいじる
// 0未満にはならない
val count = max(0, (statusArg.reblogs_count ?: 1) - 1)
for (column in appState.columnList) {
column.findStatus(
accessInfo.apDomain,
statusArg.id
) { account, status ->
// 同タンス別アカウントでもカウントは変化する
status.reblogs_count = count
// 同アカウントならreblogged状態を変化させる
if (accessInfo == account && status.myRenoteId == unrenoteId) {
status.myRenoteId = null
status.reblogged = false
}
true
}
}
callback()
}
// 処理に成功した
newStatus != null -> {
// カウント数は遅延があるみたいなので、恣意的に表示を変更する
// ブーストカウント数を加工する
val oldCount = statusArg.reblogs_count
val newCount = newStatus.reblogs_count
if (oldCount != null && newCount != null) {
if (bSet && newStatus.reblogged && newCount <= oldCount) {
// 星をつけたのにカウントが上がらないのは違和感あるので、表示をいじる
newStatus.reblogs_count = oldCount + 1
} else if (!bSet && !newStatus.reblogged && newCount >= oldCount) {
// 星を外したのにカウントが下がらないのは違和感あるので、表示をいじる
// 0未満にはならない
newStatus.reblogs_count = if (oldCount < 1) 0 else oldCount - 1
}
}
for (column in appState.columnList) {
column.findStatus(
accessInfo.apDomain,
newStatus.id
) { account, status ->
// 同タンス別アカウントでもカウントは変化する
status.reblogs_count = newStatus.reblogs_count
if (accessInfo == account) {
// 同アカウントならreblog状態を変化させる
when {
accessInfo.isMastodon ->
status.reblogged = newStatus.reblogged
bSet && status.myRenoteId == null -> {
status.myRenoteId = newStatus.myRenoteId
status.reblogged = true
}
}
// Misskey のunrenote時はここを通らない
}
true
}
}
callback()
}
else -> showToast(true, result.error)
}
}
// 結果に関わらず、更新中状態から復帰させる
showColumnMatchAccount(accessInfo)
}
}
fun ActMain.boostFromAnotherAccount(
timelineAccount: SavedAccount,
status: TootStatus?,
) {
status ?: return
launchMain {
val statusOwner = timelineAccount.getFullAcct(status.account)
val isPrivateToot = timelineAccount.isMastodon &&
status.visibility == TootVisibility.PrivateFollowers
if (isPrivateToot) {
val list = ArrayList<SavedAccount>()
for (a in SavedAccount.loadAccountList(applicationContext)) {
if (a.acct == statusOwner) list.add(a)
}
if (list.isEmpty()) {
showToast(false, R.string.boost_private_toot_not_allowed)
return@launchMain
}
pickAccount(
bAllowPseudo = false,
bAuto = false,
message = getString(R.string.account_picker_boost),
accountListArg = list
)?.let { action_account ->
boost(
action_account,
status,
statusOwner,
calcCrossAccountMode(timelineAccount, action_account),
callback = boostCompleteCallback
)
}
} else {
pickAccount(
bAllowPseudo = false,
bAuto = false,
message = getString(R.string.account_picker_boost),
accountListArg = accountListNonPseudo(timelineAccount.apDomain)
)?.let { action_account ->
boost(
action_account,
status,
statusOwner,
calcCrossAccountMode(timelineAccount, action_account),
callback = boostCompleteCallback
)
}
}
}
}
fun ActMain.statusDelete(
accessInfo: SavedAccount,
statusId: EntityId,

View File

@ -5,6 +5,7 @@ import jp.juggler.subwaytooter.ColumnType
import jp.juggler.subwaytooter.R
import jp.juggler.subwaytooter.api.entity.Acct
import jp.juggler.subwaytooter.api.entity.Host
import jp.juggler.subwaytooter.api.entity.TootTag
import jp.juggler.subwaytooter.dialog.ActionsDialog
import jp.juggler.subwaytooter.table.AcctColor
import jp.juggler.subwaytooter.table.SavedAccount
@ -14,6 +15,15 @@ import jp.juggler.util.encodePercent
import jp.juggler.util.launchMain
import java.util.*
fun ActMain.longClickTootTag(pos: Int, accessInfo: SavedAccount, item: TootTag) {
tagTimelineFromAccount(
pos = pos,
url = "https://${accessInfo.apiHost.ascii}/tags/${item.name.encodePercent()}",
host = accessInfo.apiHost,
tagWithoutSharp = item.name
)
}
// ハッシュタグへの操作を選択する
fun ActMain.tagDialog(
pos: Int,

View File

@ -5,7 +5,7 @@ import android.text.Spannable
import android.text.SpannableStringBuilder
import android.widget.TextView
import jp.juggler.subwaytooter.App1
import jp.juggler.subwaytooter.Pref
import jp.juggler.subwaytooter.PrefB
import jp.juggler.subwaytooter.R
import jp.juggler.subwaytooter.api.MisskeyAccountDetailMap
import jp.juggler.subwaytooter.api.TootParser
@ -491,7 +491,7 @@ open class TootAccount(parser: TootParser, src: JsonObject) : HostAndDomain {
.append(suggestionSource)
}
if (Pref.bpDirectoryLastActive(pref) && last_status_at > 0L) {
if (PrefB.bpDirectoryLastActive(pref) && last_status_at > 0L) {
prepareSb()
.append(context.getString(R.string.last_active))
.append(delm)
@ -499,7 +499,7 @@ open class TootAccount(parser: TootParser, src: JsonObject) : HostAndDomain {
}
if (!fromProfileHeader) {
if (Pref.bpDirectoryTootCount(pref) &&
if (PrefB.bpDirectoryTootCount(pref) &&
(statuses_count ?: 0L) > 0L
) {
prepareSb()
@ -508,8 +508,8 @@ open class TootAccount(parser: TootParser, src: JsonObject) : HostAndDomain {
.append(statuses_count.toString())
}
if (Pref.bpDirectoryFollowers(pref) &&
!Pref.bpHideFollowCount(pref) &&
if (PrefB.bpDirectoryFollowers(pref) &&
!PrefB.bpHideFollowCount(pref) &&
(followers_count ?: 0L) > 0L
) {
prepareSb()
@ -518,7 +518,7 @@ open class TootAccount(parser: TootParser, src: JsonObject) : HostAndDomain {
.append(followers_count.toString())
}
if (Pref.bpDirectoryNote(pref) && note?.isNotEmpty() == true) {
if (PrefB.bpDirectoryNote(pref) && note?.isNotEmpty() == true) {
val decodedNote = DecodeOptions(
context,
accessInfo,

View File

@ -1,7 +1,7 @@
package jp.juggler.subwaytooter.api.entity
import android.content.SharedPreferences
import jp.juggler.subwaytooter.Pref
import jp.juggler.subwaytooter.PrefB
import jp.juggler.subwaytooter.api.TootParser
import jp.juggler.util.*
@ -195,7 +195,7 @@ class TootAttachment : TootAttachmentLike {
TootAttachmentType.values().find { it.id == src }
override fun urlForThumbnail(pref: SharedPreferences) =
if (Pref.bpPriorLocalURL(pref)) {
if (PrefB.bpPriorLocalURL(pref)) {
preview_url.notEmpty() ?: preview_remote_url.notEmpty()
} else {
preview_remote_url.notEmpty() ?: preview_url.notEmpty()
@ -205,7 +205,7 @@ class TootAttachment : TootAttachmentLike {
}
fun getLargeUrl(pref: SharedPreferences) =
if (Pref.bpPriorLocalURL(pref)) {
if (PrefB.bpPriorLocalURL(pref)) {
url.notEmpty() ?: remote_url
} else {
remote_url.notEmpty() ?: url
@ -213,7 +213,7 @@ class TootAttachment : TootAttachmentLike {
fun getLargeUrlList(pref: SharedPreferences) =
ArrayList<String>().apply {
if (Pref.bpPriorLocalURL(pref)) {
if (PrefB.bpPriorLocalURL(pref)) {
url.notEmpty()?.addTo(this)
remote_url.notEmpty()?.addTo(this)
} else {

View File

@ -2,7 +2,7 @@ package jp.juggler.subwaytooter.api.entity
import android.os.SystemClock
import jp.juggler.subwaytooter.App1
import jp.juggler.subwaytooter.Pref
import jp.juggler.subwaytooter.PrefB
import jp.juggler.subwaytooter.api.TootApiClient
import jp.juggler.subwaytooter.api.TootApiResult
import jp.juggler.subwaytooter.api.TootParser
@ -399,7 +399,7 @@ class TootInstance(parser: TootParser, src: JsonObject) {
when {
pair.first?.instanceType == InstanceType.Pixelfed &&
!Pref.bpEnablePixelfed(App1.pref) &&
!PrefB.bpEnablePixelfed(App1.pref) &&
!req.allowPixelfed ->
Pair(
null, TootApiResult("currently Pixelfed instance is not supported.")

View File

@ -4,7 +4,7 @@ import android.text.Spannable
import android.text.SpannableStringBuilder
import jp.juggler.subwaytooter.App1
import jp.juggler.subwaytooter.ColumnViewHolder
import jp.juggler.subwaytooter.Pref
import jp.juggler.subwaytooter.PrefB
import jp.juggler.subwaytooter.span.NetworkEmojiSpan
import jp.juggler.subwaytooter.table.SavedAccount
import jp.juggler.subwaytooter.util.DecodeOptions
@ -165,7 +165,7 @@ class TootReaction(
}
fun chooseUrl() = when {
Pref.bpDisableEmojiAnimation(App1.pref) -> staticUrl
PrefB.bpDisableEmojiAnimation(App1.pref) -> staticUrl
else -> url
}

View File

@ -6,7 +6,7 @@ import androidx.annotation.StringRes
import android.text.Spannable
import android.text.SpannableString
import jp.juggler.subwaytooter.App1
import jp.juggler.subwaytooter.Pref
import jp.juggler.subwaytooter.PrefB
import jp.juggler.subwaytooter.R
import jp.juggler.subwaytooter.api.*
import jp.juggler.subwaytooter.emoji.CustomEmoji
@ -498,7 +498,7 @@ class TootStatus(parser: TootParser, src: JsonObject) : TimelineItem() {
TootCard(parser, quote)
// content中のQTの表現が四角括弧の有無とか色々あるみたいだし
// 選択してコピーのことを考えたらむしろ削らない方が良い気がしてきた
// removeQt = ! Pref.bpDontShowPreviewCard(Pref.pref(parser.context))
// removeQt = ! PrefB.bpDontShowPreviewCard(Pref.pref(parser.context))
} else {
null
}
@ -712,7 +712,7 @@ class TootStatus(parser: TootParser, src: JsonObject) : TimelineItem() {
card = TootCard(parser, quote)
// content中のQTの表現が四角括弧の有無とか色々あるみたいだし
// 選択してコピーのことを考えたらむしろ削らない方が良い気がしてきた
// removeQt = ! Pref.bpDontShowPreviewCard(Pref.pref(parser.context))
// removeQt = ! PrefB.bpDontShowPreviewCard(Pref.pref(parser.context))
}
// content
@ -1050,7 +1050,7 @@ class TootStatus(parser: TootParser, src: JsonObject) : TimelineItem() {
fun markDeleted(context: Context, deletedAt: Long?): Boolean {
if (Pref.bpDontRemoveDeletedToot(App1.getAppState(context).pref)) return false
if (PrefB.bpDontRemoveDeletedToot(App1.getAppState(context).pref)) return false
var sv = if (deletedAt != null) {
context.getString(R.string.status_deleted_at, formatTime(context, deletedAt, false))
@ -1281,7 +1281,7 @@ class TootStatus(parser: TootParser, src: JsonObject) : TimelineItem() {
formatDate(t, date_format2, omitZeroSecond = false, omitYear = true)
}
if (bAllowRelative && Pref.bpRelativeTimestamp(App1.pref)) {
if (bAllowRelative && PrefB.bpRelativeTimestamp(App1.pref)) {
delta = abs(delta)

View File

@ -326,7 +326,7 @@ class DlgListMember(
}
@Suppress("ClassNaming")
internal inner class VH_List(view: View) : CompoundButton.OnCheckedChangeListener, ListOnListMemberUpdatedCallback {
internal inner class VH_List(view: View) : CompoundButton.OnCheckedChangeListener {
private val cbItem: CheckBox
private var bBusy: Boolean = false
@ -368,16 +368,16 @@ class DlgListMember(
// 状態をサーバに伝える
val item = this.item ?: return
if (isChecked) {
activity.listMemberAdd(listOwner, item.id, whoLocal, callback = this)
activity.listMemberAdd(listOwner, item.id, whoLocal) { willRegistered, bSuccess ->
if (!bSuccess) revokeCheckedChanged(willRegistered)
}
} else {
activity.listMemberDelete(listOwner, item.id, whoLocal, this)
activity.listMemberDelete(listOwner, item.id, whoLocal) { willRegistered, bSuccess ->
if (!bSuccess) revokeCheckedChanged(willRegistered)
}
}
}
override fun onListMemberUpdated(willRegistered: Boolean, bSuccess: Boolean) {
if (!bSuccess) revokeCheckedChanged(willRegistered)
}
private fun revokeCheckedChanged(willRegistered: Boolean) {
item?.isRegistered = !willRegistered
adapter.notifyDataSetChanged()

View File

@ -120,12 +120,12 @@ class DlgQuickTootMenu(
}
private fun loadStrings() =
Pref.spQuickTootMacro(activity.pref).split("\n")
PrefS.spQuickTootMacro(activity.pref).split("\n")
private fun saveStrings() = activity.pref
.edit()
.put(
Pref.spQuickTootMacro,
PrefS.spQuickTootMacro,
etText.joinToString("\n") {
(it?.text?.toString() ?: "").replace("\n", " ")
}

View File

@ -97,7 +97,7 @@ class EmojiPicker(
// recentをロードする
val pref = App1.pref
val sv = Pref.spEmojiPickerRecent(pref)
val sv = PrefS.spEmojiPickerRecent(pref)
if (sv.isNotEmpty()) {
try {
for (item in sv.decodeJsonArray().objectList()) {
@ -188,7 +188,7 @@ class EmojiPicker(
R.string.emoji_category_flags
)
)
if (Pref.bpEmojiPickerCategoryOther(activity)) {
if (PrefB.bpEmojiPickerCategoryOther(activity)) {
pageList.add(
EmojiPickerPage(
true,
@ -528,7 +528,7 @@ class EmojiPicker(
// Recentをロード(他インスタンスの絵文字を含む)
val list: MutableList<JsonObject> = try {
Pref.spEmojiPickerRecent(pref).decodeJsonArray().objectList()
PrefS.spEmojiPickerRecent(pref).decodeJsonArray().objectList()
} catch (_: Throwable) {
emptyList()
}.toMutableList()
@ -571,7 +571,7 @@ class EmojiPicker(
// 保存する
try {
val sv = list.toJsonArray().toString()
App1.pref.edit().put(Pref.spEmojiPickerRecent, sv).apply()
App1.pref.edit().put(PrefS.spEmojiPickerRecent, sv).apply()
} catch (ignored: Throwable) {
}

View File

@ -2,7 +2,7 @@ package jp.juggler.subwaytooter.emoji
import androidx.annotation.DrawableRes
import jp.juggler.subwaytooter.App1
import jp.juggler.subwaytooter.Pref
import jp.juggler.subwaytooter.PrefB
import jp.juggler.subwaytooter.api.entity.Host
import jp.juggler.subwaytooter.api.entity.Mappable
import jp.juggler.util.JsonArray
@ -74,7 +74,7 @@ class CustomEmoji(
get() = shortcode
fun chooseUrl() = when {
Pref.bpDisableEmojiAnimation(App1.pref) -> staticUrl
PrefB.bpDisableEmojiAnimation(App1.pref) -> staticUrl
else -> url
}

View File

@ -170,7 +170,7 @@ class PollingWorker private constructor(contextArg: Context) {
val intervalMillis = max(
minute * 5L,
minute * Pref.spPullNotificationCheckInterval.toInt(context.pref())
minute * PrefS.spPullNotificationCheckInterval.toInt(context.pref())
)
val flexMillis = max(

View File

@ -12,13 +12,13 @@ import jp.juggler.subwaytooter.table.SavedAccount
import jp.juggler.subwaytooter.table.SubscriptionServerKey
import jp.juggler.util.*
import okhttp3.Request
import okhttp3.Response
class PushSubscriptionHelper(
val context: Context,
val account: SavedAccount,
val verbose: Boolean = false,
) {
companion object {
private val log = LogCategory("PushSubscriptionHelper")
@ -36,6 +36,12 @@ class PushSubscriptionHelper(
private fun Boolean.booleanToInt(trueValue: Int, falseValue: Int = 0) =
if (this) trueValue else falseValue
private fun Response.closeQuietly() =
try {
close()
} catch (_: Throwable) {
}
}
val flags = account.notification_boost.booleanToInt(1) +
@ -153,6 +159,46 @@ class PushSubscriptionHelper(
}
}
suspend fun updateSubscription(client: TootApiClient, force: Boolean = false): TootApiResult? = try {
when {
isRecentlyChecked() ->
TootApiResult(ERROR_PREVENT_FREQUENTLY_CHECK)
account.isPseudo ->
TootApiResult(context.getString(R.string.pseudo_account_not_supported))
account.isMisskey ->
updateSubscriptionMisskey(client)
else ->
updateSubscriptionMastodon(client, force)
}
} catch (ex: Throwable) {
TootApiResult(ex.withCaption("error."))
}?.apply {
if (error != null) addLog("$error $requestInfo".trimEnd())
// update error text on account table
val log = logString
when {
log.contains(ERROR_PREVENT_FREQUENTLY_CHECK) -> {
// don't update if check was skipped.
}
subscribed || log.isEmpty() ->
// clear error text if succeeded or no error log
if (account.last_subscription_error != null) {
account.updateSubscriptionError(null)
}
else ->
// record error text
account.updateSubscriptionError(log)
}
}
private suspend fun updateSubscriptionMisskey(client: TootApiClient): TootApiResult? {
// 現在の購読状態を取得できないので、毎回購読の更新を行う
@ -231,88 +277,33 @@ class PushSubscriptionHelper(
// https://github.com/tootsuite/mastodon/pull/7471
// https://github.com/tootsuite/mastodon/pull/7472
var r = client.request("/api/v1/push/subscription")
var res = r?.response ?: return r // cancelled or missing response
var subscription404 = false
if (res.code != 200) log.i("${account.acct}: check existing subscription: code=${res.code}")
when (res.code) {
200 -> {
if (r.error?.isNotEmpty() == true && r.jsonObject == null) {
// Pleromaが200応答でもエラーHTMLを返す場合がある
addLog(context.getString(R.string.instance_does_not_support_push_api_pleroma))
return r
}
// たぶん購読が存在する
}
404 -> {
subscription404 = true
// この時点では存在しないのが購読なのかAPIなのか分からない
}
403 -> {
// アクセストークンにpushスコープがない
if (flags != 0 || verbose) {
addLog(context.getString(R.string.missing_push_scope))
}
return r
}
in 400 until 500 -> {
addLog(context.getString(R.string.instance_does_not_support_push_api_pleroma))
return r
}
else -> {
addLog("${res.request}")
addLog("${res.code} ${res.message}")
}
val subscription404: Boolean
val oldSubscription: TootPushSubscription?
checkCurrentSubscription(client).let {
if (it.failed) return it.result
subscription404 = it.is404
oldSubscription = parseItem(::TootPushSubscription, it.result?.jsonObject)
}
val oldSubscription = parseItem(::TootPushSubscription, r.jsonObject)
if (oldSubscription == null) {
log.i("${account.acct}: oldSubscription is null")
val (ti, result) = TootInstance.get(client)
ti ?: return result
// 2.4.0rc1 未満にはプッシュ購読APIはない
if (!ti.versionGE(TootInstance.VERSION_2_4_0_rc1)) {
return TootApiResult(
context.getString(R.string.instance_does_not_support_push_api, ti.version)
)
}
if (subscription404 && flags == 0) {
when {
ti.versionGE(TootInstance.VERSION_2_4_0_rc2) -> {
// 購読が不要で現在の状況が404だった場合
// 2.4.0rc2以降では「購読が存在しない」を示すので何もしなくてよい
if (verbose) addLog(context.getString(R.string.push_subscription_not_exists))
return TootApiResult()
}
else -> {
// 2.4.0rc1では「APIが存在しない」と「購読が存在しない」を判別できない
}
}
}
checkInstanceVersionMastodon(ti, subscription404)?.let { return it }
}
// FCMのデバイスIDを取得
val deviceId = PollingWorker.getFirebaseMessagingToken(context)
?: return TootApiResult(error = context.getString(R.string.missing_fcm_device_id))
// アクセストークン
val accessToken = account.getAccessToken()
?: return TootApiResult(error = "missing access token.")
// インストールIDを取得
val installId = PollingWorker.prepareInstallId(context)
?: return TootApiResult(error = context.getString(R.string.missing_install_id))
// アクセストークン
val accessToken = account.getAccessToken()
?: return TootApiResult(error = "missing access token.")
// アクセストークンのダイジェスト
val tokenDigest = accessToken.digestSHA256Base64Url()
@ -333,86 +324,50 @@ class PushSubscriptionHelper(
put("emoji_reaction", account.notification_reaction) // fedibird拡張
}
suspend fun canSkipSubscription(): TootApiResult? {
// 購読の更新が強制されている
if (force) return null
// 購読を解除したいのに古い購読があるのなら、購読の更新が必要
if (flags == 0 && oldSubscription != null) return null
// endpoint URLが合わないなら購読の更新が必要
if (force || oldSubscription?.endpoint != endpoint) return null
suspend fun makeSkipResult(): TootApiResult {
// 既に登録済みで、endpointも一致している
subscribed = true
if (verbose) addLog(context.getString(R.string.push_subscription_already_exists))
return updateServerKey(client, clientIdentifier, oldSubscription.server_key)
}
// STがstatus通知に対応した時期に古いサーバでここを通ると
// flagsの値が変わりendpoint URLも変わった状態で購読を自動更新してしまう
// しかしそのタイミングではサーバは古いのでサーバ側の購読内容は変化しなかった。
// サーバ上の購読アラートのリスト
var alertsOld = oldSubscription.alerts.entries
.mapNotNull { if (it.value) it.key else null }
.sorted()
// 期待する購読アラートのリスト
var alertsNew = newAlerts.entries
.mapNotNull { pair -> pair.key.takeIf { pair.value == true } }
.sorted()
// 両方に共通するアラートは除去する
val bothHave = alertsOld.filter { alertsNew.contains(it) }
alertsOld = alertsOld.filter { !bothHave.contains(it) }
alertsNew = alertsNew.filter { !bothHave.contains(it) }
// サーバのバージョンを調べる前に、この時点でalertsが一致するか確認する
if (alertsOld.joinToString(",") == alertsNew.joinToString(",")) {
log.i("${account.acct}: same alerts(1)")
return makeSkipResult()
}
// ここでサーバのバージョンによって対応が変わる
val (ti, result) = TootInstance.get(client)
ti ?: return result
// サーバが知らないアラート種別は比較対象から除去する
fun Iterable<String>.knownOnly() = filter {
when (it) {
"follow", "mention", "favourite", "reblog" -> true
"poll" -> ti.versionGE(TootInstance.VERSION_2_8_0_rc1)
"follow_request" -> ti.versionGE(TootInstance.VERSION_3_1_0_rc1)
"status" -> ti.versionGE(TootInstance.VERSION_3_3_0_rc1)
"emoji_reaction" -> ti.versionGE(TootInstance.VERSION_3_4_0_rc1) &&
InstanceCapability.emojiReaction(account, ti)
else -> {
log.w("${account.acct}: unknown alert '$it'. server version='${ti.version}'")
false // 未知のアラートの差異は比較しない
}
}
}
alertsOld = alertsOld.knownOnly()
alertsNew = alertsNew.knownOnly()
return if (alertsOld.joinToString(",") == alertsNew.joinToString(",")) {
log.i("${account.acct}: same alerts(2)")
makeSkipResult()
} else {
addLog("${account.acct}: alerts not match. account=${account.acct.pretty} old=${alertsOld.sorted()}, new=${alertsNew.sorted()}")
null
}
if (!force) {
canSkipSubscriptionMastodon(
client = client,
clientIdentifier = clientIdentifier,
endpoint = endpoint,
oldSubscription = oldSubscription,
newAlerts = newAlerts,
)?.let { return it }
}
val skipResult = canSkipSubscription()
if (skipResult != null) return skipResult
// アクセストークンの優先権を取得
r = client.http(
checkDeviceHasPriority(
client,
tokenDigest = tokenDigest,
installId = installId,
).let {
if (it.failed) return it.result
}
return when (flags) {
// 通知設定が全てカラなので、購読を取り消したい
0 -> unsubscribeMastodon(client)
// 通知設定が空ではないので購読を行いたい
else -> subscribeMastodon(
client = client,
clientIdentifier = clientIdentifier,
endpoint = endpoint,
newAlerts = newAlerts
)
}
}
private class CheckDeviceHasPriorityResult(
val result: TootApiResult?,
val failed: Boolean,
)
private suspend fun checkDeviceHasPriority(
client: TootApiClient,
tokenDigest: String,
installId: String,
): CheckDeviceHasPriorityResult {
val r = client.http(
jsonObject {
put("token_digest", tokenDigest)
put("install_id", installId)
@ -421,151 +376,275 @@ class PushSubscriptionHelper(
.url("${PollingWorker.APP_SERVER}/webpushtokencheck")
.build()
)
res = r.response ?: return r
if (res.code == 200) {
try {
res.close()
} catch (_: Throwable) {
fun rvError() = CheckDeviceHasPriorityResult(r, true)
fun rvOk() = CheckDeviceHasPriorityResult(r, false)
val res = r.response ?: return rvError()
return when {
res.code != 200 -> {
if (res.code == 403) addLog(context.getString(R.string.token_exported))
r.caption = "(SubwayTooter App server)"
client.readBodyString(r)
rvError()
}
} else {
if (res.code == 403) addLog(context.getString(R.string.token_exported))
r.caption = "(SubwayTooter App server)"
client.readBodyString(r)
return r
}
return if (flags == 0) {
// 通知設定が全てカラなので、購読を取り消したい
r = client.request("/api/v1/push/subscription", Request.Builder().delete())
res = r?.response ?: return r
when (res.code) {
200 -> {
if (verbose) addLog(context.getString(R.string.push_subscription_deleted))
TootApiResult()
}
404 -> {
if (verbose) {
addLog(context.getString(R.string.missing_push_api))
r
} else {
TootApiResult()
}
}
403 -> {
addLog(context.getString(R.string.missing_push_scope))
r
}
else -> {
addLog("${res.request}")
addLog("${res.code} ${res.message}")
r
}
}
} else {
// 通知設定が空ではないので購読を行いたい
@Suppress("SpellCheckingInspection")
val params = JsonObject().apply {
put("subscription", JsonObject().apply {
put("endpoint", endpoint)
put("keys", JsonObject().apply {
put(
"p256dh",
"BBEUVi7Ehdzzpe_ZvlzzkQnhujNJuBKH1R0xYg7XdAKNFKQG9Gpm0TSGRGSuaU7LUFKX-uz8YW0hAshifDCkPuE"
)
put("auth", "iRdmDrOS6eK6xvG1H6KshQ")
})
})
put("data", JsonObject().apply {
put("alerts", newAlerts)
account.push_policy?.let { put("policy", it) }
})
}
r = client.request(
"/api/v1/push/subscription",
params.toPostRequestBuilder()
) ?: return null
res = r.response ?: return r
when (res.code) {
404 -> {
addLog(context.getString(R.string.missing_push_api))
r
}
403 -> {
addLog(context.getString(R.string.missing_push_scope))
r
}
200 -> {
val newSubscription = parseItem(::TootPushSubscription, r.jsonObject)
?: return r.setError("parse error.")
subscribed = true
if (verbose) addLog(context.getString(R.string.push_subscription_updated))
return updateServerKey(
client,
clientIdentifier,
newSubscription.server_key
)
}
else -> {
addLog(r.jsonObject?.toString())
r
}
else -> {
res.closeQuietly()
rvOk()
}
}
}
suspend fun updateSubscription(client: TootApiClient, force: Boolean = false): TootApiResult? =
try {
// returns null if no error
private fun checkInstanceVersionMastodon(ti: TootInstance, subscription404: Boolean): TootApiResult? {
// 2.4.0rc1 未満にはプッシュ購読APIはない
if (!ti.versionGE(TootInstance.VERSION_2_4_0_rc1)) {
return TootApiResult(
context.getString(R.string.instance_does_not_support_push_api, ti.version)
)
}
if (subscription404 && flags == 0) {
when {
isRecentlyChecked() ->
TootApiResult(ERROR_PREVENT_FREQUENTLY_CHECK)
account.isPseudo ->
TootApiResult(context.getString(R.string.pseudo_account_not_supported))
account.isMisskey ->
updateSubscriptionMisskey(client)
else ->
updateSubscriptionMastodon(client, force)
}
} catch (ex: Throwable) {
TootApiResult(ex.withCaption("error."))
}?.apply {
if (error != null) addLog("$error $requestInfo".trimEnd())
// update error text on account table
val log = logString
when {
log.contains(ERROR_PREVENT_FREQUENTLY_CHECK) -> {
// don't update if check was skipped.
ti.versionGE(TootInstance.VERSION_2_4_0_rc2) -> {
// 購読が不要で現在の状況が404だった場合
// 2.4.0rc2以降では「購読が存在しない」を示すので何もしなくてよい
if (verbose) addLog(context.getString(R.string.push_subscription_not_exists))
return TootApiResult()
}
subscribed || log.isEmpty() ->
// clear error text if succeeded or no error log
if (account.last_subscription_error != null) {
account.updateSubscriptionError(null)
}
else ->
// record error text
account.updateSubscriptionError(log)
else -> {
// 2.4.0rc1では「APIが存在しない」と「購読が存在しない」を判別できない
}
}
}
return null
}
private class CheckCurrentSubscriptionResult(
val result: TootApiResult?,
val failed: Boolean,
val is404: Boolean,
)
@Suppress("BooleanLiteralArgument")
private suspend fun checkCurrentSubscription(client: TootApiClient): CheckCurrentSubscriptionResult {
val r = client.request("/api/v1/push/subscription")
fun rvError() = CheckCurrentSubscriptionResult(r, true, false)
fun rvOk() = CheckCurrentSubscriptionResult(r, false, false)
fun rv404() = CheckCurrentSubscriptionResult(r, false, true)
val res = r?.response ?: return rvError() // cancelled or missing response
if (res.code != 200) log.i("${account.acct}: check existing subscription: code=${res.code}")
return when (res.code) {
200 -> {
if (r.error?.isNotEmpty() == true && r.jsonObject == null) {
// Pleromaが200応答でもエラーHTMLを返す場合がある
addLog(context.getString(R.string.instance_does_not_support_push_api_pleroma))
rvError()
} else {
// たぶん購読が存在する
rvOk()
}
}
// この時点では存在しないのが購読なのかAPIなのか分からない
404 -> rv404()
403 -> {
// アクセストークンにpushスコープがない
if (flags != 0 || verbose) addLog(context.getString(R.string.missing_push_scope))
rvError()
}
in 400 until 500 -> {
addLog(context.getString(R.string.instance_does_not_support_push_api_pleroma))
rvError()
}
else -> {
addLog("${res.request}")
addLog("${res.code} ${res.message}")
rvOk() // 後でリトライする
}
}
}
suspend fun canSkipSubscriptionMastodon(
client: TootApiClient,
clientIdentifier: String,
endpoint: String,
oldSubscription: TootPushSubscription?,
newAlerts: JsonObject,
): TootApiResult? {
// 購読を解除したいのに古い購読があるのなら、購読の更新が必要
if (flags == 0 && oldSubscription != null) return null
// endpoint URLが合わないなら購読の更新が必要
if (oldSubscription?.endpoint != endpoint) return null
suspend fun makeSkipResult(): TootApiResult {
// 既に登録済みで、endpointも一致している
subscribed = true
if (verbose) addLog(context.getString(R.string.push_subscription_already_exists))
return updateServerKey(client, clientIdentifier, oldSubscription.server_key)
}
// STがstatus通知に対応した時期に古いサーバでここを通ると
// flagsの値が変わりendpoint URLも変わった状態で購読を自動更新してしまう
// しかしそのタイミングではサーバは古いのでサーバ側の購読内容は変化しなかった。
// サーバ上の購読アラートのリスト
var alertsOld = oldSubscription.alerts.entries
.mapNotNull { if (it.value) it.key else null }
.sorted()
// 期待する購読アラートのリスト
var alertsNew = newAlerts.entries
.mapNotNull { pair -> pair.key.takeIf { pair.value == true } }
.sorted()
// 両方に共通するアラートは除去する
val bothHave = alertsOld.filter { alertsNew.contains(it) }
alertsOld = alertsOld.filter { !bothHave.contains(it) }
alertsNew = alertsNew.filter { !bothHave.contains(it) }
// サーバのバージョンを調べる前に、この時点でalertsが一致するか確認する
if (alertsOld.joinToString(",") == alertsNew.joinToString(",")) {
log.i("${account.acct}: same alerts(1)")
return makeSkipResult()
}
// ここでサーバのバージョンによって対応が変わる
val (ti, result) = TootInstance.get(client)
ti ?: return result
// サーバが知らないアラート種別は比較対象から除去する
fun Iterable<String>.knownOnly() = filter {
when (it) {
"follow", "mention", "favourite", "reblog" -> true
"poll" -> ti.versionGE(TootInstance.VERSION_2_8_0_rc1)
"follow_request" -> ti.versionGE(TootInstance.VERSION_3_1_0_rc1)
"status" -> ti.versionGE(TootInstance.VERSION_3_3_0_rc1)
"emoji_reaction" -> ti.versionGE(TootInstance.VERSION_3_4_0_rc1) &&
InstanceCapability.emojiReaction(account, ti)
else -> {
log.w("${account.acct}: unknown alert '$it'. server version='${ti.version}'")
false // 未知のアラートの差異は比較しない
}
}
}
alertsOld = alertsOld.knownOnly()
alertsNew = alertsNew.knownOnly()
return if (alertsOld.joinToString(",") == alertsNew.joinToString(",")) {
log.i("${account.acct}: same alerts(2)")
makeSkipResult()
} else {
addLog("${account.acct}: alerts not match. account=${account.acct.pretty} old=${alertsOld.sorted()}, new=${alertsNew.sorted()}")
null
}
}
private suspend fun unsubscribeMastodon(
client: TootApiClient,
): TootApiResult? {
val r = client.request("/api/v1/push/subscription", Request.Builder().delete())
val res = r?.response ?: return r
return when (res.code) {
200 -> {
if (verbose) addLog(context.getString(R.string.push_subscription_deleted))
TootApiResult()
}
404 -> {
if (verbose) {
addLog(context.getString(R.string.missing_push_api))
r
} else {
TootApiResult()
}
}
403 -> {
addLog(context.getString(R.string.missing_push_scope))
r
}
else -> {
addLog("${res.request}")
addLog("${res.code} ${res.message}")
r
}
}
}
private suspend fun subscribeMastodon(
client: TootApiClient,
clientIdentifier: String,
endpoint: String,
newAlerts: JsonObject,
): TootApiResult? {
@Suppress("SpellCheckingInspection")
val params = JsonObject().apply {
put("subscription", JsonObject().apply {
put("endpoint", endpoint)
put("keys", JsonObject().apply {
put(
"p256dh",
"BBEUVi7Ehdzzpe_ZvlzzkQnhujNJuBKH1R0xYg7XdAKNFKQG9Gpm0TSGRGSuaU7LUFKX-uz8YW0hAshifDCkPuE"
)
put("auth", "iRdmDrOS6eK6xvG1H6KshQ")
})
})
put("data", JsonObject().apply {
put("alerts", newAlerts)
account.push_policy?.let { put("policy", it) }
})
}
val r = client.request(
"/api/v1/push/subscription",
params.toPostRequestBuilder()
) ?: return null
val res = r.response ?: return r
return when (res.code) {
404 -> {
addLog(context.getString(R.string.missing_push_api))
r
}
403 -> {
addLog(context.getString(R.string.missing_push_scope))
r
}
200 -> {
val newSubscription = parseItem(::TootPushSubscription, r.jsonObject)
?: return r.setError("parse error.")
subscribed = true
if (verbose) addLog(context.getString(R.string.push_subscription_updated))
return updateServerKey(
client,
clientIdentifier,
newSubscription.server_key
)
}
else -> {
addLog(r.jsonObject?.toString())
r
}
}
}
}

View File

@ -9,7 +9,7 @@ import androidx.core.app.NotificationCompat
import androidx.core.content.ContextCompat
import jp.juggler.subwaytooter.ActCallback
import jp.juggler.subwaytooter.EventReceiver
import jp.juggler.subwaytooter.Pref
import jp.juggler.subwaytooter.PrefB
import jp.juggler.subwaytooter.R
import jp.juggler.subwaytooter.api.TootApiCallback
import jp.juggler.subwaytooter.api.TootApiClient
@ -339,7 +339,7 @@ class TaskRunner(
this.parser = TootParser(context, account)
if (Pref.bpSeparateReplyNotificationGroup(pref)) {
if (PrefB.bpSeparateReplyNotificationGroup(pref)) {
var tr = TrackingRunner(
trackingType = TrackingType.NotReply,
trackingName = NotificationHelper.TRACKING_NAME_DEFAULT
@ -474,7 +474,7 @@ class TaskRunner(
val first = dataList.firstOrNull()
if (first == null) {
log.d("showNotification[${account.acct.pretty}/$notificationTag] cancel notification.")
if (Build.VERSION.SDK_INT >= 23 && Pref.bpDivideNotification(pref)) {
if (Build.VERSION.SDK_INT >= 23 && PrefB.bpDivideNotification(pref)) {
notificationManager.activeNotifications?.forEach {
if (it != null &&
it.id == PollingWorker.NOTIFICATION_ID &&
@ -501,7 +501,7 @@ class TaskRunner(
return
}
if (Build.VERSION.SDK_INT >= 23 && Pref.bpDivideNotification(pref)) {
if (Build.VERSION.SDK_INT >= 23 && PrefB.bpDivideNotification(pref)) {
val activeNotificationMap = HashMap<String, StatusBarNotification>().apply {
notificationManager.activeNotifications?.forEach {
if (it != null &&
@ -554,7 +554,7 @@ class TaskRunner(
if (Build.VERSION.SDK_INT < 26) {
var iv = 0
if (Pref.bpNotificationSound(pref)) {
if (PrefB.bpNotificationSound(pref)) {
var soundUri: Uri? = null
@ -583,11 +583,11 @@ class TaskRunner(
}
}
if (Pref.bpNotificationVibration(pref)) {
if (PrefB.bpNotificationVibration(pref)) {
iv = iv or NotificationCompat.DEFAULT_VIBRATE
}
if (Pref.bpNotificationLED(pref)) {
if (PrefB.bpNotificationLED(pref)) {
iv = iv or NotificationCompat.DEFAULT_LIGHTS
}
@ -629,7 +629,7 @@ class TaskRunner(
var iv = 0
if (Pref.bpNotificationSound(pref)) {
if (PrefB.bpNotificationSound(pref)) {
var soundUri: Uri? = null
@ -659,11 +659,11 @@ class TaskRunner(
}
}
if (Pref.bpNotificationVibration(pref)) {
if (PrefB.bpNotificationVibration(pref)) {
iv = iv or NotificationCompat.DEFAULT_VIBRATE
}
if (Pref.bpNotificationLED(pref)) {
if (PrefB.bpNotificationLED(pref)) {
iv = iv or NotificationCompat.DEFAULT_LIGHTS
}
@ -829,7 +829,7 @@ class TaskRunner(
private fun NotificationData.getNotificationLine(): String {
val name = when (Pref.bpShowAcctInSystemNotification(pref)) {
val name = when (PrefB.bpShowAcctInSystemNotification(pref)) {
false -> notification.accountRef?.decoded_display_name
true -> {

View File

@ -23,7 +23,7 @@ object MspHelper {
private suspend fun TootApiClient.search(query: String, maxId: String?): TootApiResult? {
// ユーザトークンを読む
var user_token: String? = Pref.spMspUserToken(pref)
var user_token: String? = PrefS.spMspUserToken(pref)
for (nTry in 0 until 3) {
if (callback.isApiCancelled) return null
@ -56,7 +56,7 @@ object MspHelper {
if (user_token?.isEmpty() != false) {
return result.setError("Can't get MSP user token. response=${result.bodyString}")
} else {
pref.edit().put(Pref.spMspUserToken, user_token).apply()
pref.edit().put(PrefS.spMspUserToken, user_token).apply()
}
}

View File

@ -8,7 +8,7 @@ import android.text.style.ReplacementSpan
import androidx.annotation.IntRange
import jp.juggler.apng.ApngFrames
import jp.juggler.subwaytooter.App1
import jp.juggler.subwaytooter.Pref
import jp.juggler.subwaytooter.PrefB
import jp.juggler.util.LogCategory
import java.lang.ref.WeakReference
@ -89,7 +89,7 @@ class NetworkEmojiSpan internal constructor(
} ?: return
val t = when {
Pref.bpDisableEmojiAnimation(App1.pref) -> 0L
PrefB.bpDisableEmojiAnimation(App1.pref) -> 0L
else -> invalidateCallback.timeFromStart
}
@ -152,7 +152,7 @@ class NetworkEmojiSpan internal constructor(
// 少し後に描画しなおす
val delay = mFrameFindResult.delay
if (delay != Long.MAX_VALUE && !Pref.bpDisableEmojiAnimation(App1.pref)) {
if (delay != Long.MAX_VALUE && !PrefB.bpDisableEmojiAnimation(App1.pref)) {
invalidateCallback.delayInvalidate(delay)
}
}

View File

@ -1,7 +1,7 @@
package jp.juggler.subwaytooter.streaming
import android.os.SystemClock
import jp.juggler.subwaytooter.Pref
import jp.juggler.subwaytooter.PrefB
import jp.juggler.subwaytooter.api.TootApiCallback
import jp.juggler.subwaytooter.api.TootApiClient
import jp.juggler.subwaytooter.api.TootApiResult
@ -139,7 +139,7 @@ class StreamConnection(
}
private fun fireDeleteId(id: EntityId) {
if (Pref.bpDontRemoveDeletedToot.invoke(manager.appState.pref)) return
if (PrefB.bpDontRemoveDeletedToot.invoke(manager.appState.pref)) return
val timelineHost = acctGroup.account.apiHost
manager.appState.columnList.forEach {
runOnMainLooper {

View File

@ -2,7 +2,7 @@ package jp.juggler.subwaytooter.streaming
import jp.juggler.subwaytooter.AppState
import jp.juggler.subwaytooter.Column
import jp.juggler.subwaytooter.Pref
import jp.juggler.subwaytooter.PrefB
import jp.juggler.subwaytooter.api.TootApiCallback
import jp.juggler.subwaytooter.api.TootApiClient
import jp.juggler.subwaytooter.api.entity.Acct
@ -69,7 +69,7 @@ class StreamManager(val appState: AppState) {
return acctGroup
}
if (isScreenOn && !Pref.bpDontUseStreaming(appState.pref)) {
if (isScreenOn && !PrefB.bpDontUseStreaming(appState.pref)) {
for (column in appState.columnList) {
val accessInfo = column.accessInfo
if (column.isDispose.get() || column.dontStreaming || accessInfo.isNA) continue

View File

@ -11,15 +11,12 @@ import android.os.Build
import android.os.Bundle
import androidx.browser.customtabs.CustomTabColorSchemeParams
import androidx.browser.customtabs.CustomTabsIntent
import jp.juggler.subwaytooter.ActMain
import jp.juggler.subwaytooter.Pref
import jp.juggler.subwaytooter.R
import jp.juggler.subwaytooter.*
import jp.juggler.subwaytooter.action.*
import jp.juggler.subwaytooter.api.entity.*
import jp.juggler.subwaytooter.api.entity.TootStatus.Companion.findStatusIdFromUrl
import jp.juggler.subwaytooter.api.entity.TootTag.Companion.findHashtagFromUrl
import jp.juggler.subwaytooter.dialog.DlgAppPicker
import jp.juggler.subwaytooter.pref
import jp.juggler.subwaytooter.span.LinkInfo
import jp.juggler.subwaytooter.table.SavedAccount
import jp.juggler.util.*
@ -42,7 +39,7 @@ private fun Activity.openBrowserExcludeMe(
): Boolean {
try {
if (intent.component == null) {
val cn = Pref.spWebBrowser(pref).cn()
val cn = PrefS.spWebBrowser(pref).cn()
if (cn?.exists(this) == true) {
intent.component = cn
}
@ -134,7 +131,7 @@ fun Activity.openCustomTab(url: String?, pref: SharedPreferences = pref()) {
return
}
if (Pref.bpDontUseCustomTabs(pref)) {
if (PrefB.bpDontUseCustomTabs(pref)) {
openBrowser(url, pref)
return
}
@ -161,7 +158,7 @@ fun Activity.openCustomTab(url: String?, pref: SharedPreferences = pref()) {
)
}
if (url.startsWith("http") && Pref.bpPriorChrome(pref)) {
if (url.startsWith("http") && PrefB.bpPriorChrome(pref)) {
try {
// 初回はChrome指定で試す
val cn = ComponentName(

View File

@ -7,7 +7,7 @@ import android.text.style.ForegroundColorSpan
import android.view.View
import androidx.appcompat.app.AppCompatActivity
import jp.juggler.subwaytooter.App1
import jp.juggler.subwaytooter.Pref
import jp.juggler.subwaytooter.PrefB
import jp.juggler.subwaytooter.R
import jp.juggler.subwaytooter.api.*
import jp.juggler.subwaytooter.api.entity.*
@ -436,7 +436,7 @@ class CompletionHelper(
private val openPickerEmoji: Runnable = Runnable {
EmojiPicker(
activity, accessInfo,
closeOnSelected = Pref.bpEmojiPickerCloseOnSelected(pref)
closeOnSelected = PrefB.bpEmojiPickerCloseOnSelected(pref)
) { result ->
val et = this.et ?: return@EmojiPicker
@ -469,7 +469,7 @@ class CompletionHelper(
fun openEmojiPickerFromMore() {
EmojiPicker(
activity, accessInfo,
closeOnSelected = Pref.bpEmojiPickerCloseOnSelected(pref)
closeOnSelected = PrefB.bpEmojiPickerCloseOnSelected(pref)
) { result ->
val et = this.et ?: return@EmojiPicker

View File

@ -5,7 +5,7 @@ import android.graphics.PorterDuff
import android.graphics.drawable.Drawable
import androidx.core.content.ContextCompat
import jp.juggler.subwaytooter.App1
import jp.juggler.subwaytooter.Pref
import jp.juggler.subwaytooter.PrefS
import jp.juggler.subwaytooter.R
import jp.juggler.subwaytooter.api.entity.TootStatus
import jp.juggler.subwaytooter.table.SavedAccount
@ -38,22 +38,22 @@ object CustomShare {
val defaultComponentName: String?
when (target) {
CustomShareTarget.Translate -> {
src = Pref.spTranslateAppComponent(pref)
src = PrefS.spTranslateAppComponent(pref)
defaultComponentName = translate_app_component_default
}
CustomShareTarget.CustomShare1 -> {
src = Pref.spCustomShare1(pref)
src = PrefS.spCustomShare1(pref)
defaultComponentName = null
}
CustomShareTarget.CustomShare2 -> {
src = Pref.spCustomShare2(pref)
src = PrefS.spCustomShare2(pref)
defaultComponentName = null
}
CustomShareTarget.CustomShare3 -> {
src = Pref.spCustomShare3(pref)
src = PrefS.spCustomShare3(pref)
defaultComponentName = null
}
}

View File

@ -7,7 +7,7 @@ import android.text.Spanned
import android.util.SparseBooleanArray
import androidx.annotation.DrawableRes
import jp.juggler.subwaytooter.App1
import jp.juggler.subwaytooter.Pref
import jp.juggler.subwaytooter.PrefB
import jp.juggler.subwaytooter.R
import jp.juggler.subwaytooter.emoji.CustomEmoji
import jp.juggler.subwaytooter.emoji.EmojiMap
@ -34,7 +34,7 @@ object EmojiDecoder {
var handleUnicodeEmoji = true
fun customEmojiSeparator(pref: SharedPreferences) = if (Pref.bpCustomEmojiSeparatorZwsp(pref)) {
fun customEmojiSeparator(pref: SharedPreferences) = if (PrefB.bpCustomEmojiSeparatorZwsp(pref)) {
'\u200B'
} else {
' '
@ -351,7 +351,7 @@ object EmojiDecoder {
val useEmojioneShortcode = when (val context = options.context) {
null -> false
else -> Pref.bpEmojioneShortcode(context.pref())
else -> PrefB.bpEmojioneShortcode(context.pref())
}
splitShortCode(s, callback = object : ShortCodeSplitterCallback {
@ -376,7 +376,7 @@ object EmojiDecoder {
val emojiCustom = emojiMapCustom?.get(name)
if (emojiCustom != null) {
val url = when {
Pref.bpDisableEmojiAnimation(App1.pref) && emojiCustom.staticUrl?.isNotEmpty() == true -> emojiCustom.staticUrl
PrefB.bpDisableEmojiAnimation(App1.pref) && emojiCustom.staticUrl?.isNotEmpty() == true -> emojiCustom.staticUrl
else -> emojiCustom.url
}
builder.addNetworkEmojiSpan(part, url)
@ -421,7 +421,7 @@ object EmojiDecoder {
s: String,
emojiMapCustom: HashMap<String, CustomEmoji>? = null
): String {
val decodeEmojioneShortcode = Pref.bpEmojioneShortcode(App1.pref)
val decodeEmojioneShortcode = PrefB.bpEmojioneShortcode(App1.pref)
val sb = StringBuilder()

View File

@ -7,7 +7,7 @@ import android.text.SpannableStringBuilder
import android.text.Spanned
import android.text.style.*
import jp.juggler.subwaytooter.App1
import jp.juggler.subwaytooter.Pref
import jp.juggler.subwaytooter.PrefB
import jp.juggler.subwaytooter.R
import jp.juggler.subwaytooter.api.entity.*
import jp.juggler.subwaytooter.span.*
@ -468,10 +468,10 @@ object HTMLDecoder {
else -> false
}
fun canSkipEncode(isBlockParent: Boolean, parent: Node, prev: Node?, next: Node?) = when {
fun canSkipEncode(isBlockParent: Boolean, curr: Node, parent: Node, prev: Node?, next: Node?) = when {
!isBlockParent -> false
tag != TAG_TEXT -> false
text.isNotBlank() -> false
curr.tag != TAG_TEXT -> false
curr.text.isNotBlank() -> false
else -> when {
prev?.tag?.tagsCanRemoveNearSpaces() == true -> true
next?.tag?.tagsCanRemoveNearSpaces() == true -> true
@ -480,6 +480,248 @@ object HTMLDecoder {
}
}
fun encodeText(options: DecodeOptions, sb: SpannableStringBuilder) {
if (options.context != null && options.decodeEmoji) {
sb.append(options.decodeEmoji(decodeEntity(text)))
} else {
sb.append(decodeEntity(text))
}
}
fun encodeImage(options: DecodeOptions, sb: SpannableStringBuilder) {
val attrs = parseAttributes(text)
if (options.unwrapEmojiImageTag) {
val cssClass = attrs["class"]
val title = attrs["title"]
val url = attrs["src"]
val alt = attrs["alt"]
if (cssClass != null &&
title != null &&
cssClass.contains("emojione") &&
reShortcode.matcher(title).find()
) {
sb.append(options.decodeEmoji(title))
return
} else if (cssClass == "emoji" && url != null && alt != null && reNotestockEmojiAlt.matches(alt)) {
// notestock custom emoji
sb.run {
val start = length
append(alt)
val end = length
setSpan(
NetworkEmojiSpan(url, scale = options.enlargeCustomEmoji),
start,
end,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
)
}
return
}
}
sb.append("<img ")
val url = attrs["src"] ?: ""
val caption = attrs["alt"] ?: ""
if (caption.isNotEmpty() || url.isNotEmpty()) {
val start = sb.length
sb.append(caption.notEmpty() ?: url)
if (reUrlStart.find(url) != null) {
val span =
MyClickableSpan(LinkInfo(url = url, ac = null, tag = null, caption = caption, mention = null))
sb.setSpan(span, start, sb.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
}
sb.append(" ")
}
sb.append("/>")
}
class EncodeSpanEnv(
val options: DecodeOptions,
val listContext: ListContext,
val tag: String,
val sb: SpannableStringBuilder,
val sbTmp: SpannableStringBuilder,
val spanStart: Int,
)
val originalFlusher: EncodeSpanEnv.() -> Unit = {
when (tag) {
"s", "strike", "del" -> {
sb.setSpan(StrikethroughSpan(), spanStart, sb.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
}
"em" -> {
sb.setSpan(
fontSpan(Typeface.defaultFromStyle(Typeface.ITALIC)),
spanStart,
sb.length,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
)
}
"strong" -> {
sb.setSpan(StyleSpan(Typeface.BOLD), spanStart, sb.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
}
"tr" -> {
sb.append("|")
}
"style", "script" -> {
// sb_tmpにレンダリングした分は読み捨てる
}
"h1" -> {
sb.setSpan(StyleSpan(Typeface.BOLD), spanStart, sb.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
sb.setSpan(RelativeSizeSpan(1.8f), spanStart, sb.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
}
"h2" -> {
sb.setSpan(StyleSpan(Typeface.BOLD), spanStart, sb.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
sb.setSpan(RelativeSizeSpan(1.6f), spanStart, sb.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
}
"h3" -> {
sb.setSpan(StyleSpan(Typeface.BOLD), spanStart, sb.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
sb.setSpan(RelativeSizeSpan(1.4f), spanStart, sb.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
}
"h4" -> {
sb.setSpan(StyleSpan(Typeface.BOLD), spanStart, sb.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
sb.setSpan(RelativeSizeSpan(1.2f), spanStart, sb.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
}
"h5" -> {
sb.setSpan(StyleSpan(Typeface.BOLD), spanStart, sb.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
sb.setSpan(RelativeSizeSpan(1.0f), spanStart, sb.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
}
"h6" -> {
sb.setSpan(StyleSpan(Typeface.BOLD), spanStart, sb.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
sb.setSpan(RelativeSizeSpan(0.8f), spanStart, sb.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
}
"pre" -> {
sb.setSpan(BackgroundColorSpan(0x40808080), spanStart, sb.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
sb.setSpan(RelativeSizeSpan(0.7f), spanStart, sb.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
sb.setSpan(fontSpan(Typeface.MONOSPACE), spanStart, sb.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
}
"code" -> {
sb.setSpan(BackgroundColorSpan(0x40808080), spanStart, sb.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
sb.setSpan(fontSpan(Typeface.MONOSPACE), spanStart, sb.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
}
"hr" -> sb.append("----------")
}
}
val tmpFlusher = HashMap<String, EncodeSpanEnv.() -> Unit>().apply {
fun add(vararg tags: String, block: EncodeSpanEnv.() -> Unit) {
for (tag in tags) this[tag] = block
}
add("a") {
val linkInfo = formatLinkCaption(options, sbTmp, href ?: "")
val caption = linkInfo.caption
if (caption.isNotEmpty()) {
val start = sb.length
sb.append(linkInfo.caption)
val end = sb.length
if (linkInfo.url.isNotEmpty()) {
val span = MyClickableSpan(linkInfo)
sb.setSpan(span, start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
}
// リンクスパンを設定した後に色をつける
val list = options.highlightTrie?.matchList(sb, start, end)
if (list != null) {
for (range in list) {
val word = HighlightWord.load(range.word) ?: continue
sb.setSpan(
HighlightSpan(word.color_fg, word.color_bg),
range.start,
range.end,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
)
if (word.sound_type != HighlightWord.SOUND_TYPE_NONE) {
if (options.highlightSound == null) options.highlightSound = word
}
if (word.speech != 0) {
if (options.highlightSpeech == null) options.highlightSpeech = word
}
if (options.highlightAny == null) options.highlightAny = word
}
}
}
}
add("style", "script") {
// 読み捨てる
// 最適化によりtmpFlusherOriginalとこのラムダが同一オブジェクトにならないようにする
}
add("blockquote") {
val bg_color = listContext.quoteColor()
// TextView の文字装飾では「ブロック要素の入れ子」を表現できない
// 内容の各行の始端に何か追加するというのがまずキツい
// しかし各行の頭に引用マークをつけないと引用のネストで意味が通じなくなってしまう
val startItalic = sb.length
sbTmp.splitLines().forEach { line ->
val lineStart = sb.length
sb.append("> ")
sb.setSpan(
BackgroundColorSpan(bg_color),
lineStart,
lineStart + 1,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
)
sb.append(line)
}
sb.setSpan(
fontSpan(Typeface.defaultFromStyle(Typeface.ITALIC)),
startItalic,
sb.length,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
)
}
add("li") {
val lineHeader1 = listContext.increment()
val lineHeader2 = " ".repeat(lineHeader1.length)
sbTmp.splitLines().forEachIndexed { i, line ->
sb.append(if (i == 0) lineHeader1 else lineHeader2)
sb.append(line)
}
}
add("dt") {
val prefix = listContext.increment()
val startBold = sb.length
sbTmp.splitLines().forEach { line ->
sb.append(prefix)
sb.append(line)
}
sb.setSpan(
fontSpan(Typeface.defaultFromStyle(Typeface.BOLD)),
startBold,
sb.length,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
)
}
add("dd") {
val prefix = listContext.increment() + "  "
sbTmp.splitLines().forEach { line ->
sb.append(prefix)
sb.append(line)
}
}
}
fun childListContext(tag: String, outerContext: ListContext) = when (tag) {
"ol" -> outerContext.subOrdered()
"ul" -> outerContext.subUnordered()
"dl" -> outerContext.subDefinition()
"blockquote" -> outerContext.subQuote()
else -> outerContext
}
fun encodeSpan(
options: DecodeOptions,
sb: SpannableStringBuilder,
@ -488,59 +730,11 @@ object HTMLDecoder {
val isBlock = blockLevelElements.contains(tag)
when (tag) {
TAG_TEXT -> {
if (options.context != null && options.decodeEmoji) {
sb.append(options.decodeEmoji(decodeEntity(text)))
} else {
sb.append(decodeEntity(text))
}
encodeText(options, sb)
return
}
"img" -> {
val attrs = parseAttributes(text)
if (options.unwrapEmojiImageTag) {
val cssClass = attrs["class"]
val title = attrs["title"]
val url = attrs["src"]
val alt = attrs["alt"]
if (cssClass != null &&
title != null &&
cssClass.contains("emojione") &&
reShortcode.matcher(title).find()
) {
sb.append(options.decodeEmoji(title))
return
} else if (cssClass == "emoji" && url != null && alt != null && reNotestockEmojiAlt.matches(alt)) {
// notestock custom emoji
sb.run {
val start = length
append(alt)
val end = length
setSpan(
NetworkEmojiSpan(url, scale = options.enlargeCustomEmoji),
start,
end,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
)
}
return
}
}
sb.append("<img ")
val url = attrs["src"] ?: ""
val caption = attrs["alt"] ?: ""
if (caption.isNotEmpty() || url.isNotEmpty()) {
val start = sb.length
sb.append(caption.notEmpty() ?: url)
if (reUrlStart.find(url) != null) {
val span =
MyClickableSpan(LinkInfo(url = url, ac = null, tag = null, caption = caption, mention = null))
sb.setSpan(span, start, sb.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
}
sb.append(" ")
}
sb.append("/>")
encodeImage(options, sb)
return
}
@ -556,216 +750,46 @@ object HTMLDecoder {
}
}
var spanStart = 0
val tmpFlusherOriginal: (SpannableStringBuilder) -> Unit = {
when (tag) {
"s", "strike", "del" -> {
sb.setSpan(StrikethroughSpan(), spanStart, sb.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
}
"em" -> {
sb.setSpan(
fontSpan(Typeface.defaultFromStyle(Typeface.ITALIC)),
spanStart,
sb.length,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
)
}
"strong" -> {
sb.setSpan(StyleSpan(Typeface.BOLD), spanStart, sb.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
}
"tr" -> {
sb.append("|")
}
"style", "script" -> {
// sb_tmpにレンダリングした分は読み捨てる
}
"h1" -> {
sb.setSpan(StyleSpan(Typeface.BOLD), spanStart, sb.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
sb.setSpan(RelativeSizeSpan(1.8f), spanStart, sb.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
}
"h2" -> {
sb.setSpan(StyleSpan(Typeface.BOLD), spanStart, sb.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
sb.setSpan(RelativeSizeSpan(1.6f), spanStart, sb.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
}
"h3" -> {
sb.setSpan(StyleSpan(Typeface.BOLD), spanStart, sb.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
sb.setSpan(RelativeSizeSpan(1.4f), spanStart, sb.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
}
"h4" -> {
sb.setSpan(StyleSpan(Typeface.BOLD), spanStart, sb.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
sb.setSpan(RelativeSizeSpan(1.2f), spanStart, sb.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
}
"h5" -> {
sb.setSpan(StyleSpan(Typeface.BOLD), spanStart, sb.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
sb.setSpan(RelativeSizeSpan(1.0f), spanStart, sb.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
}
"h6" -> {
sb.setSpan(StyleSpan(Typeface.BOLD), spanStart, sb.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
sb.setSpan(RelativeSizeSpan(0.8f), spanStart, sb.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
}
"pre" -> {
sb.setSpan(BackgroundColorSpan(0x40808080), spanStart, sb.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
sb.setSpan(RelativeSizeSpan(0.7f), spanStart, sb.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
sb.setSpan(fontSpan(Typeface.MONOSPACE), spanStart, sb.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
}
"code" -> {
sb.setSpan(BackgroundColorSpan(0x40808080), spanStart, sb.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
sb.setSpan(fontSpan(Typeface.MONOSPACE), spanStart, sb.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
}
"hr" -> sb.append("----------")
}
}
val tmpFlusher = when (tag) {
"a" -> {
{ sb_tmp ->
val linkInfo = formatLinkCaption(options, sb_tmp, href ?: "")
val caption = linkInfo.caption
if (caption.isNotEmpty()) {
val start = sb.length
sb.append(linkInfo.caption)
val end = sb.length
if (linkInfo.url.isNotEmpty()) {
val span = MyClickableSpan(linkInfo)
sb.setSpan(span, start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
}
// リンクスパンを設定した後に色をつける
val list = options.highlightTrie?.matchList(sb, start, end)
if (list != null) {
for (range in list) {
val word = HighlightWord.load(range.word) ?: continue
sb.setSpan(
HighlightSpan(word.color_fg, word.color_bg),
range.start,
range.end,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
)
if (word.sound_type != HighlightWord.SOUND_TYPE_NONE) {
if (options.highlightSound == null) options.highlightSound = word
}
if (word.speech != 0) {
if (options.highlightSpeech == null) options.highlightSpeech = word
}
if (options.highlightAny == null) options.highlightAny = word
}
}
}
}
}
"style", "script" -> {
{
// 読み捨てる
// 最適化によりtmpFlusherOriginalとこのラムダが同一オブジェクトにならないようにする
}
}
"blockquote" -> {
{ sb_tmp ->
val bg_color = listContext.quoteColor()
// TextView の文字装飾では「ブロック要素の入れ子」を表現できない
// 内容の各行の始端に何か追加するというのがまずキツい
// しかし各行の頭に引用マークをつけないと引用のネストで意味が通じなくなってしまう
val startItalic = sb.length
sb_tmp.splitLines().forEach { line ->
val lineStart = sb.length
sb.append("> ")
sb.setSpan(
BackgroundColorSpan(bg_color),
lineStart,
lineStart + 1,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
)
sb.append(line)
}
sb.setSpan(
fontSpan(Typeface.defaultFromStyle(Typeface.ITALIC)),
startItalic,
sb.length,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
)
}
}
"li" -> {
{ sb_tmp ->
val lineHeader1 = listContext.increment()
val lineHeader2 = " ".repeat(lineHeader1.length)
sb_tmp.splitLines().forEachIndexed { i, line ->
sb.append(if (i == 0) lineHeader1 else lineHeader2)
sb.append(line)
}
}
}
"dt" -> {
{ sb_tmp ->
val prefix = listContext.increment()
val startBold = sb.length
sb_tmp.splitLines().forEach { line ->
sb.append(prefix)
sb.append(line)
}
sb.setSpan(
fontSpan(Typeface.defaultFromStyle(Typeface.BOLD)),
startBold,
sb.length,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
)
}
}
"dd" -> {
{ sb_tmp ->
val prefix = listContext.increment() + "  "
sb_tmp.splitLines().forEach { line ->
sb.append(prefix)
sb.append(line)
}
}
}
else -> tmpFlusherOriginal
}
val sb_tmp = if (tmpFlusher == tmpFlusherOriginal) {
sb
var flusher = this.tmpFlusher[tag]
val encodeSpanEnv = if (flusher != null) {
// 一時的なバッファに子要素を出力して、後で何か処理する
EncodeSpanEnv(
options = options,
listContext = listContext,
tag = tag,
sb = sb,
sbTmp = SpannableStringBuilder(),
spanStart = 0,
)
} else {
SpannableStringBuilder()
// 現在のバッファに出力する
flusher = originalFlusher
EncodeSpanEnv(
options = options,
listContext = listContext,
tag = tag,
sb = sb,
sbTmp = sb,
spanStart = sb.length
)
}
spanStart = sb_tmp.length
val childListContext = when (tag) {
"ol" -> listContext.subOrdered()
"ul" -> listContext.subUnordered()
"dl" -> listContext.subDefinition()
"blockquote" -> listContext.subQuote()
else -> listContext
}
val childListContext = childListContext(tag, listContext)
child_nodes.forEachIndexed { i, child ->
if (!canSkipEncode(
isBlock,
curr = child,
parent = this,
prev = child_nodes.elementAtOrNull(i - 1),
next = child_nodes.elementAtOrNull(i + 1)
)
) {
child.encodeSpan(options, sb_tmp, childListContext)
child.encodeSpan(options, encodeSpanEnv.sbTmp, childListContext)
}
}
tmpFlusher(sb_tmp)
flusher(encodeSpanEnv)
if (isBlock) {
// ブロック要素
@ -849,7 +873,7 @@ object HTMLDecoder {
val linkInfo = if (fullAcct != null) {
LinkInfo(
url = item.url,
caption = "@${(if (Pref.bpMentionFullAcct(App1.pref)) fullAcct else item.acct).pretty}",
caption = "@${(if (PrefB.bpMentionFullAcct(App1.pref)) fullAcct else item.acct).pretty}",
ac = AcctColor.load(fullAcct),
mention = item,
tag = link_tag
@ -939,7 +963,7 @@ object HTMLDecoder {
fun afterFullAcctResolved(fullAcct: Acct) {
linkInfo.ac = AcctColor.load(fullAcct)
if (options.mentionFullAcct || Pref.bpMentionFullAcct(App1.pref)) {
if (options.mentionFullAcct || PrefB.bpMentionFullAcct(App1.pref)) {
linkInfo.caption = "@${fullAcct.pretty}"
}
}

View File

@ -13,7 +13,7 @@ import android.util.SparseArray
import android.util.SparseBooleanArray
import jp.juggler.subwaytooter.ActMain
import jp.juggler.subwaytooter.App1
import jp.juggler.subwaytooter.Pref
import jp.juggler.subwaytooter.PrefB
import jp.juggler.subwaytooter.R
import jp.juggler.subwaytooter.api.entity.*
import jp.juggler.subwaytooter.span.*
@ -860,7 +860,7 @@ object MisskeyMarkdownDecoder {
// リンク表記はユーザの記述やアプリ設定の影響を受ける
val caption = "@${
when {
Pref.bpMentionFullAcct(App1.pref) -> fullAcct
PrefB.bpMentionFullAcct(App1.pref) -> fullAcct
else -> rawAcct
}.pretty
}"

View File

@ -4,7 +4,7 @@ import android.os.SystemClock
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import jp.juggler.subwaytooter.App1
import jp.juggler.subwaytooter.Pref
import jp.juggler.subwaytooter.PrefB
import jp.juggler.subwaytooter.R
import jp.juggler.subwaytooter.Styler
import jp.juggler.subwaytooter.api.TootApiClient
@ -131,7 +131,6 @@ class PostImpl(
return false
}
return true
}
@ -156,7 +155,7 @@ class PostImpl(
return false
}
if (!bConfirmTagCharacter && Pref.bpWarnHashtagAsciiAndNonAscii(App1.pref)) {
if (!bConfirmTagCharacter && PrefB.bpWarnHashtagAsciiAndNonAscii(App1.pref)) {
val tags = TootTag.findHashtags(content, account.isMisskey)
val badTags = tags
?.filter {
@ -565,7 +564,7 @@ class PostImpl(
val requestBuilder = bodyString.toRequestBody(MEDIA_TYPE_JSON).toPost()
if (!Pref.bpDontDuplicationCheck(App1.pref)) {
if (!PrefB.bpDontDuplicationCheck(App1.pref)) {
val digest = (bodyString + account.acct.ascii).digestSHA256Hex()
requestBuilder.header("Idempotency-Key", digest)
}

View File

@ -5,7 +5,7 @@ import android.content.Intent
import androidx.annotation.StringRes
import jp.juggler.subwaytooter.ActText
import jp.juggler.subwaytooter.App1
import jp.juggler.subwaytooter.Pref
import jp.juggler.subwaytooter.PrefB
import jp.juggler.subwaytooter.R
import jp.juggler.subwaytooter.api.entity.*
import jp.juggler.subwaytooter.table.SavedAccount
@ -302,7 +302,7 @@ object TootTextEncoder {
addHeader(context, sb, R.string.send_header_account_created_at, who.created_at)
addHeader(context, sb, R.string.send_header_account_statuses_count, who.statuses_count)
if (!Pref.bpHideFollowCount(App1.getAppState(context).pref)) {
if (!PrefB.bpHideFollowCount(App1.getAppState(context).pref)) {
addHeader(
context,
sb,

View File

@ -23,7 +23,7 @@ import com.bumptech.glide.load.resource.gif.MyGifDrawable
import com.bumptech.glide.request.target.ImageViewTarget
import com.bumptech.glide.request.target.Target
import com.bumptech.glide.request.transition.Transition
import jp.juggler.subwaytooter.Pref
import jp.juggler.subwaytooter.PrefB
import jp.juggler.util.LogCategory
import jp.juggler.util.clipRange
@ -80,7 +80,7 @@ class MyNetworkImageView : AppCompatImageView {
mCornerRadius = r
val gifUrl = if (Pref.bpEnableGifAnimation(pref)) gifUrlArg else null
val gifUrl = if (PrefB.bpEnableGifAnimation(pref)) gifUrlArg else null
if (gifUrl?.isNotEmpty() == true) {
mUrl = gifUrl

View File

@ -16,7 +16,7 @@ import android.widget.CompoundButton
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.SwitchCompat
import jp.juggler.subwaytooter.App1
import jp.juggler.subwaytooter.Pref
import jp.juggler.subwaytooter.PrefI
import jp.juggler.subwaytooter.R
import org.xmlpull.v1.XmlPullParser
import kotlin.math.pow
@ -109,8 +109,8 @@ fun Context.setSwitchColor(
root: View?
) {
val colorBg = attrColor(R.attr.colorWindowBackground)
val colorOn = Pref.ipSwitchOnColor(pref)
val colorOff = /* Pref.ipSwitchOffColor(pref).notZero() ?: */
val colorOn = PrefI.ipSwitchOnColor(pref)
val colorOff = /* PrefI.ipSwitchOffColor(pref).notZero() ?: */
attrColor(android.R.attr.colorPrimary)
val colorDisabled = mixColor(colorBg, colorOff)
@ -209,7 +209,7 @@ fun AppCompatActivity.setStatusBarColor(forceDark: Boolean = false) {
var c = when {
forceDark -> Color.BLACK
else -> Pref.ipStatusBarColor(App1.pref).notZero()
else -> PrefI.ipStatusBarColor(App1.pref).notZero()
?: attrColor(R.attr.colorPrimaryDark)
}
statusBarColor = c or Color.BLACK
@ -235,7 +235,7 @@ fun AppCompatActivity.setStatusBarColor(forceDark: Boolean = false) {
c = when {
forceDark -> Color.BLACK
else -> Pref.ipNavigationBarColor(App1.pref)
else -> PrefI.ipNavigationBarColor(App1.pref)
}
if (c != 0) {