diff --git a/.idea/dictionaries/tateisu.xml b/.idea/dictionaries/tateisu.xml index 63a3105a..74163de4 100644 --- a/.idea/dictionaries/tateisu.xml +++ b/.idea/dictionaries/tateisu.xml @@ -166,6 +166,7 @@ utoken weblate webpushcallback + webpushendpoint webpushserverkey webpushtokencheck weiner diff --git a/app/src/main/java/jp/juggler/subwaytooter/ActAccountSetting.kt b/app/src/main/java/jp/juggler/subwaytooter/ActAccountSetting.kt index 3f3b47ef..f4c17b68 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/ActAccountSetting.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/ActAccountSetting.kt @@ -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 -> diff --git a/app/src/main/java/jp/juggler/subwaytooter/ActMain.kt b/app/src/main/java/jp/juggler/subwaytooter/ActMain.kt index 4c76a52b..5dad5dbd 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/ActMain.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/ActMain.kt @@ -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(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) diff --git a/app/src/main/java/jp/juggler/subwaytooter/ActPost.kt b/app/src/main/java/jp/juggler/subwaytooter/ActPost.kt index 420d49fe..bcec2891 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/ActPost.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/ActPost.kt @@ -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(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(KEY_SENT_INTENT) - if (sentIntent != null) { + val sharedIntent = intent.getParcelableExtra(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(Intent.EXTRA_STREAM) - val type = sentIntent.type + val uri = sharedIntent.getParcelableExtra(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(Intent.EXTRA_STREAM) + sharedIntent.getParcelableArrayListExtra(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() + + // 自己レス以外なら元レスへのメンションを追加 + // 最初に追加する 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() - - // 自己レス以外なら元レスへのメンションを追加 - // 最初に追加する 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) { diff --git a/app/src/main/java/jp/juggler/subwaytooter/App1.kt b/app/src/main/java/jp/juggler/subwaytooter/App1.kt index 6a88af3e..ed1ca696 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/App1.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/App1.kt @@ -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 diff --git a/app/src/main/java/jp/juggler/subwaytooter/AppDataExporter.kt b/app/src/main/java/jp/juggler/subwaytooter/AppDataExporter.kt index 8aa1f3c2..c46a0221 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/AppDataExporter.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/AppDataExporter.kt @@ -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() } } diff --git a/app/src/main/java/jp/juggler/subwaytooter/AppSettingItem.kt b/app/src/main/java/jp/juggler/subwaytooter/AppSettingItem.kt index 53377f4d..a32b09d0 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/AppSettingItem.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/AppSettingItem.kt @@ -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()!! @@ -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)) } } diff --git a/app/src/main/java/jp/juggler/subwaytooter/Column.kt b/app/src/main/java/jp/juggler/subwaytooter/Column.kt index cd555bb4..f9b4177d 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/Column.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/Column.kt @@ -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) } diff --git a/app/src/main/java/jp/juggler/subwaytooter/ColumnExtra1.kt b/app/src/main/java/jp/juggler/subwaytooter/ColumnExtra1.kt index b4f0abe3..40ebb21f 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/ColumnExtra1.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/ColumnExtra1.kt @@ -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 = "" diff --git a/app/src/main/java/jp/juggler/subwaytooter/ColumnTask_Gap.kt b/app/src/main/java/jp/juggler/subwaytooter/ColumnTask_Gap.kt index ded8c1bb..094b7c11 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/ColumnTask_Gap.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/ColumnTask_Gap.kt @@ -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) { // ギャップを頭から読んだ場合、スクロール位置の調整は不要 diff --git a/app/src/main/java/jp/juggler/subwaytooter/ColumnTask_Loading.kt b/app/src/main/java/jp/juggler/subwaytooter/ColumnTask_Loading.kt index 910229ca..dc99eea9 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/ColumnTask_Loading.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/ColumnTask_Loading.kt @@ -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() } diff --git a/app/src/main/java/jp/juggler/subwaytooter/ColumnTask_Refresh.kt b/app/src/main/java/jp/juggler/subwaytooter/ColumnTask_Refresh.kt index 1d64b60d..a6196240 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/ColumnTask_Refresh.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/ColumnTask_Refresh.kt @@ -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) } diff --git a/app/src/main/java/jp/juggler/subwaytooter/ColumnViewHolder.kt b/app/src/main/java/jp/juggler/subwaytooter/ColumnViewHolder.kt index b5aa6c76..a8ecb738 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/ColumnViewHolder.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/ColumnViewHolder.kt @@ -226,7 +226,7 @@ class ColumnViewHolder( showAnnouncements(force = false) } - val procRestorescrollposition = object : Runnable { + val procRestoreScrollPosition = object : Runnable { override fun run() { activity.handler.removeCallbacks(this) @@ -347,7 +347,7 @@ class ColumnViewHolder( } } - if (Pref.bpShareViewPool(activity.pref)) { + if (PrefB.bpShareViewPool(activity.pref)) { listView.setRecycledViewPool(activity.viewPool) } listView.itemAnimator = null @@ -420,7 +420,7 @@ class ColumnViewHolder( cbWithHighlight, ).forEach { it.setOnCheckedChangeListener(this) } - if (Pref.bpMoveNotificationsQuickFilter(activity.pref)) { + if (PrefB.bpMoveNotificationsQuickFilter(activity.pref)) { (svQuickFilter.parent as? ViewGroup)?.removeView(svQuickFilter) llColumnSettingInside.addView(svQuickFilter, 0) @@ -543,9 +543,738 @@ class ColumnViewHolder( override fun onCheckedChanged(buttonView: CompoundButton?, isChecked: Boolean) = onCheckedChangedImpl(buttonView, isChecked) - fun inflate(activity: ActMain, parent: ViewGroup) = with(activity.UI {}) { - val b = Benchmark(log, "Item-Inflate", 40L) + private fun _LinearLayout.inflateColumnHeader() { + llColumnHeader = customView { + lparams(matchParent, wrapContent) + + orientation = LinearLayout.VERTICAL + + background = ContextCompat.getDrawable(context, R.drawable.bg_column_header) + startPadding = dip(12) + endPadding = dip(12) + topPadding = dip(3) + bottomPadding = dip(3) + + linearLayout { + lparams(matchParent, wrapContent) + gravity = Gravity.BOTTOM + + tvColumnContext = textView { + gravity = Gravity.END + startPadding = dip(4) + endPadding = dip(4) + textColor = context.attrColor(R.attr.colorColumnHeaderAcct) + textSize = 12f + }.lparams(0, wrapContent) { + weight = 1f + } + + tvColumnStatus = textView { + gravity = Gravity.END + textColor = context.attrColor(R.attr.colorColumnHeaderPageNumber) + textSize = 12f + }.lparams(wrapContent, wrapContent) { + marginStart = dip(12) + } + + tvColumnIndex = textView { + gravity = Gravity.END + textColor = context.attrColor(R.attr.colorColumnHeaderPageNumber) + textSize = 12f + }.lparams(wrapContent, wrapContent) { + marginStart = dip(4) + } + } + + linearLayout { + lparams(matchParent, wrapContent) { + topMargin = dip(0) + } + gravity = Gravity.CENTER_VERTICAL + isBaselineAligned = false + + ivColumnIcon = imageView { + importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_NO + scaleType = ImageView.ScaleType.FIT_CENTER + }.lparams(dip(32), dip(32)) { + endMargin = dip(4) + } + + tvColumnName = textView { + // Kannada語の "ಸ್ಥಳೀಯ ಟೈಮ್ ಲೈನ್" の上下が途切れることがあるらしい + // GS10+では再現しなかった + }.lparams(dip(0), wrapContent) { + weight = 1f + } + + frameLayout { + lparams(wrapContent, wrapContent) { + gravity = Gravity.CENTER_VERTICAL + startMargin = dip(2) + } + clipChildren = false + + btnAnnouncements = imageButton { + background = ContextCompat.getDrawable( + context, + R.drawable.btn_bg_transparent_round6dp + ) + contentDescription = context.getString(R.string.announcements) + setImageResource(R.drawable.ic_info_outline) + padding = dip(8) + scaleType = ImageView.ScaleType.FIT_CENTER + + btnAnnouncementsCutout = Paint().apply { + isAntiAlias = true + } + val path = Path() + addOutsideDrawer(this) { canvas, parent, view, left, top -> + if (llAnnouncementsBox.visibility == View.VISIBLE) { + val viewW = view.width + val viewH = view.height + val triTopX = (left + viewW / 2).toFloat() + val triTopY = top.toFloat() + viewH * 0.75f + val triBottomLeft = left.toFloat() + val triBottomRight = (left + viewW).toFloat() + val triBottom = parent.height.toFloat() + path.reset() + path.moveTo(triTopX, triTopY) + path.lineTo(triBottomRight, triBottom) + path.lineTo(triBottomLeft, triBottom) + path.lineTo(triTopX, triTopY) + canvas.drawPath(path, btnAnnouncementsCutout) + } + } + }.lparams(dip(40), dip(40)) + + btnAnnouncementsBadge = imageView { + setImageResource(R.drawable.announcements_dot) + scaleType = ImageView.ScaleType.FIT_CENTER + }.lparams(dip(7), dip(7)) { + gravity = Gravity.END or Gravity.TOP + endMargin = dip(4) + topMargin = dip(4) + } + } + + frameLayout { + lparams(wrapContent, wrapContent) { + gravity = Gravity.CENTER_VERTICAL + startMargin = dip(2) + } + clipChildren = false + + btnColumnSetting = imageButton { + background = + ContextCompat.getDrawable( + context, + R.drawable.btn_bg_transparent_round6dp + ) + contentDescription = context.getString(R.string.setting) + setImageResource(R.drawable.ic_tune) + padding = dip(8) + scaleType = ImageView.ScaleType.FIT_CENTER + + val paint = Paint().apply { + isAntiAlias = true + color = + context.attrColor(R.attr.colorColumnSettingBackground) + } + val path = Path() + addOutsideDrawer(this) { canvas, parent, view, left, top -> + if (llColumnSetting.visibility == View.VISIBLE) { + val viewW = view.width + val viewH = view.height + val triTopX = (left + viewW / 2).toFloat() + val triTopY = top.toFloat() + viewH * 0.75f + val triBottomLeft = left.toFloat() + val triBottomRight = (left + viewW).toFloat() + val triBottom = parent.height.toFloat() + path.reset() + path.moveTo(triTopX, triTopY) + path.lineTo(triBottomRight, triBottom) + path.lineTo(triBottomLeft, triBottom) + path.lineTo(triTopX, triTopY) + canvas.drawPath(path, paint) + } + } + }.lparams(dip(40), dip(40)) + } + + btnColumnReload = imageButton { + background = + ContextCompat.getDrawable( + context, + R.drawable.btn_bg_transparent_round6dp + ) + contentDescription = context.getString(R.string.reload) + setImageResource(R.drawable.ic_refresh) + padding = dip(8) + scaleType = ImageView.ScaleType.FIT_CENTER + }.lparams(dip(40), dip(40)) { + gravity = Gravity.CENTER_VERTICAL + startMargin = dip(2) + } + + btnColumnClose = imageButton { + background = ContextCompat.getDrawable( + context, + R.drawable.btn_bg_transparent_round6dp + ) + contentDescription = context.getString(R.string.close_column) + setImageResource(R.drawable.ic_close) + padding = dip(8) + scaleType = ImageView.ScaleType.FIT_CENTER + }.lparams(dip(40), dip(40)) { + gravity = Gravity.CENTER_VERTICAL + startMargin = dip(2) + } + } + } // end of column header + } + + private fun _LinearLayout.inflateColumnSetting() { var label: TextView? = null + llColumnSetting = maxHeightScrollView { + lparams(matchParent, wrapContent) + isScrollbarFadingEnabled = false + maxHeight = dip(240) + + backgroundColor = + context.attrColor(R.attr.colorColumnSettingBackground) + + llColumnSettingInside = verticalLayout { + lparams(matchParent, wrapContent) + + startPadding = dip(12) + endPadding = dip(12) + topPadding = dip(3) + bottomPadding = dip(3) + + llHashtagExtra = verticalLayout { + lparams(matchParent, wrapContent) + + label = textView { + textColor = + context.attrColor(R.attr.colorColumnHeaderPageNumber) + text = context.getString(R.string.hashtag_extra_any) + }.lparams(matchParent, wrapContent) + + etHashtagExtraAny = editText { + id = View.generateViewId() + inputType = InputType.TYPE_CLASS_TEXT + maxLines = 1 + setHorizontallyScrolling(true) + isHorizontalScrollBarEnabled = true + }.lparams(matchParent, wrapContent) + label?.labelFor = etHashtagExtraAny.id + + label = textView { + textColor = + context.attrColor(R.attr.colorColumnHeaderPageNumber) + text = context.getString(R.string.hashtag_extra_all) + }.lparams(matchParent, wrapContent) + + etHashtagExtraAll = editText { + id = View.generateViewId() + inputType = InputType.TYPE_CLASS_TEXT + maxLines = 1 + setHorizontallyScrolling(true) + isHorizontalScrollBarEnabled = true + }.lparams(matchParent, wrapContent) + label?.labelFor = etHashtagExtraAll.id + + label = textView { + textColor = + context.attrColor(R.attr.colorColumnHeaderPageNumber) + text = context.getString(R.string.hashtag_extra_none) + }.lparams(matchParent, wrapContent) + + etHashtagExtraNone = editText { + id = View.generateViewId() + inputType = InputType.TYPE_CLASS_TEXT + maxLines = 1 + setHorizontallyScrolling(true) + isHorizontalScrollBarEnabled = true + }.lparams(matchParent, wrapContent) + label?.labelFor = etHashtagExtraNone.id + } // end of hashtag extra + + cbDontCloseColumn = checkBox { + text = context.getString(R.string.dont_close_column) + }.lparams(matchParent, wrapContent) + + cbRemoteOnly = checkBox { + text = context.getString(R.string.remote_only) + }.lparams(matchParent, wrapContent) + + cbWithAttachment = checkBox { + text = context.getString(R.string.with_attachment) + }.lparams(matchParent, wrapContent) + + cbWithHighlight = checkBox { + text = context.getString(R.string.with_highlight) + }.lparams(matchParent, wrapContent) + + cbDontShowBoost = checkBox { + text = context.getString(R.string.dont_show_boost) + }.lparams(matchParent, wrapContent) + + cbDontShowFavourite = checkBox { + text = context.getString(R.string.dont_show_favourite) + }.lparams(matchParent, wrapContent) + + cbDontShowFollow = checkBox { + text = context.getString(R.string.dont_show_follow) + }.lparams(matchParent, wrapContent) + + cbDontShowReply = checkBox { + text = context.getString(R.string.dont_show_reply) + }.lparams(matchParent, wrapContent) + + cbDontShowReaction = checkBox { + text = context.getString(R.string.dont_show_reaction) + }.lparams(matchParent, wrapContent) + + cbDontShowVote = checkBox { + text = context.getString(R.string.dont_show_vote) + }.lparams(matchParent, wrapContent) + + cbDontShowNormalToot = checkBox { + text = context.getString(R.string.dont_show_normal_toot) + }.lparams(matchParent, wrapContent) + + cbDontShowNonPublicToot = checkBox { + text = context.getString(R.string.dont_show_non_public_toot) + }.lparams(matchParent, wrapContent) + + cbInstanceLocal = checkBox { + text = context.getString(R.string.instance_local) + }.lparams(matchParent, wrapContent) + + cbDontStreaming = checkBox { + text = context.getString(R.string.dont_use_streaming_api) + }.lparams(matchParent, wrapContent) + + cbDontAutoRefresh = checkBox { + text = context.getString(R.string.dont_refresh_on_activity_resume) + }.lparams(matchParent, wrapContent) + + cbHideMediaDefault = checkBox { + text = context.getString(R.string.hide_media_default) + }.lparams(matchParent, wrapContent) + + cbSystemNotificationNotRelated = checkBox { + text = context.getString(R.string.system_notification_not_related) + }.lparams(matchParent, wrapContent) + + cbEnableSpeech = checkBox { + text = context.getString(R.string.enable_speech) + }.lparams(matchParent, wrapContent) + + cbOldApi = checkBox { + text = context.getString(R.string.use_old_api) + }.lparams(matchParent, wrapContent) + + llRegexFilter = linearLayout { + lparams(matchParent, wrapContent) + + label = textView { + textColor = + context.attrColor(R.attr.colorColumnHeaderPageNumber) + text = context.getString(R.string.regex_filter) + }.lparams(wrapContent, wrapContent) + + tvRegexFilterError = textView { + textColor = context.attrColor(R.attr.colorRegexFilterError) + }.lparams(0, wrapContent) { + weight = 1f + startMargin = dip(4) + } + } + + etRegexFilter = editText { + id = View.generateViewId() + inputType = InputType.TYPE_CLASS_TEXT + maxLines = 1 + setHorizontallyScrolling(true) + isHorizontalScrollBarEnabled = true + }.lparams(matchParent, wrapContent) + + label?.labelFor = etRegexFilter.id + + btnDeleteNotification = button { + isAllCaps = false + text = context.getString(R.string.notification_delete) + }.lparams(matchParent, wrapContent) + + btnColor = button { + isAllCaps = false + text = context.getString(R.string.color_and_background) + }.lparams(matchParent, wrapContent) + + btnLanguageFilter = button { + isAllCaps = false + text = context.getString(R.string.language_filter) + }.lparams(matchParent, wrapContent) + } + } // end of column setting scroll view + } + + private fun _LinearLayout.inflateAnnouncementsBox() { + llAnnouncementsBox = verticalLayout { + lparams(matchParent, wrapContent) { + startMargin = dip(6) + endMargin = dip(6) + bottomMargin = dip(2) + } + + val buttonHeight = ActMain.boostButtonSize + val paddingH = (buttonHeight.toFloat() * 0.1f + 0.5f).toInt() + val paddingV = (buttonHeight.toFloat() * 0.1f + 0.5f).toInt() + + linearLayout { + lparams(matchParent, wrapContent) + val padLr = dip(6) + setPadding(padLr, 0, padLr, 0) + + background = ContextCompat.getDrawable( + context, + R.drawable.btn_bg_transparent_round6dp + ) + + gravity = Gravity.CENTER_VERTICAL or Gravity.END + + tvAnnouncementsCaption = textView { + gravity = Gravity.END or Gravity.CENTER_VERTICAL + text = context.getString(R.string.announcements) + }.lparams(0, wrapContent) { + weight = 1f + } + + btnAnnouncementsPrev = imageButton { + background = ContextCompat.getDrawable( + context, + R.drawable.btn_bg_transparent_round6dp + ) + contentDescription = context.getString(R.string.previous) + imageResource = R.drawable.ic_arrow_start + setPadding(paddingH, paddingV, paddingH, paddingV) + scaleType = ImageView.ScaleType.FIT_CENTER + }.lparams(buttonHeight, buttonHeight) { + marginStart = dip(4) + } + + tvAnnouncementsIndex = textView { + }.lparams(wrapContent, wrapContent) { + marginStart = dip(4) + } + + btnAnnouncementsNext = imageButton { + background = ContextCompat.getDrawable( + context, + R.drawable.btn_bg_transparent_round6dp + ) + contentDescription = context.getString(R.string.next) + imageResource = R.drawable.ic_arrow_end + setPadding(paddingH, paddingV, paddingH, paddingV) + scaleType = ImageView.ScaleType.FIT_CENTER + }.lparams(buttonHeight, buttonHeight) { + marginStart = dip(4) + } + } + + llAnnouncements = maxHeightScrollView { + lparams(matchParent, wrapContent) { + topMargin = dip(1) + } + + val padLr = dip(6) + val padTb = dip(2) + setPadding(padLr, padTb, padLr, padTb) + + scrollBarStyle = View.SCROLLBARS_OUTSIDE_OVERLAY + isScrollbarFadingEnabled = false + + maxHeight = dip(240) + + verticalLayout { + lparams(matchParent, wrapContent) + + // 期間があれば表示する + tvAnnouncementPeriod = textView { + gravity = Gravity.END + }.lparams(matchParent, wrapContent) { + bottomMargin = dip(3) + } + + tvAnnouncementContent = myTextView { + setLineSpacing(lineSpacingExtra, 1.1f) + // tools:text="Contents\nContents" + }.lparams(matchParent, wrapContent) { + topMargin = dip(3) + } + + llAnnouncementExtra = verticalLayout { + lparams(matchParent, wrapContent) { + topMargin = dip(3) + } + } + } + } + } + } + + private fun _LinearLayout.inflateSearchBar() { + llSearch = verticalLayout { + lparams(matchParent, wrapContent) + + linearLayout { + lparams(matchParent, wrapContent) + isBaselineAligned = false + gravity = Gravity.CENTER + + etSearch = editText { + id = View.generateViewId() + imeOptions = EditorInfo.IME_ACTION_SEARCH + inputType = InputType.TYPE_CLASS_TEXT + maxLines = 1 + }.lparams(0, wrapContent) { + weight = 1f + } + + flEmoji = flexboxLayout { + flexWrap = FlexWrap.WRAP + justifyContent = JustifyContent.FLEX_START + }.lparams(0, wrapContent) { + weight = 1f + } + + btnEmojiAdd = imageButton { + backgroundResource = R.drawable.btn_bg_transparent_round6dp + contentDescription = context.getString(R.string.add) + imageResource = R.drawable.ic_add + imageTintList = ColorStateList.valueOf( + context.attrColor(R.attr.colorVectorDrawable) + ) + }.lparams(dip(40), dip(40)) { + startMargin = dip(4) + } + + btnSearchClear = imageButton { + backgroundResource = R.drawable.btn_bg_transparent_round6dp + contentDescription = context.getString(R.string.clear) + imageResource = R.drawable.ic_close + imageTintList = ColorStateList.valueOf( + context.attrColor(R.attr.colorVectorDrawable) + ) + }.lparams(dip(40), dip(40)) { + startMargin = dip(4) + } + + btnSearch = imageButton { + backgroundResource = R.drawable.btn_bg_transparent_round6dp + contentDescription = context.getString(R.string.search) + imageResource = R.drawable.ic_search + imageTintList = ColorStateList.valueOf( + context.attrColor(R.attr.colorVectorDrawable) + ) + }.lparams(dip(40), dip(40)) { + startMargin = dip(4) + } + } + + cbResolve = checkBox { + text = context.getString(R.string.resolve_non_local_account) + }.lparams(wrapContent, wrapContent) // チェックボックスの余白はタッチ判定外 + + tvEmojiDesc = textView { + text = context.getString(R.string.long_tap_to_delete) + textColor = context.attrColor(R.attr.colorColumnHeaderPageNumber) + textSize = 12f + }.lparams(wrapContent, wrapContent) + } // end of search bar + } + + private fun _LinearLayout.inflateListBar() { + llListList = linearLayout { + lparams(matchParent, wrapContent) + + isBaselineAligned = false + gravity = Gravity.CENTER + + etListName = editText { + hint = context.getString(R.string.list_create_hint) + imeOptions = EditorInfo.IME_ACTION_SEND + inputType = InputType.TYPE_CLASS_TEXT + }.lparams(0, wrapContent) { + weight = 1f + } + + btnListAdd = imageButton { + backgroundResource = R.drawable.btn_bg_transparent_round6dp + contentDescription = context.getString(R.string.add) + imageResource = R.drawable.ic_add + imageTintList = ColorStateList.valueOf( + context.attrColor( + R.attr.colorVectorDrawable + ) + ) + }.lparams(dip(40), dip(40)) { + startMargin = dip(4) + } + } // end of list bar header + } + + private fun _LinearLayout.inflateQuickFilter() { + svQuickFilter = horizontalScrollView { + lparams(matchParent, wrapContent) + isFillViewport = true + linearLayout { + lparams(matchParent, dip(40)) + + btnQuickFilterAll = button { + backgroundResource = R.drawable.btn_bg_transparent_round6dp + minWidthCompat = dip(40) + startPadding = dip(4) + endPadding = dip(4) + isAllCaps = false + stateListAnimator = null + text = context.getString(R.string.all) + }.lparams(wrapContent, matchParent) { + margin = 0 + } + + btnQuickFilterMention = imageButton { + backgroundResource = R.drawable.btn_bg_transparent_round6dp + contentDescription = context.getString(R.string.mention2) + }.lparams(dip(40), matchParent) { + margin = 0 + } + + btnQuickFilterFavourite = imageButton { + backgroundResource = R.drawable.btn_bg_transparent_round6dp + contentDescription = context.getString(R.string.favourite) + }.lparams(dip(40), matchParent) { + margin = 0 + } + + btnQuickFilterBoost = imageButton { + backgroundResource = R.drawable.btn_bg_transparent_round6dp + contentDescription = context.getString(R.string.boost) + }.lparams(dip(40), matchParent) { + margin = 0 + } + + btnQuickFilterFollow = imageButton { + backgroundResource = R.drawable.btn_bg_transparent_round6dp + contentDescription = context.getString(R.string.follow) + }.lparams(dip(40), matchParent) { + margin = 0 + } + + btnQuickFilterPost = imageButton { + backgroundResource = R.drawable.btn_bg_transparent_round6dp + contentDescription = context.getString(R.string.notification_type_post) + }.lparams(dip(40), matchParent) { + margin = 0 + } + + btnQuickFilterReaction = imageButton { + backgroundResource = R.drawable.btn_bg_transparent_round6dp + contentDescription = context.getString(R.string.reaction) + }.lparams(dip(40), matchParent) { + margin = 0 + } + + btnQuickFilterVote = imageButton { + backgroundResource = R.drawable.btn_bg_transparent_round6dp + contentDescription = context.getString(R.string.vote_polls) + }.lparams(dip(40), matchParent) { + margin = 0 + } + } + } // end of notification quick filter bar + } + + private fun _LinearLayout.inflateColumnBody(actMain: ActMain) { + flColumnBackground = frameLayout { + ivColumnBackgroundImage = imageView { + importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_NO + scaleType = ImageView.ScaleType.CENTER_CROP + visibility = View.GONE + }.lparams(matchParent, matchParent) + + llLoading = verticalLayout { + lparams(matchParent, matchParent) + + isBaselineAligned = false + + gravity = Gravity.CENTER + + tvLoading = textView { + gravity = Gravity.CENTER + }.lparams(matchParent, wrapContent) + + btnConfirmMail = button { + text = context.getString(R.string.resend_confirm_mail) + background = ContextCompat.getDrawable( + context, + R.drawable.btn_bg_transparent_round6dp + ) + }.lparams(matchParent, wrapContent) { + topMargin = dip(8) + } + } + + refreshLayout = swipyRefreshLayout { + lparams(matchParent, matchParent) + + direction = SwipyRefreshLayoutDirection.BOTH + + // スタイルで指定しないとAndroid 6 で落ちる… + listView = recyclerView { + listLayoutManager = LinearLayoutManager(actMain) + layoutManager = listLayoutManager + }.lparams(matchParent, matchParent) { + } + } + + llRefreshError = frameLayout { + foregroundGravity = Gravity.BOTTOM + backgroundResource = R.drawable.bg_refresh_error + + startPadding = dip(6) + endPadding = dip(6) + topPadding = dip(3) + bottomPadding = dip(3) + + ivRefreshError = imageView { + importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_NO + scaleType = ImageView.ScaleType.FIT_CENTER + imageResource = R.drawable.ic_error + imageTintList = ColorStateList.valueOf(Color.RED) + }.lparams(dip(24), dip(24)) { + gravity = Gravity.START or Gravity.CENTER_VERTICAL + startMargin = dip(4) + } + + tvRefreshError = textView { + textColor = Color.WHITE + }.lparams(matchParent, wrapContent) { + gravity = Gravity.TOP or Gravity.START + startMargin = dip(32) + } + }.lparams(matchParent, wrapContent) { + margin = dip(6) + } + }.lparams(matchParent, 0) { + weight = 1f + } + } + + fun inflate(actMain: ActMain, parent: ViewGroup) = with(actMain.UI {}) { + val b = Benchmark(log, "Item-Inflate", 40L) val rv = verticalLayout { // トップレベルのViewGroupのlparamsはイニシャライザ内部に置くしかないみたい val lp = parent.generateLayoutParamsEx() @@ -557,723 +1286,13 @@ class ColumnViewHolder( } layoutParams = lp } - - llColumnHeader = customView { - lparams(matchParent, wrapContent) - - orientation = LinearLayout.VERTICAL - - background = ContextCompat.getDrawable(context, R.drawable.bg_column_header) - startPadding = dip(12) - endPadding = dip(12) - topPadding = dip(3) - bottomPadding = dip(3) - - linearLayout { - lparams(matchParent, wrapContent) - gravity = Gravity.BOTTOM - - tvColumnContext = textView { - gravity = Gravity.END - startPadding = dip(4) - endPadding = dip(4) - textColor = context.attrColor(R.attr.colorColumnHeaderAcct) - textSize = 12f - }.lparams(0, wrapContent) { - weight = 1f - } - - tvColumnStatus = textView { - gravity = Gravity.END - textColor = context.attrColor(R.attr.colorColumnHeaderPageNumber) - textSize = 12f - }.lparams(wrapContent, wrapContent) { - marginStart = dip(12) - } - - tvColumnIndex = textView { - gravity = Gravity.END - textColor = context.attrColor(R.attr.colorColumnHeaderPageNumber) - textSize = 12f - }.lparams(wrapContent, wrapContent) { - marginStart = dip(4) - } - } - - linearLayout { - lparams(matchParent, wrapContent) { - topMargin = dip(0) - } - gravity = Gravity.CENTER_VERTICAL - isBaselineAligned = false - - ivColumnIcon = imageView { - importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_NO - scaleType = ImageView.ScaleType.FIT_CENTER - }.lparams(dip(32), dip(32)) { - endMargin = dip(4) - } - - tvColumnName = textView { - // Kannada語の "ಸ್ಥಳೀಯ ಟೈಮ್ ಲೈನ್" の上下が途切れることがあるらしい - // GS10+では再現しなかった - }.lparams(dip(0), wrapContent) { - weight = 1f - } - - frameLayout { - lparams(wrapContent, wrapContent) { - gravity = Gravity.CENTER_VERTICAL - startMargin = dip(2) - } - clipChildren = false - - btnAnnouncements = imageButton { - background = ContextCompat.getDrawable( - context, - R.drawable.btn_bg_transparent_round6dp - ) - contentDescription = context.getString(R.string.announcements) - setImageResource(R.drawable.ic_info_outline) - padding = dip(8) - scaleType = ImageView.ScaleType.FIT_CENTER - - btnAnnouncementsCutout = Paint().apply { - isAntiAlias = true - } - val path = Path() - addOutsideDrawer(this) { canvas, parent, view, left, top -> - if (llAnnouncementsBox.visibility == View.VISIBLE) { - val viewW = view.width - val viewH = view.height - val triTopX = (left + viewW / 2).toFloat() - val triTopY = top.toFloat() + viewH * 0.75f - val triBottomLeft = left.toFloat() - val triBottomRight = (left + viewW).toFloat() - val triBottom = parent.height.toFloat() - path.reset() - path.moveTo(triTopX, triTopY) - path.lineTo(triBottomRight, triBottom) - path.lineTo(triBottomLeft, triBottom) - path.lineTo(triTopX, triTopY) - canvas.drawPath(path, btnAnnouncementsCutout) - } - } - }.lparams(dip(40), dip(40)) - - btnAnnouncementsBadge = imageView { - setImageResource(R.drawable.announcements_dot) - scaleType = ImageView.ScaleType.FIT_CENTER - }.lparams(dip(7), dip(7)) { - gravity = Gravity.END or Gravity.TOP - endMargin = dip(4) - topMargin = dip(4) - } - } - - frameLayout { - lparams(wrapContent, wrapContent) { - gravity = Gravity.CENTER_VERTICAL - startMargin = dip(2) - } - clipChildren = false - - btnColumnSetting = imageButton { - background = - ContextCompat.getDrawable( - context, - R.drawable.btn_bg_transparent_round6dp - ) - contentDescription = context.getString(R.string.setting) - setImageResource(R.drawable.ic_tune) - padding = dip(8) - scaleType = ImageView.ScaleType.FIT_CENTER - - val paint = Paint().apply { - isAntiAlias = true - color = - context.attrColor(R.attr.colorColumnSettingBackground) - } - val path = Path() - addOutsideDrawer(this) { canvas, parent, view, left, top -> - if (llColumnSetting.visibility == View.VISIBLE) { - val viewW = view.width - val viewH = view.height - val triTopX = (left + viewW / 2).toFloat() - val triTopY = top.toFloat() + viewH * 0.75f - val triBottomLeft = left.toFloat() - val triBottomRight = (left + viewW).toFloat() - val triBottom = parent.height.toFloat() - path.reset() - path.moveTo(triTopX, triTopY) - path.lineTo(triBottomRight, triBottom) - path.lineTo(triBottomLeft, triBottom) - path.lineTo(triTopX, triTopY) - canvas.drawPath(path, paint) - } - } - }.lparams(dip(40), dip(40)) - } - - btnColumnReload = imageButton { - background = - ContextCompat.getDrawable( - context, - R.drawable.btn_bg_transparent_round6dp - ) - contentDescription = context.getString(R.string.reload) - setImageResource(R.drawable.ic_refresh) - padding = dip(8) - scaleType = ImageView.ScaleType.FIT_CENTER - }.lparams(dip(40), dip(40)) { - gravity = Gravity.CENTER_VERTICAL - startMargin = dip(2) - } - - btnColumnClose = imageButton { - background = ContextCompat.getDrawable( - context, - R.drawable.btn_bg_transparent_round6dp - ) - contentDescription = context.getString(R.string.close_column) - setImageResource(R.drawable.ic_close) - padding = dip(8) - scaleType = ImageView.ScaleType.FIT_CENTER - }.lparams(dip(40), dip(40)) { - gravity = Gravity.CENTER_VERTICAL - startMargin = dip(2) - } - } - } // end of column header - - llColumnSetting = maxHeightScrollView { - lparams(matchParent, wrapContent) - isScrollbarFadingEnabled = false - maxHeight = dip(240) - - backgroundColor = - context.attrColor(R.attr.colorColumnSettingBackground) - - llColumnSettingInside = verticalLayout { - lparams(matchParent, wrapContent) - - startPadding = dip(12) - endPadding = dip(12) - topPadding = dip(3) - bottomPadding = dip(3) - - llHashtagExtra = verticalLayout { - lparams(matchParent, wrapContent) - - label = textView { - textColor = - context.attrColor(R.attr.colorColumnHeaderPageNumber) - text = context.getString(R.string.hashtag_extra_any) - }.lparams(matchParent, wrapContent) - - etHashtagExtraAny = editText { - id = View.generateViewId() - inputType = InputType.TYPE_CLASS_TEXT - maxLines = 1 - setHorizontallyScrolling(true) - isHorizontalScrollBarEnabled = true - }.lparams(matchParent, wrapContent) - label?.labelFor = etHashtagExtraAny.id - - label = textView { - textColor = - context.attrColor(R.attr.colorColumnHeaderPageNumber) - text = context.getString(R.string.hashtag_extra_all) - }.lparams(matchParent, wrapContent) - - etHashtagExtraAll = editText { - id = View.generateViewId() - inputType = InputType.TYPE_CLASS_TEXT - maxLines = 1 - setHorizontallyScrolling(true) - isHorizontalScrollBarEnabled = true - }.lparams(matchParent, wrapContent) - label?.labelFor = etHashtagExtraAll.id - - label = textView { - textColor = - context.attrColor(R.attr.colorColumnHeaderPageNumber) - text = context.getString(R.string.hashtag_extra_none) - }.lparams(matchParent, wrapContent) - - etHashtagExtraNone = editText { - id = View.generateViewId() - inputType = InputType.TYPE_CLASS_TEXT - maxLines = 1 - setHorizontallyScrolling(true) - isHorizontalScrollBarEnabled = true - }.lparams(matchParent, wrapContent) - label?.labelFor = etHashtagExtraNone.id - } // end of hashtag extra - - cbDontCloseColumn = checkBox { - text = context.getString(R.string.dont_close_column) - }.lparams(matchParent, wrapContent) - - cbRemoteOnly = checkBox { - text = context.getString(R.string.remote_only) - }.lparams(matchParent, wrapContent) - - cbWithAttachment = checkBox { - text = context.getString(R.string.with_attachment) - }.lparams(matchParent, wrapContent) - - cbWithHighlight = checkBox { - text = context.getString(R.string.with_highlight) - }.lparams(matchParent, wrapContent) - - cbDontShowBoost = checkBox { - text = context.getString(R.string.dont_show_boost) - }.lparams(matchParent, wrapContent) - - cbDontShowFavourite = checkBox { - text = context.getString(R.string.dont_show_favourite) - }.lparams(matchParent, wrapContent) - - cbDontShowFollow = checkBox { - text = context.getString(R.string.dont_show_follow) - }.lparams(matchParent, wrapContent) - - cbDontShowReply = checkBox { - text = context.getString(R.string.dont_show_reply) - }.lparams(matchParent, wrapContent) - - cbDontShowReaction = checkBox { - text = context.getString(R.string.dont_show_reaction) - }.lparams(matchParent, wrapContent) - - cbDontShowVote = checkBox { - text = context.getString(R.string.dont_show_vote) - }.lparams(matchParent, wrapContent) - - cbDontShowNormalToot = checkBox { - text = context.getString(R.string.dont_show_normal_toot) - }.lparams(matchParent, wrapContent) - - cbDontShowNonPublicToot = checkBox { - text = context.getString(R.string.dont_show_non_public_toot) - }.lparams(matchParent, wrapContent) - - cbInstanceLocal = checkBox { - text = context.getString(R.string.instance_local) - }.lparams(matchParent, wrapContent) - - cbDontStreaming = checkBox { - text = context.getString(R.string.dont_use_streaming_api) - }.lparams(matchParent, wrapContent) - - cbDontAutoRefresh = checkBox { - text = context.getString(R.string.dont_refresh_on_activity_resume) - }.lparams(matchParent, wrapContent) - - cbHideMediaDefault = checkBox { - text = context.getString(R.string.hide_media_default) - }.lparams(matchParent, wrapContent) - - cbSystemNotificationNotRelated = checkBox { - text = context.getString(R.string.system_notification_not_related) - }.lparams(matchParent, wrapContent) - - cbEnableSpeech = checkBox { - text = context.getString(R.string.enable_speech) - }.lparams(matchParent, wrapContent) - - cbOldApi = checkBox { - text = context.getString(R.string.use_old_api) - }.lparams(matchParent, wrapContent) - - llRegexFilter = linearLayout { - lparams(matchParent, wrapContent) - - label = textView { - textColor = - context.attrColor(R.attr.colorColumnHeaderPageNumber) - text = context.getString(R.string.regex_filter) - }.lparams(wrapContent, wrapContent) - - tvRegexFilterError = textView { - textColor = context.attrColor(R.attr.colorRegexFilterError) - }.lparams(0, wrapContent) { - weight = 1f - startMargin = dip(4) - } - } - - etRegexFilter = editText { - id = View.generateViewId() - inputType = InputType.TYPE_CLASS_TEXT - maxLines = 1 - setHorizontallyScrolling(true) - isHorizontalScrollBarEnabled = true - }.lparams(matchParent, wrapContent) - - label?.labelFor = etRegexFilter.id - - btnDeleteNotification = button { - isAllCaps = false - text = context.getString(R.string.notification_delete) - }.lparams(matchParent, wrapContent) - - btnColor = button { - isAllCaps = false - text = context.getString(R.string.color_and_background) - }.lparams(matchParent, wrapContent) - - btnLanguageFilter = button { - isAllCaps = false - text = context.getString(R.string.language_filter) - }.lparams(matchParent, wrapContent) - } - } // end of column setting scroll view - - llAnnouncementsBox = verticalLayout { - lparams(matchParent, wrapContent) { - startMargin = dip(6) - endMargin = dip(6) - bottomMargin = dip(2) - } - - val buttonHeight = ActMain.boostButtonSize - val paddingH = (buttonHeight.toFloat() * 0.1f + 0.5f).toInt() - val paddingV = (buttonHeight.toFloat() * 0.1f + 0.5f).toInt() - - linearLayout { - lparams(matchParent, wrapContent) - val padLr = dip(6) - setPadding(padLr, 0, padLr, 0) - - background = ContextCompat.getDrawable( - context, - R.drawable.btn_bg_transparent_round6dp - ) - - gravity = Gravity.CENTER_VERTICAL or Gravity.END - - tvAnnouncementsCaption = textView { - gravity = Gravity.END or Gravity.CENTER_VERTICAL - text = context.getString(R.string.announcements) - }.lparams(0, wrapContent) { - weight = 1f - } - - btnAnnouncementsPrev = imageButton { - background = ContextCompat.getDrawable( - context, - R.drawable.btn_bg_transparent_round6dp - ) - contentDescription = context.getString(R.string.previous) - imageResource = R.drawable.ic_arrow_start - setPadding(paddingH, paddingV, paddingH, paddingV) - scaleType = ImageView.ScaleType.FIT_CENTER - }.lparams(buttonHeight, buttonHeight) { - marginStart = dip(4) - } - - tvAnnouncementsIndex = textView { - }.lparams(wrapContent, wrapContent) { - marginStart = dip(4) - } - - btnAnnouncementsNext = imageButton { - background = ContextCompat.getDrawable( - context, - R.drawable.btn_bg_transparent_round6dp - ) - contentDescription = context.getString(R.string.next) - imageResource = R.drawable.ic_arrow_end - setPadding(paddingH, paddingV, paddingH, paddingV) - scaleType = ImageView.ScaleType.FIT_CENTER - }.lparams(buttonHeight, buttonHeight) { - marginStart = dip(4) - } - } - - llAnnouncements = maxHeightScrollView { - lparams(matchParent, wrapContent) { - topMargin = dip(1) - } - - val padLr = dip(6) - val padTb = dip(2) - setPadding(padLr, padTb, padLr, padTb) - - scrollBarStyle = View.SCROLLBARS_OUTSIDE_OVERLAY - isScrollbarFadingEnabled = false - - maxHeight = dip(240) - - verticalLayout { - lparams(matchParent, wrapContent) - - // 期間があれば表示する - tvAnnouncementPeriod = textView { - gravity = Gravity.END - }.lparams(matchParent, wrapContent) { - bottomMargin = dip(3) - } - - tvAnnouncementContent = myTextView { - setLineSpacing(lineSpacingExtra, 1.1f) - // tools:text="Contents\nContents" - }.lparams(matchParent, wrapContent) { - topMargin = dip(3) - } - - llAnnouncementExtra = verticalLayout { - lparams(matchParent, wrapContent) { - topMargin = dip(3) - } - } - } - } - } - - llSearch = verticalLayout { - lparams(matchParent, wrapContent) - - linearLayout { - lparams(matchParent, wrapContent) - isBaselineAligned = false - gravity = Gravity.CENTER - - etSearch = editText { - id = View.generateViewId() - imeOptions = EditorInfo.IME_ACTION_SEARCH - inputType = InputType.TYPE_CLASS_TEXT - maxLines = 1 - }.lparams(0, wrapContent) { - weight = 1f - } - - flEmoji = flexboxLayout { - flexWrap = FlexWrap.WRAP - justifyContent = JustifyContent.FLEX_START - }.lparams(0, wrapContent) { - weight = 1f - } - - btnEmojiAdd = imageButton { - backgroundResource = R.drawable.btn_bg_transparent_round6dp - contentDescription = context.getString(R.string.add) - imageResource = R.drawable.ic_add - imageTintList = ColorStateList.valueOf( - context.attrColor(R.attr.colorVectorDrawable) - ) - }.lparams(dip(40), dip(40)) { - startMargin = dip(4) - } - - btnSearchClear = imageButton { - backgroundResource = R.drawable.btn_bg_transparent_round6dp - contentDescription = context.getString(R.string.clear) - imageResource = R.drawable.ic_close - imageTintList = ColorStateList.valueOf( - context.attrColor(R.attr.colorVectorDrawable) - ) - }.lparams(dip(40), dip(40)) { - startMargin = dip(4) - } - - btnSearch = imageButton { - backgroundResource = R.drawable.btn_bg_transparent_round6dp - contentDescription = context.getString(R.string.search) - imageResource = R.drawable.ic_search - imageTintList = ColorStateList.valueOf( - context.attrColor(R.attr.colorVectorDrawable) - ) - }.lparams(dip(40), dip(40)) { - startMargin = dip(4) - } - } - - cbResolve = checkBox { - text = context.getString(R.string.resolve_non_local_account) - }.lparams(wrapContent, wrapContent) // チェックボックスの余白はタッチ判定外 - - tvEmojiDesc = textView { - text = context.getString(R.string.long_tap_to_delete) - textColor = context.attrColor(R.attr.colorColumnHeaderPageNumber) - textSize = 12f - }.lparams(wrapContent, wrapContent) - } // end of search bar - - llListList = linearLayout { - lparams(matchParent, wrapContent) - - isBaselineAligned = false - gravity = Gravity.CENTER - - etListName = editText { - hint = context.getString(R.string.list_create_hint) - imeOptions = EditorInfo.IME_ACTION_SEND - inputType = InputType.TYPE_CLASS_TEXT - }.lparams(0, wrapContent) { - weight = 1f - } - - btnListAdd = imageButton { - backgroundResource = R.drawable.btn_bg_transparent_round6dp - contentDescription = context.getString(R.string.add) - imageResource = R.drawable.ic_add - imageTintList = ColorStateList.valueOf( - context.attrColor( - R.attr.colorVectorDrawable - ) - ) - }.lparams(dip(40), dip(40)) { - startMargin = dip(4) - } - } // end of list list header - - svQuickFilter = horizontalScrollView { - lparams(matchParent, wrapContent) - isFillViewport = true - linearLayout { - lparams(matchParent, dip(40)) - - btnQuickFilterAll = button { - backgroundResource = R.drawable.btn_bg_transparent_round6dp - minWidthCompat = dip(40) - startPadding = dip(4) - endPadding = dip(4) - isAllCaps = false - stateListAnimator = null - text = context.getString(R.string.all) - }.lparams(wrapContent, matchParent) { - margin = 0 - } - - btnQuickFilterMention = imageButton { - backgroundResource = R.drawable.btn_bg_transparent_round6dp - contentDescription = context.getString(R.string.mention2) - }.lparams(dip(40), matchParent) { - margin = 0 - } - - btnQuickFilterFavourite = imageButton { - backgroundResource = R.drawable.btn_bg_transparent_round6dp - contentDescription = context.getString(R.string.favourite) - }.lparams(dip(40), matchParent) { - margin = 0 - } - - btnQuickFilterBoost = imageButton { - backgroundResource = R.drawable.btn_bg_transparent_round6dp - contentDescription = context.getString(R.string.boost) - }.lparams(dip(40), matchParent) { - margin = 0 - } - - btnQuickFilterFollow = imageButton { - backgroundResource = R.drawable.btn_bg_transparent_round6dp - contentDescription = context.getString(R.string.follow) - }.lparams(dip(40), matchParent) { - margin = 0 - } - - btnQuickFilterPost = imageButton { - backgroundResource = R.drawable.btn_bg_transparent_round6dp - contentDescription = context.getString(R.string.notification_type_post) - }.lparams(dip(40), matchParent) { - margin = 0 - } - - btnQuickFilterReaction = imageButton { - backgroundResource = R.drawable.btn_bg_transparent_round6dp - contentDescription = context.getString(R.string.reaction) - }.lparams(dip(40), matchParent) { - margin = 0 - } - - btnQuickFilterVote = imageButton { - backgroundResource = R.drawable.btn_bg_transparent_round6dp - contentDescription = context.getString(R.string.vote_polls) - }.lparams(dip(40), matchParent) { - margin = 0 - } - } - } // end of notification quick filter bar - - flColumnBackground = frameLayout { - - ivColumnBackgroundImage = imageView { - importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_NO - scaleType = ImageView.ScaleType.CENTER_CROP - visibility = View.GONE - }.lparams(matchParent, matchParent) - - llLoading = verticalLayout { - lparams(matchParent, matchParent) - - isBaselineAligned = false - - gravity = Gravity.CENTER - - tvLoading = textView { - gravity = Gravity.CENTER - }.lparams(matchParent, wrapContent) - - btnConfirmMail = button { - text = activity.getString(R.string.resend_confirm_mail) - background = ContextCompat.getDrawable( - activity, - R.drawable.btn_bg_transparent_round6dp - ) - }.lparams(matchParent, wrapContent) { - topMargin = dip(8) - } - } - - refreshLayout = swipyRefreshLayout { - lparams(matchParent, matchParent) - - direction = SwipyRefreshLayoutDirection.BOTH - - // スタイルで指定しないとAndroid 6 で落ちる… - listView = recyclerView { - listLayoutManager = LinearLayoutManager(activity) - layoutManager = listLayoutManager - }.lparams(matchParent, matchParent) { - } - } - - llRefreshError = frameLayout { - - foregroundGravity = Gravity.BOTTOM - backgroundResource = R.drawable.bg_refresh_error - - startPadding = dip(6) - endPadding = dip(6) - topPadding = dip(3) - bottomPadding = dip(3) - - ivRefreshError = imageView { - importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_NO - scaleType = ImageView.ScaleType.FIT_CENTER - imageResource = R.drawable.ic_error - imageTintList = ColorStateList.valueOf(Color.RED) - }.lparams(dip(24), dip(24)) { - gravity = Gravity.START or Gravity.CENTER_VERTICAL - startMargin = dip(4) - } - - tvRefreshError = textView { - textColor = Color.WHITE - }.lparams(matchParent, wrapContent) { - gravity = Gravity.TOP or Gravity.START - startMargin = dip(32) - } - }.lparams(matchParent, wrapContent) { - margin = dip(6) - } - }.lparams(matchParent, 0) { - weight = 1f - } + inflateColumnHeader() + inflateColumnSetting() + inflateAnnouncementsBox() + inflateSearchBar() + inflateListBar() + inflateQuickFilter() + inflateColumnBody(actMain) } b.report() rv diff --git a/app/src/main/java/jp/juggler/subwaytooter/ColumnViewHolderAnnouncements.kt b/app/src/main/java/jp/juggler/subwaytooter/ColumnViewHolderAnnouncements.kt index 9b99e332..7e127a92 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/ColumnViewHolderAnnouncements.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/ColumnViewHolderAnnouncements.kt @@ -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() - ?.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, + 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() + ?.endMargin = 0 +} + fun ColumnViewHolder.reactionAdd(item: TootAnnouncement, sample: TootReaction?) { val column = column ?: return if (sample == null) { diff --git a/app/src/main/java/jp/juggler/subwaytooter/ColumnViewHolderLifecycle.kt b/app/src/main/java/jp/juggler/subwaytooter/ColumnViewHolderLifecycle.kt index 08fe5c27..9e9be7a1 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/ColumnViewHolderLifecycle.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/ColumnViewHolderLifecycle.kt @@ -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 { diff --git a/app/src/main/java/jp/juggler/subwaytooter/ColumnViewHolderQuickFilter.kt b/app/src/main/java/jp/juggler/subwaytooter/ColumnViewHolderQuickFilter.kt index 15bcadfc..ef0ad8a5 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/ColumnViewHolderQuickFilter.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/ColumnViewHolderQuickFilter.kt @@ -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 diff --git a/app/src/main/java/jp/juggler/subwaytooter/ColumnViewHolderShow.kt b/app/src/main/java/jp/juggler/subwaytooter/ColumnViewHolderShow.kt index af1b3689..94d026c8 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/ColumnViewHolderShow.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/ColumnViewHolderShow.kt @@ -138,7 +138,7 @@ internal fun ColumnViewHolder.showContent( refreshLayout.isRefreshing = false showRefreshError() } - procRestorescrollposition.run() + procRestoreScrollPosition.run() } fun ColumnViewHolder.showColumnSetting(show: Boolean): Boolean { diff --git a/app/src/main/java/jp/juggler/subwaytooter/DlgContextMenu.kt b/app/src/main/java/jp/juggler/subwaytooter/DlgContextMenu.kt index a2f11a55..a9278936 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/DlgContextMenu.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/DlgContextMenu.kt @@ -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) diff --git a/app/src/main/java/jp/juggler/subwaytooter/ItemViewHolder.kt b/app/src/main/java/jp/juggler/subwaytooter/ItemViewHolder.kt index f26f825c..8b0147d5 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/ItemViewHolder.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/ItemViewHolder.kt @@ -64,7 +64,7 @@ class ItemViewHolder( lateinit var ivFollowedBy: ImageView lateinit var llStatus: View - lateinit var ivThumbnail: MyNetworkImageView + lateinit var ivAvatar: MyNetworkImageView lateinit var tvName: TextView lateinit var tvTime: TextView lateinit var tvAcct: TextView @@ -189,7 +189,7 @@ class ItemViewHolder( ivCardImage, btnCardImageHide, btnCardImageShow, - ivThumbnail, + ivAvatar, llBoosted, llReply, llFollow, @@ -211,7 +211,7 @@ class ItemViewHolder( llReply, llFollow, llConversationIcons, - ivThumbnail, + ivAvatar, llTrendTag )) { v.setOnLongClickListener(this) @@ -290,8 +290,8 @@ class ItemViewHolder( } var s = activity.avatarIconSize - ivThumbnail.layoutParams.height = s - ivThumbnail.layoutParams.width = s + ivAvatar.layoutParams.height = s + ivAvatar.layoutParams.width = s ivFollow.layoutParams.width = s ivBoosted.layoutParams.width = s @@ -343,7 +343,605 @@ class ItemViewHolder( ///////////////////////////////////////////////////////////////////// - fun inflate(activity: ActMain) = with(activity.UI {}) { + private fun _LinearLayout.inflateBoosted() { + llBoosted = linearLayout { + lparams(matchParent, wrapContent) { + bottomMargin = dip(6) + } + backgroundResource = R.drawable.btn_bg_transparent_round6dp + gravity = Gravity.CENTER_VERTICAL + + ivBoosted = imageView { + scaleType = ImageView.ScaleType.FIT_END + importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_NO + }.lparams(dip(48), dip(32)) { + endMargin = dip(4) + } + + verticalLayout { + lparams(dip(0), wrapContent) { + weight = 1f + } + + linearLayout { + lparams(matchParent, wrapContent) + + tvBoostedAcct = textView { + ellipsize = TextUtils.TruncateAt.END + gravity = Gravity.END + maxLines = 1 + textSize = 12f // textSize の単位はSP + // tools:text ="who@hoge" + }.lparams(dip(0), wrapContent) { + weight = 1f + } + + tvBoostedTime = textView { + + startPadding = dip(2) + + gravity = Gravity.END + textSize = 12f // textSize の単位はSP + // tools:ignore="RtlSymmetry" + // tools:text="2017-04-16 09:37:14" + }.lparams(wrapContent, wrapContent) + } + + tvBoosted = textView { + // tools:text = "~にブーストされました" + }.lparams(matchParent, wrapContent) + } + } + } + + private fun _LinearLayout.inflateFollowed() { + llFollow = linearLayout { + lparams(matchParent, wrapContent) + + background = + ContextCompat.getDrawable(context, R.drawable.btn_bg_transparent_round6dp) + gravity = Gravity.CENTER_VERTICAL + + ivFollow = myNetworkImageView { + contentDescription = context.getString(R.string.thumbnail) + scaleType = ImageView.ScaleType.FIT_END + }.lparams(dip(48), dip(40)) { + endMargin = dip(4) + } + + verticalLayout { + + lparams(dip(0), wrapContent) { + weight = 1f + } + + tvFollowerName = textView { + // tools:text="Follower Name" + }.lparams(matchParent, wrapContent) + + tvFollowerAcct = textView { + setPaddingStartEnd(dip(4), dip(4)) + textSize = 12f // SP + }.lparams(matchParent, wrapContent) + + tvLastStatusAt = myTextView { + setPaddingStartEnd(dip(4), dip(4)) + textSize = 12f // SP + }.lparams(matchParent, wrapContent) + } + + frameLayout { + lparams(dip(40), dip(40)) { + startMargin = dip(4) + } + + btnFollow = imageButton { + background = + ContextCompat.getDrawable( + context, + R.drawable.btn_bg_transparent_round6dp + ) + contentDescription = context.getString(R.string.follow) + scaleType = ImageView.ScaleType.CENTER + // tools:src="?attr/ic_follow_plus" + }.lparams(matchParent, matchParent) + + ivFollowedBy = imageView { + scaleType = ImageView.ScaleType.CENTER + // tools:src="?attr/ic_followed_by" + importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_NO + }.lparams(matchParent, matchParent) + } + } + } + + private fun _LinearLayout.inflateVerticalMediaOne(thumbnailHeight: Int) = + myNetworkImageView { + background = ContextCompat.getDrawable(context, R.drawable.bg_thumbnail) + contentDescription = context.getString(R.string.thumbnail) + scaleType = ImageView.ScaleType.CENTER_CROP + }.lparams(matchParent, thumbnailHeight) { + topMargin = dip(3) + } + + private fun _LinearLayout.inflateVerticalMedia(thumbnailHeight: Int) = + frameLayout { + lparams(matchParent, wrapContent) { + topMargin = dip(3) + } + llMedia = verticalLayout { + lparams(matchParent, matchParent) + + btnHideMedia = imageButton { + background = + ContextCompat.getDrawable(context, R.drawable.btn_bg_transparent_round6dp) + contentDescription = context.getString(R.string.hide) + imageResource = R.drawable.ic_close + }.lparams(dip(32), dip(32)) { + gravity = Gravity.END + } + ivMedia1 = inflateVerticalMediaOne(thumbnailHeight) + ivMedia2 = inflateVerticalMediaOne(thumbnailHeight) + ivMedia3 = inflateVerticalMediaOne(thumbnailHeight) + ivMedia4 = inflateVerticalMediaOne(thumbnailHeight) + } + + btnShowMedia = blurhashView { + errorColor = context.attrColor(R.attr.colorShowMediaBackground) + gravity = Gravity.CENTER + textColor = context.attrColor(R.attr.colorShowMediaText) + minHeightCompat = dip(48) + }.lparams(matchParent, thumbnailHeight) + } + + private fun _LinearLayout.inflateHorizontalMediaOne(trail: Boolean) = + myNetworkImageView { + background = ContextCompat.getDrawable(context, R.drawable.bg_thumbnail) + contentDescription = context.getString(R.string.thumbnail) + scaleType = ImageView.ScaleType.CENTER_CROP + }.lparams(0, matchParent) { + weight = 1f + if (trail) startMargin = dip(8) + } + + private fun _LinearLayout.inflateHorizontalMedia(thumbnailHeight: Int): View { + return frameLayout { + lparams(matchParent, thumbnailHeight) { topMargin = dip(3) } + llMedia = linearLayout { + lparams(matchParent, matchParent) + ivMedia1 = inflateHorizontalMediaOne(trail = false) + ivMedia2 = inflateHorizontalMediaOne(trail = true) + ivMedia3 = inflateHorizontalMediaOne(trail = true) + ivMedia4 = inflateHorizontalMediaOne(trail = true) + btnHideMedia = imageButton { + background = + ContextCompat.getDrawable(context, R.drawable.btn_bg_transparent_round6dp) + contentDescription = context.getString(R.string.hide) + imageResource = R.drawable.ic_close + }.lparams(dip(32), matchParent) { + startMargin = dip(8) + } + } + + btnShowMedia = blurhashView { + errorColor = context.attrColor(R.attr.colorShowMediaBackground) + textColor = context.attrColor(R.attr.colorShowMediaText) + gravity = Gravity.CENTER + }.lparams(matchParent, matchParent) + } + } + + private fun _LinearLayout.inflateCard(actMain: ActMain) { + llCardOuter = verticalLayout { + lparams(matchParent, wrapContent) { + topMargin = dip(3) + startMargin = dip(12) + endMargin = dip(6) + } + padding = dip(3) + bottomPadding = dip(6) + + background = PreviewCardBorder() + + tvCardText = myTextView { + }.lparams(matchParent, wrapContent) { + } + + flCardImage = frameLayout { + lparams(matchParent, actMain.appState.mediaThumbHeight) { + topMargin = dip(3) + } + + llCardImage = linearLayout { + lparams(matchParent, matchParent) + + ivCardImage = myNetworkImageView { + contentDescription = context.getString(R.string.thumbnail) + scaleType = when { + PrefB.bpDontCropMediaThumb(App1.pref) -> ImageView.ScaleType.FIT_CENTER + else -> ImageView.ScaleType.CENTER_CROP + } + }.lparams(0, matchParent) { + weight = 1f + } + btnCardImageHide = imageButton { + background = + ContextCompat.getDrawable(context, R.drawable.btn_bg_transparent_round6dp) + contentDescription = context.getString(R.string.hide) + imageResource = R.drawable.ic_close + }.lparams(dip(32), matchParent) { + startMargin = dip(4) + } + } + + btnCardImageShow = blurhashView { + errorColor = context.attrColor(R.attr.colorShowMediaBackground) + textColor = context.attrColor(R.attr.colorShowMediaText) + gravity = Gravity.CENTER + }.lparams(matchParent, matchParent) + } + } + } + + private fun _LinearLayout.inflateStatusReplyInfo() { + llReply = linearLayout { + lparams(matchParent, wrapContent) { + bottomMargin = dip(3) + } + + background = + ContextCompat.getDrawable(context, R.drawable.btn_bg_transparent_round6dp) + gravity = Gravity.CENTER_VERTICAL + + ivReply = imageView { + scaleType = ImageView.ScaleType.FIT_END + importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_NO + padding = dip(4) + }.lparams(dip(32), dip(32)) { + endMargin = dip(4) + } + + tvReply = textView { + }.lparams(dip(0), wrapContent) { + weight = 1f + } + } + } + + private fun _LinearLayout.inflateStatusContentWarning() { + llContentWarning = linearLayout { + lparams(matchParent, wrapContent) { + topMargin = dip(3) + isBaselineAligned = false + } + gravity = Gravity.CENTER_VERTICAL + + btnContentWarning = button { + backgroundDrawable = + ContextCompat.getDrawable(context, R.drawable.bg_button_cw) + minWidthCompat = dip(40) + padding = dip(4) + //tools:text="見る" + }.lparams(wrapContent, dip(40)) { + endMargin = dip(8) + } + + verticalLayout { + lparams(dip(0), wrapContent) { + weight = 1f + } + + tvMentions = myTextView {}.lparams(matchParent, wrapContent) + + tvContentWarning = myTextView { + }.lparams(matchParent, wrapContent) { + topMargin = dip(3) + } + } + } + } + + private fun _LinearLayout.inflateStatusContents(actMain: ActMain) { + llContents = verticalLayout { + lparams(matchParent, wrapContent) + + tvContent = myTextView { + setLineSpacing(lineSpacingExtra, 1.1f) + // tools:text="Contents\nContents" + }.lparams(matchParent, wrapContent) { + topMargin = dip(3) + } + + val thumbnailHeight = actMain.appState.mediaThumbHeight + flMedia = when (PrefB.bpVerticalArrangeThumbnails(actMain.pref)) { + true -> inflateVerticalMedia(thumbnailHeight) + else -> inflateHorizontalMedia(thumbnailHeight) + } + + tvMediaDescription = textView {}.lparams(matchParent, wrapContent) + + inflateCard(actMain) + + llExtra = verticalLayout { + lparams(matchParent, wrapContent) { + topMargin = dip(0) + } + } + } + } + + private fun _LinearLayout.inflateStatusButtons(actMain: ActMain) { + // button bar + statusButtonsViewHolder = StatusButtonsViewHolder( + actMain, + matchParent, + 3f, + justifyContent = when (PrefI.ipBoostButtonJustify(App1.pref)) { + 0 -> JustifyContent.FLEX_START + 1 -> JustifyContent.CENTER + else -> JustifyContent.FLEX_END + } + ) + llButtonBar = statusButtonsViewHolder.viewRoot + addView(llButtonBar) + } + + private fun _LinearLayout.inflateOpenSticker() { + + llOpenSticker = linearLayout { + lparams(matchParent, wrapContent) + + ivOpenSticker = myNetworkImageView { + }.lparams(dip(16), dip(16)) { + isBaselineAligned = false + } + + tvOpenSticker = textView { + setTextSize(TypedValue.COMPLEX_UNIT_DIP, 10f) + gravity = Gravity.CENTER_VERTICAL + setPaddingStartEnd(dip(4f), dip(4f)) + }.lparams(0, dip(16)) { + isBaselineAligned = false + weight = 1f + } + } + } + + private fun _LinearLayout.inflateStatusAcctTime() { + linearLayout { + lparams(matchParent, wrapContent) + tvAcct = textView { + ellipsize = TextUtils.TruncateAt.END + gravity = Gravity.END + maxLines = 1 + textSize = 12f // SP + // tools:text="who@hoge" + }.lparams(dip(0), wrapContent) { + weight = 1f + } + + tvTime = textView { + gravity = Gravity.END + startPadding = dip(2) + textSize = 12f // SP + // tools:ignore="RtlSymmetry" + // tools:text="2017-04-16 09:37:14" + }.lparams(wrapContent, wrapContent) + } + } + + private fun _LinearLayout.inflateStatusAvatar() { + ivAvatar = myNetworkImageView { + background = + ContextCompat.getDrawable(context, R.drawable.btn_bg_transparent_round6dp) + contentDescription = context.getString(R.string.thumbnail) + scaleType = ImageView.ScaleType.CENTER_CROP + }.lparams(dip(48), dip(48)) { + topMargin = dip(4) + endMargin = dip(4) + } + } + + private fun _LinearLayout.inflateStatus(actMain: ActMain) { + llStatus = verticalLayout { + lparams(matchParent, wrapContent) + + inflateStatusAcctTime() + + // horizontal split : avatar and other + linearLayout { + lparams(matchParent, wrapContent) + inflateStatusAvatar() + verticalLayout { + lparams(0, wrapContent) { weight = 1f } + + tvName = textView { + }.lparams(matchParent, wrapContent) + + inflateOpenSticker() + inflateStatusReplyInfo() + inflateStatusContentWarning() + inflateStatusContents(actMain) + inflateStatusButtons(actMain) + + tvApplication = textView { + gravity = Gravity.END + }.lparams(matchParent, wrapContent) + } + } + } + } + + fun _LinearLayout.inflateConversationIconOne() = + myNetworkImageView { + scaleType = ImageView.ScaleType.CENTER_CROP + }.lparams(dip(24), dip(24)) { + endMargin = dip(3) + } + + private fun _LinearLayout.inflateConversationIcons() { + llConversationIcons = linearLayout { + lparams(matchParent, dip(40)) + + isBaselineAligned = false + gravity = Gravity.START or Gravity.CENTER_VERTICAL + + tvConversationParticipants = textView { + text = context.getString(R.string.participants) + }.lparams(wrapContent, wrapContent) { + endMargin = dip(3) + } + + ivConversationIcon1 = inflateConversationIconOne() + ivConversationIcon2 = inflateConversationIconOne() + ivConversationIcon3 = inflateConversationIconOne() + ivConversationIcon4 = inflateConversationIconOne() + + tvConversationIconsMore = textView {}.lparams(wrapContent, wrapContent) + } + } + + private fun _LinearLayout.inflateSearchTag() { + llSearchTag = linearLayout { + lparams(matchParent, wrapContent) + + btnSearchTag = button { + background = + ContextCompat.getDrawable(context, R.drawable.btn_bg_transparent_round6dp) + allCaps = false + }.lparams(0, wrapContent) { + weight = 1f + } + + btnGapHead = imageButton { + background = ContextCompat.getDrawable( + context, + R.drawable.btn_bg_transparent_round6dp + ) + contentDescription = context.getString(R.string.read_gap_head) + imageResource = R.drawable.ic_arrow_drop_down + }.lparams(dip(32), matchParent) { + startMargin = dip(8) + } + + btnGapTail = imageButton { + background = ContextCompat.getDrawable( + context, + R.drawable.btn_bg_transparent_round6dp + ) + contentDescription = context.getString(R.string.read_gap_tail) + imageResource = R.drawable.ic_arrow_drop_up + }.lparams(dip(32), matchParent) { + startMargin = dip(8) + } + } + } + + private fun _LinearLayout.inflateTrendTag() { + llTrendTag = linearLayout { + lparams(matchParent, wrapContent) + + gravity = Gravity.CENTER_VERTICAL + background = + ContextCompat.getDrawable(context, R.drawable.btn_bg_transparent_round6dp) + + verticalLayout { + lparams(0, wrapContent) { + weight = 1f + } + + tvTrendTagName = textView {}.lparams(matchParent, wrapContent) + + tvTrendTagDesc = textView { + textSize = 12f // SP + }.lparams(matchParent, wrapContent) + } + tvTrendTagCount = textView {}.lparams(wrapContent, wrapContent) { + startMargin = dip(6) + endMargin = dip(6) + } + + cvTagHistory = trendTagHistoryView {}.lparams(dip(64), dip(32)) + } + } + + private fun _LinearLayout.inflateList() { + llList = linearLayout { + lparams(matchParent, wrapContent) + + gravity = Gravity.CENTER_VERTICAL + isBaselineAligned = false + minimumHeight = dip(40) + + btnListTL = button { + background = + ContextCompat.getDrawable(context, R.drawable.btn_bg_transparent_round6dp) + allCaps = false + }.lparams(0, wrapContent) { + weight = 1f + } + + btnListMore = imageButton { + background = + ContextCompat.getDrawable(context, R.drawable.btn_bg_transparent_round6dp) + imageResource = R.drawable.ic_more + contentDescription = context.getString(R.string.more) + }.lparams(dip(40), matchParent) { + startMargin = dip(4) + } + } + } + + private fun _LinearLayout.inflateMessageHolder() { + tvMessageHolder = textView { + padding = dip(4) + }.lparams(matchParent, wrapContent) + } + + private fun _LinearLayout.inflateFollowRequest() { + llFollowRequest = linearLayout { + lparams(matchParent, wrapContent) { + topMargin = dip(6) + } + gravity = Gravity.END + + btnFollowRequestAccept = imageButton { + background = + ContextCompat.getDrawable(context, R.drawable.btn_bg_transparent_round6dp) + contentDescription = context.getString(R.string.follow_accept) + imageResource = R.drawable.ic_check + setPadding(0, 0, 0, 0) + }.lparams(dip(48f), dip(32f)) + + btnFollowRequestDeny = imageButton { + background = + ContextCompat.getDrawable(context, R.drawable.btn_bg_transparent_round6dp) + contentDescription = context.getString(R.string.follow_deny) + imageResource = R.drawable.ic_close + setPadding(0, 0, 0, 0) + }.lparams(dip(48f), dip(32f)) { + startMargin = dip(4) + } + } + } + + private fun _LinearLayout.inflateFilter() { + llFilter = verticalLayout { + lparams(matchParent, wrapContent) { + } + minimumHeight = dip(40) + + tvFilterPhrase = textView { + typeface = Typeface.DEFAULT_BOLD + }.lparams(matchParent, wrapContent) + + tvFilterDetail = textView { + textSize = 12f // SP + }.lparams(matchParent, wrapContent) + } + } + + fun inflate(actMain: ActMain) = with(actMain.UI {}) { val b = Benchmark(log, "Item-Inflate", 40L) val rv = verticalLayout { // トップレベルのViewGroupのlparamsはイニシャライザ内部に置くしかないみたい @@ -360,634 +958,17 @@ class ItemViewHolder( descendantFocusability = ViewGroup.FOCUS_BLOCK_DESCENDANTS - llBoosted = linearLayout { - lparams(matchParent, wrapContent) { - bottomMargin = dip(6) - } - backgroundResource = R.drawable.btn_bg_transparent_round6dp - gravity = Gravity.CENTER_VERTICAL - - ivBoosted = imageView { - scaleType = ImageView.ScaleType.FIT_END - importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_NO - }.lparams(dip(48), dip(32)) { - endMargin = dip(4) - } - - verticalLayout { - lparams(dip(0), wrapContent) { - weight = 1f - } - - linearLayout { - lparams(matchParent, wrapContent) - - tvBoostedAcct = textView { - ellipsize = TextUtils.TruncateAt.END - gravity = Gravity.END - maxLines = 1 - textSize = 12f // textSize の単位はSP - // tools:text ="who@hoge" - }.lparams(dip(0), wrapContent) { - weight = 1f - } - - tvBoostedTime = textView { - - startPadding = dip(2) - - gravity = Gravity.END - textSize = 12f // textSize の単位はSP - // tools:ignore="RtlSymmetry" - // tools:text="2017-04-16 09:37:14" - }.lparams(wrapContent, wrapContent) - } - - tvBoosted = textView { - // tools:text = "~にブーストされました" - }.lparams(matchParent, wrapContent) - } - } - - llFollow = linearLayout { - lparams(matchParent, wrapContent) - - background = - ContextCompat.getDrawable(context, R.drawable.btn_bg_transparent_round6dp) - gravity = Gravity.CENTER_VERTICAL - - ivFollow = myNetworkImageView { - contentDescription = context.getString(R.string.thumbnail) - scaleType = ImageView.ScaleType.FIT_END - }.lparams(dip(48), dip(40)) { - endMargin = dip(4) - } - - verticalLayout { - - lparams(dip(0), wrapContent) { - weight = 1f - } - - tvFollowerName = textView { - // tools:text="Follower Name" - }.lparams(matchParent, wrapContent) - - tvFollowerAcct = textView { - setPaddingStartEnd(dip(4), dip(4)) - textSize = 12f // SP - }.lparams(matchParent, wrapContent) - - tvLastStatusAt = myTextView { - setPaddingStartEnd(dip(4), dip(4)) - textSize = 12f // SP - }.lparams(matchParent, wrapContent) - } - - frameLayout { - lparams(dip(40), dip(40)) { - startMargin = dip(4) - } - - btnFollow = imageButton { - background = - ContextCompat.getDrawable( - context, - R.drawable.btn_bg_transparent_round6dp - ) - contentDescription = context.getString(R.string.follow) - scaleType = ImageView.ScaleType.CENTER - // tools:src="?attr/ic_follow_plus" - }.lparams(matchParent, matchParent) - - ivFollowedBy = imageView { - scaleType = ImageView.ScaleType.CENTER - // tools:src="?attr/ic_followed_by" - importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_NO - }.lparams(matchParent, matchParent) - } - } - - llStatus = verticalLayout { - lparams(matchParent, wrapContent) - - linearLayout { - lparams(matchParent, wrapContent) - - tvAcct = textView { - ellipsize = TextUtils.TruncateAt.END - gravity = Gravity.END - maxLines = 1 - textSize = 12f // SP - // tools:text="who@hoge" - }.lparams(dip(0), wrapContent) { - weight = 1f - } - - tvTime = textView { - gravity = Gravity.END - startPadding = dip(2) - textSize = 12f // SP - // tools:ignore="RtlSymmetry" - // tools:text="2017-04-16 09:37:14" - }.lparams(wrapContent, wrapContent) - } - - linearLayout { - lparams(matchParent, wrapContent) - - ivThumbnail = myNetworkImageView { - background = - ContextCompat.getDrawable( - context, - R.drawable.btn_bg_transparent_round6dp - ) - contentDescription = context.getString(R.string.thumbnail) - scaleType = ImageView.ScaleType.CENTER_CROP - }.lparams(dip(48), dip(48)) { - topMargin = dip(4) - endMargin = dip(4) - } - - verticalLayout { - lparams(dip(0), wrapContent) { - weight = 1f - } - - tvName = textView { - }.lparams(matchParent, wrapContent) - - llOpenSticker = linearLayout { - lparams(matchParent, wrapContent) - - ivOpenSticker = myNetworkImageView { - }.lparams(dip(16), dip(16)) { - isBaselineAligned = false - } - - tvOpenSticker = textView { - setTextSize(TypedValue.COMPLEX_UNIT_DIP, 10f) - gravity = Gravity.CENTER_VERTICAL - setPaddingStartEnd(dip(4f), dip(4f)) - }.lparams(0, dip(16)) { - isBaselineAligned = false - weight = 1f - } - } - - llReply = linearLayout { - lparams(matchParent, wrapContent) { - bottomMargin = dip(3) - } - - background = - ContextCompat.getDrawable( - context, - R.drawable.btn_bg_transparent_round6dp - ) - gravity = Gravity.CENTER_VERTICAL - - ivReply = imageView { - scaleType = ImageView.ScaleType.FIT_END - importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_NO - padding = dip(4) - }.lparams(dip(32), dip(32)) { - endMargin = dip(4) - } - - tvReply = textView { - }.lparams(dip(0), wrapContent) { - weight = 1f - } - } - - llContentWarning = linearLayout { - lparams(matchParent, wrapContent) { - topMargin = dip(3) - isBaselineAligned = false - } - gravity = Gravity.CENTER_VERTICAL - - btnContentWarning = button { - backgroundDrawable = - ContextCompat.getDrawable(context, R.drawable.bg_button_cw) - minWidthCompat = dip(40) - padding = dip(4) - //tools:text="見る" - }.lparams(wrapContent, dip(40)) { - endMargin = dip(8) - } - - verticalLayout { - lparams(dip(0), wrapContent) { - weight = 1f - } - - tvMentions = myTextView { - }.lparams(matchParent, wrapContent) - - tvContentWarning = myTextView { - }.lparams(matchParent, wrapContent) { - topMargin = dip(3) - } - } - } - - llContents = verticalLayout { - lparams(matchParent, wrapContent) - - tvContent = myTextView { - setLineSpacing(lineSpacingExtra, 1.1f) - // tools:text="Contents\nContents" - }.lparams(matchParent, wrapContent) { - topMargin = dip(3) - } - - val thumbnailHeight = activity.appState.mediaThumbHeight - val verticalArrangeThumbnails = - Pref.bpVerticalArrangeThumbnails(activity.pref) - - flMedia = if (verticalArrangeThumbnails) { - frameLayout { - lparams(matchParent, wrapContent) { - topMargin = dip(3) - } - llMedia = verticalLayout { - lparams(matchParent, matchParent) - - btnHideMedia = imageButton { - background = ContextCompat.getDrawable( - context, - R.drawable.btn_bg_transparent_round6dp - ) - contentDescription = context.getString(R.string.hide) - imageResource = R.drawable.ic_close - }.lparams(dip(32), dip(32)) { - gravity = Gravity.END - } - - ivMedia1 = myNetworkImageView { - background = ContextCompat.getDrawable(context, R.drawable.bg_thumbnail) - contentDescription = context.getString(R.string.thumbnail) - scaleType = ImageView.ScaleType.CENTER_CROP - }.lparams(matchParent, thumbnailHeight) { - topMargin = dip(3) - } - - ivMedia2 = myNetworkImageView { - background = ContextCompat.getDrawable(context, R.drawable.bg_thumbnail) - contentDescription = context.getString(R.string.thumbnail) - scaleType = ImageView.ScaleType.CENTER_CROP - }.lparams(matchParent, thumbnailHeight) { - topMargin = dip(3) - } - - ivMedia3 = myNetworkImageView { - background = ContextCompat.getDrawable(context, R.drawable.bg_thumbnail) - contentDescription = context.getString(R.string.thumbnail) - scaleType = ImageView.ScaleType.CENTER_CROP - }.lparams(matchParent, thumbnailHeight) { - topMargin = dip(3) - } - - ivMedia4 = myNetworkImageView { - background = ContextCompat.getDrawable(context, R.drawable.bg_thumbnail) - contentDescription = context.getString(R.string.thumbnail) - scaleType = ImageView.ScaleType.CENTER_CROP - }.lparams(matchParent, thumbnailHeight) { - topMargin = dip(3) - } - } - - btnShowMedia = blurhashView { - errorColor = context.attrColor(R.attr.colorShowMediaBackground) - gravity = Gravity.CENTER - textColor = context.attrColor(R.attr.colorShowMediaText) - minHeightCompat = dip(48) - }.lparams(matchParent, thumbnailHeight) - } - } else { - frameLayout { - lparams(matchParent, thumbnailHeight) { - topMargin = dip(3) - } - llMedia = linearLayout { - lparams(matchParent, matchParent) - - ivMedia1 = myNetworkImageView { - background = ContextCompat.getDrawable( - context, - R.drawable.bg_thumbnail - ) - contentDescription = - context.getString(R.string.thumbnail) - scaleType = ImageView.ScaleType.CENTER_CROP - }.lparams(0, matchParent) { - weight = 1f - } - - ivMedia2 = myNetworkImageView { - background = ContextCompat.getDrawable(context, R.drawable.bg_thumbnail) - contentDescription = context.getString(R.string.thumbnail) - scaleType = ImageView.ScaleType.CENTER_CROP - }.lparams(0, matchParent) { - startMargin = dip(8) - weight = 1f - } - - ivMedia3 = myNetworkImageView { - background = ContextCompat.getDrawable(context, R.drawable.bg_thumbnail) - contentDescription = context.getString(R.string.thumbnail) - scaleType = ImageView.ScaleType.CENTER_CROP - }.lparams(0, matchParent) { - startMargin = dip(8) - weight = 1f - } - - ivMedia4 = myNetworkImageView { - background = ContextCompat.getDrawable(context, R.drawable.bg_thumbnail) - contentDescription = context.getString(R.string.thumbnail) - scaleType = ImageView.ScaleType.CENTER_CROP - }.lparams(0, matchParent) { - startMargin = dip(8) - weight = 1f - } - - btnHideMedia = imageButton { - background = - ContextCompat.getDrawable(context, R.drawable.btn_bg_transparent_round6dp) - contentDescription = context.getString(R.string.hide) - imageResource = R.drawable.ic_close - }.lparams(dip(32), matchParent) { - startMargin = dip(8) - } - } - - btnShowMedia = blurhashView { - errorColor = context.attrColor( - R.attr.colorShowMediaBackground - ) - gravity = Gravity.CENTER - - textColor = context.attrColor( - R.attr.colorShowMediaText - ) - }.lparams(matchParent, matchParent) - } - } - - tvMediaDescription = textView {}.lparams(matchParent, wrapContent) - - llCardOuter = verticalLayout { - lparams(matchParent, wrapContent) { - topMargin = dip(3) - startMargin = dip(12) - endMargin = dip(6) - } - padding = dip(3) - bottomPadding = dip(6) - - background = PreviewCardBorder() - - tvCardText = myTextView { - }.lparams(matchParent, wrapContent) { - } - - flCardImage = frameLayout { - lparams(matchParent, activity.appState.mediaThumbHeight) { - topMargin = dip(3) - } - - llCardImage = linearLayout { - lparams(matchParent, matchParent) - - ivCardImage = myNetworkImageView { - contentDescription = - context.getString(R.string.thumbnail) - - scaleType = when { - Pref.bpDontCropMediaThumb(App1.pref) -> ImageView.ScaleType.FIT_CENTER - else -> ImageView.ScaleType.CENTER_CROP - } - }.lparams(0, matchParent) { - weight = 1f - } - btnCardImageHide = imageButton { - background = ContextCompat.getDrawable( - context, - R.drawable.btn_bg_transparent_round6dp - ) - contentDescription = context.getString(R.string.hide) - imageResource = R.drawable.ic_close - }.lparams(dip(32), matchParent) { - startMargin = dip(4) - } - } - - btnCardImageShow = blurhashView { - errorColor = context.attrColor( - R.attr.colorShowMediaBackground - ) - gravity = Gravity.CENTER - - textColor = context.attrColor( - R.attr.colorShowMediaText - ) - }.lparams(matchParent, matchParent) - } - } - - llExtra = verticalLayout { - lparams(matchParent, wrapContent) { - topMargin = dip(0) - } - } - } - - // button bar - statusButtonsViewHolder = StatusButtonsViewHolder( - activity, - matchParent, - 3f, - justifyContent = when (Pref.ipBoostButtonJustify(App1.pref)) { - 0 -> JustifyContent.FLEX_START - 1 -> JustifyContent.CENTER - else -> JustifyContent.FLEX_END - } - ) - llButtonBar = statusButtonsViewHolder.viewRoot - addView(llButtonBar) - - tvApplication = textView { - gravity = Gravity.END - }.lparams(matchParent, wrapContent) - } - } - } - - llConversationIcons = linearLayout { - lparams(matchParent, dip(40)) - - isBaselineAligned = false - gravity = Gravity.START or Gravity.CENTER_VERTICAL - - tvConversationParticipants = textView { - text = context.getString(R.string.participants) - }.lparams(wrapContent, wrapContent) { - endMargin = dip(3) - } - - ivConversationIcon1 = myNetworkImageView { - scaleType = ImageView.ScaleType.CENTER_CROP - }.lparams(dip(24), dip(24)) { - endMargin = dip(3) - } - ivConversationIcon2 = myNetworkImageView { - scaleType = ImageView.ScaleType.CENTER_CROP - }.lparams(dip(24), dip(24)) { - endMargin = dip(3) - } - ivConversationIcon3 = myNetworkImageView { - scaleType = ImageView.ScaleType.CENTER_CROP - }.lparams(dip(24), dip(24)) { - endMargin = dip(3) - } - ivConversationIcon4 = myNetworkImageView { - scaleType = ImageView.ScaleType.CENTER_CROP - }.lparams(dip(24), dip(24)) { - endMargin = dip(3) - } - - tvConversationIconsMore = textView {}.lparams(wrapContent, wrapContent) - } - - llSearchTag = linearLayout { - lparams(matchParent, wrapContent) - - btnSearchTag = button { - background = - ContextCompat.getDrawable(context, R.drawable.btn_bg_transparent_round6dp) - allCaps = false - }.lparams(0, wrapContent) { - weight = 1f - } - - btnGapHead = imageButton { - background = ContextCompat.getDrawable( - context, - R.drawable.btn_bg_transparent_round6dp - ) - contentDescription = context.getString(R.string.read_gap_head) - imageResource = R.drawable.ic_arrow_drop_down - }.lparams(dip(32), matchParent) { - startMargin = dip(8) - } - - btnGapTail = imageButton { - background = ContextCompat.getDrawable( - context, - R.drawable.btn_bg_transparent_round6dp - ) - contentDescription = context.getString(R.string.read_gap_tail) - imageResource = R.drawable.ic_arrow_drop_up - }.lparams(dip(32), matchParent) { - startMargin = dip(8) - } - } - - llTrendTag = linearLayout { - lparams(matchParent, wrapContent) - - gravity = Gravity.CENTER_VERTICAL - background = - ContextCompat.getDrawable(context, R.drawable.btn_bg_transparent_round6dp) - - verticalLayout { - lparams(0, wrapContent) { - weight = 1f - } - - tvTrendTagName = textView {}.lparams(matchParent, wrapContent) - - tvTrendTagDesc = textView { - textSize = 12f // SP - }.lparams(matchParent, wrapContent) - } - tvTrendTagCount = textView {}.lparams(wrapContent, wrapContent) { - startMargin = dip(6) - endMargin = dip(6) - } - - cvTagHistory = trendTagHistoryView {}.lparams(dip(64), dip(32)) - } - - llList = linearLayout { - lparams(matchParent, wrapContent) - - gravity = Gravity.CENTER_VERTICAL - isBaselineAligned = false - minimumHeight = dip(40) - - btnListTL = button { - background = - ContextCompat.getDrawable(context, R.drawable.btn_bg_transparent_round6dp) - allCaps = false - }.lparams(0, wrapContent) { - weight = 1f - } - - btnListMore = imageButton { - background = - ContextCompat.getDrawable(context, R.drawable.btn_bg_transparent_round6dp) - imageResource = R.drawable.ic_more - contentDescription = context.getString(R.string.more) - }.lparams(dip(40), matchParent) { - startMargin = dip(4) - } - } - - tvMessageHolder = textView { - padding = dip(4) - }.lparams(matchParent, wrapContent) - - llFollowRequest = linearLayout { - lparams(matchParent, wrapContent) { - topMargin = dip(6) - } - gravity = Gravity.END - - btnFollowRequestAccept = imageButton { - background = - ContextCompat.getDrawable(context, R.drawable.btn_bg_transparent_round6dp) - contentDescription = context.getString(R.string.follow_accept) - imageResource = R.drawable.ic_check - setPadding(0, 0, 0, 0) - }.lparams(dip(48f), dip(32f)) - - btnFollowRequestDeny = imageButton { - background = - ContextCompat.getDrawable(context, R.drawable.btn_bg_transparent_round6dp) - contentDescription = context.getString(R.string.follow_deny) - imageResource = R.drawable.ic_close - setPadding(0, 0, 0, 0) - }.lparams(dip(48f), dip(32f)) { - startMargin = dip(4) - } - } - - llFilter = verticalLayout { - lparams(matchParent, wrapContent) { - } - minimumHeight = dip(40) - - tvFilterPhrase = textView { - typeface = Typeface.DEFAULT_BOLD - }.lparams(matchParent, wrapContent) - - tvFilterDetail = textView { - textSize = 12f // SP - }.lparams(matchParent, wrapContent) - } + inflateBoosted() + inflateFollowed() + inflateStatus(actMain) + inflateConversationIcons() + inflateSearchTag() + inflateTrendTag() + inflateList() + inflateMessageHolder() + + inflateFollowRequest() + inflateFilter() } b.report() rv diff --git a/app/src/main/java/jp/juggler/subwaytooter/ItemViewHolderActions.kt b/app/src/main/java/jp/juggler/subwaytooter/ItemViewHolderActions.kt index e31dbfef..6834a397 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/ItemViewHolderActions.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/ItemViewHolderActions.kt @@ -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()?.let { MediaShown.save(it.uri, show) } +} + +private fun ItemViewHolder.toggleContentWarning() { + // トグル動作 + val show = llContents.visibility == View.GONE + + statusShowing?.let { ContentWarning.save(it, show) } + item.cast()?.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.") + } +} diff --git a/app/src/main/java/jp/juggler/subwaytooter/ItemViewHolderPreviewCard.kt b/app/src/main/java/jp/juggler/subwaytooter/ItemViewHolderPreviewCard.kt index b2f9d221..66646758 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/ItemViewHolderPreviewCard.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/ItemViewHolderPreviewCard.kt @@ -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("
") - val limit = Pref.spCardDescriptionLength.toInt(activity.pref) + val limit = PrefS.spCardDescriptionLength.toInt(activity.pref) sb.append( HTMLDecoder.encodeEntity( diff --git a/app/src/main/java/jp/juggler/subwaytooter/ItemViewHolderReaction.kt b/app/src/main/java/jp/juggler/subwaytooter/ItemViewHolderReaction.kt index 1176ff93..49802cf1 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/ItemViewHolderReaction.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/ItemViewHolderReaction.kt @@ -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 ) diff --git a/app/src/main/java/jp/juggler/subwaytooter/ItemViewHolderShow.kt b/app/src/main/java/jp/juggler/subwaytooter/ItemViewHolderShow.kt index 1700992c..7c269310 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/ItemViewHolderShow.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/ItemViewHolderShow.kt @@ -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, - 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) - } -} diff --git a/app/src/main/java/jp/juggler/subwaytooter/ItemViewHolderShowNotification.kt b/app/src/main/java/jp/juggler/subwaytooter/ItemViewHolderShowNotification.kt new file mode 100644 index 00000000..76352d42 --- /dev/null +++ b/app/src/main/java/jp/juggler/subwaytooter/ItemViewHolderShowNotification.kt @@ -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)) + } + } +} diff --git a/app/src/main/java/jp/juggler/subwaytooter/ItemViewHolderShowStatus.kt b/app/src/main/java/jp/juggler/subwaytooter/ItemViewHolderShowStatus.kt new file mode 100644 index 00000000..bb25d815 --- /dev/null +++ b/app/src/main/java/jp/juggler/subwaytooter/ItemViewHolderShowStatus.kt @@ -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, + 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) + } +} diff --git a/app/src/main/java/jp/juggler/subwaytooter/Pref.kt b/app/src/main/java/jp/juggler/subwaytooter/Pref.kt index 08deeb5f..a1d96a1b 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/Pref.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/Pref.kt @@ -12,12 +12,17 @@ fun Context.pref(): SharedPreferences = @Suppress("EqualsOrHashCode") abstract class BasePref(val key: String, val defVal: T) { + companion object { + // キー名と設定項目のマップ。インポートやアプリ設定で使う + val allPref = HashMap>() + } + 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>() - +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 diff --git a/app/src/main/java/jp/juggler/subwaytooter/SideMenuAdapter.kt b/app/src/main/java/jp/juggler/subwaytooter/SideMenuAdapter.kt index 9e7ea5ad..26cccf91 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/SideMenuAdapter.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/SideMenuAdapter.kt @@ -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 = diff --git a/app/src/main/java/jp/juggler/subwaytooter/StatusButtons.kt b/app/src/main/java/jp/juggler/subwaytooter/StatusButtons.kt index 0663f3f1..17e8f450 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/StatusButtons.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/StatusButtons.kt @@ -6,6 +6,7 @@ import android.widget.ImageButton import android.widget.ImageView import android.widget.LinearLayout import android.widget.PopupWindow +import androidx.annotation.StringRes import androidx.core.content.ContextCompat import com.google.android.flexbox.FlexWrap import com.google.android.flexbox.FlexboxLayout @@ -16,13 +17,27 @@ import jp.juggler.subwaytooter.table.SavedAccount import jp.juggler.subwaytooter.table.UserRelation import jp.juggler.subwaytooter.util.CustomShare import jp.juggler.subwaytooter.util.CustomShareTarget -import jp.juggler.subwaytooter.util.emptyCallback import jp.juggler.subwaytooter.util.startMargin import jp.juggler.subwaytooter.view.CountImageButton import jp.juggler.util.* import org.jetbrains.anko.* import org.jetbrains.anko.custom.customView +enum class AdditionalButtonsPosition( + val idx: Int, // spinner index start from 0 + @StringRes val captionId: Int, +) { + Top(0, R.string.top), + Bottom(1, R.string.bottom), + Start(2, R.string.start), + End(3, R.string.end), + ; + + companion object { + fun fromIndex(i: Int) = values().find { it.idx == i } ?: Top + } +} + class StatusButtons( private val activity: ActMain, private val column: Column, @@ -66,6 +81,10 @@ class StatusButtons( private val colorAccent: Int get() = activity.attrColor(R.attr.colorImageButtonAccent) + var optionalButtonFirst: View? = null + var optionalButtonCount = 0 + var ti: TootInstance? = null + init { this.accessInfo = column.accessInfo @@ -97,11 +116,26 @@ class StatusButtons( fun bind(status: TootStatus, notification: TootNotification?) { holder.viewRoot.visibility = View.VISIBLE + this.status = status this.notification = notification + this.ti = TootInstance.getCached(accessInfo) - val pref = activity.pref + bindMoreButton() + bindConversationButton() + bindQuoteButton() + bindReplyButton(status) + bindBoostButton(status) + bindReactionButton(status) + bindFavouriteButton(status) + bindBookmarkButton(status) + bindFollowButton(status) + // 最後に呼び出す + bindAdditionalButtons() + } + + private fun bindConversationButton() { setIconDrawableId( activity, btnConversation, @@ -109,7 +143,9 @@ class StatusButtons( color = colorNormal, alphaMultiplier = Styler.boostAlpha ) + } + private fun bindMoreButton() { setIconDrawableId( activity, btnMore, @@ -117,7 +153,9 @@ class StatusButtons( color = colorNormal, alphaMultiplier = Styler.boostAlpha ) + } + private fun bindReplyButton(status: TootStatus) { setButton( btnReply, true, @@ -125,20 +163,21 @@ class StatusButtons( R.drawable.ic_reply, when (val repliesCount = status.replies_count) { null -> "" - else -> when (Pref.ipRepliesCount(activity.pref)) { - Pref.RC_SIMPLE -> when { + else -> when (PrefI.ipRepliesCount(activity.pref)) { + PrefI.RC_SIMPLE -> when { repliesCount >= 2L -> "1+" repliesCount == 1L -> "1" else -> "" } - Pref.RC_ACTUAL -> repliesCount.toString() + PrefI.RC_ACTUAL -> repliesCount.toString() else -> "" } }, activity.getString(R.string.reply) ) + } - // ブーストボタン + private fun bindBoostButton(status: TootStatus) { when { // マストドンではDirectはブーストできない (Misskeyはできる) (!accessInfo.isMisskey && status.visibility.order <= TootVisibility.DirectSpecified.order) -> @@ -165,27 +204,30 @@ class StatusButtons( btnBoost, true, when { - status.reblogged -> Pref.ipButtonBoostedColor(pref).notZero() ?: colorAccent - else -> colorNormal + status.reblogged -> + PrefI.ipButtonBoostedColor(activity.pref).notZero() ?: colorAccent + else -> + colorNormal }, R.drawable.ic_repeat, when (val boostsCount = status.reblogs_count) { null -> "" - else -> when (Pref.ipBoostsCount(activity.pref)) { - Pref.RC_SIMPLE -> when { + else -> when (PrefI.ipBoostsCount(activity.pref)) { + PrefI.RC_SIMPLE -> when { boostsCount >= 2L -> "1+" boostsCount == 1L -> "1" else -> "" } - Pref.RC_ACTUAL -> boostsCount.toString() + PrefI.RC_ACTUAL -> boostsCount.toString() else -> "" } }, activity.getString(R.string.boost) ) } + } - val ti = TootInstance.getCached(accessInfo) + private fun bindQuoteButton() { btnQuote.vg(ti?.feature_quote == true)?.let { setButton( it, @@ -195,7 +237,9 @@ class StatusButtons( activity.getString(R.string.quote) ) } + } + private fun bindReactionButton(status: TootStatus) { btnReaction.vg(TootReaction.canReaction(accessInfo, ti))?.let { val canMultipleReaction = InstanceCapability.canMultipleReaction(accessInfo, ti) val hasMyReaction = status.reactionSet?.hasMyReaction() == true @@ -210,73 +254,83 @@ class StatusButtons( ) ) } + } - // お気に入りボタン - val favIconDrawable = when { - accessInfo.isNicoru(status.account) -> R.drawable.ic_nicoru - else -> R.drawable.ic_star - } + private fun bindFavouriteButton(status: TootStatus) { when { - activity.appState.isBusyFav(accessInfo, status) -> setButton( - btnFavourite, - false, - colorNormal, - R.drawable.ic_refresh, - "?", - activity.getString(R.string.favourite) - ) + activity.appState.isBusyFav(accessInfo, status) -> + setButton( + btnFavourite, + false, + colorNormal, + R.drawable.ic_refresh, + "?", + activity.getString(R.string.favourite) + ) - else -> setButton( - btnFavourite, - true, - when { - status.favourited -> Pref.ipButtonFavoritedColor(pref).notZero() ?: colorAccent - else -> colorNormal - }, - favIconDrawable, - when (val favouritesCount = status.favourites_count) { - null -> "" - else -> when (Pref.ipFavouritesCount(activity.pref)) { - Pref.RC_SIMPLE -> when { - favouritesCount >= 2L -> "1+" - favouritesCount == 1L -> "1" + else -> + setButton( + btnFavourite, + true, + when { + status.favourited -> + PrefI.ipButtonFavoritedColor(activity.pref).notZero() ?: colorAccent + else -> colorNormal + }, + when { + accessInfo.isNicoru(status.account) -> R.drawable.ic_nicoru + else -> R.drawable.ic_star + }, + when (val favouritesCount = status.favourites_count) { + null -> "" + else -> when (PrefI.ipFavouritesCount(activity.pref)) { + PrefI.RC_SIMPLE -> when { + favouritesCount >= 2L -> "1+" + favouritesCount == 1L -> "1" + else -> "" + } + PrefI.RC_ACTUAL -> favouritesCount.toString() else -> "" } - Pref.RC_ACTUAL -> favouritesCount.toString() - else -> "" - } - }, - activity.getString(R.string.favourite) - ) + }, + activity.getString(R.string.favourite) + ) } + } - // ブックマークボタン - when { - !Pref.bpShowBookmarkButton(activity.pref) -> btnBookmark.vg(false) - - activity.appState.isBusyBookmark(accessInfo, status) -> setButton( - btnBookmark, - false, - colorNormal, - R.drawable.ic_refresh, - activity.getString(R.string.bookmark) - ) - - else -> setButton( - btnBookmark, - true, + private fun bindBookmarkButton(status: TootStatus) { + btnBookmark.vg(PrefB.bpShowBookmarkButton(activity.pref)) + ?.let { btn -> when { - status.bookmarked -> Pref.ipButtonBookmarkedColor(pref).notZero() ?: colorAccent - else -> colorNormal - }, - R.drawable.ic_bookmark, - activity.getString(R.string.bookmark) - ) - } + activity.appState.isBusyBookmark(accessInfo, status) -> + setButton( + btn, + false, + colorNormal, + R.drawable.ic_refresh, + activity.getString(R.string.bookmark) + ) + else -> + setButton( + btn, + true, + when { + status.bookmarked -> + PrefI.ipButtonBookmarkedColor(activity.pref).notZero() ?: colorAccent + else -> + colorNormal + }, + R.drawable.ic_bookmark, + activity.getString(R.string.bookmark) + ) + } + } + } + + private fun bindFollowButton(status: TootStatus) { val account = status.account - - this.relation = if (!Pref.bpShowFollowButtonInButtonBar(activity.pref)) { + this.relation = if (!PrefB.bpShowFollowButtonInButtonBar(activity.pref)) { llFollow2.visibility = View.GONE null } else { @@ -293,62 +347,80 @@ class StatusButtons( ) relation } + } - var optionalButtonFirst: View? = null - var optionalButtonCount = 0 + private fun bindAdditionalButtons() { + optionalButtonFirst = null + optionalButtonCount = 0 - fun ImageButton.showCustomShare(target: CustomShareTarget) { - val (label, icon) = CustomShare.getCache(target) - ?: error("showCustomShare: invalid target") - - vg(label != null || icon != null)?.apply { - isEnabled = true - contentDescription = label ?: "?" - setImageDrawable( - icon ?: createColoredDrawable( - this@StatusButtons.activity, - R.drawable.ic_question, - colorNormal, - Styler.boostAlpha - ) - ) - ++optionalButtonCount - if (optionalButtonFirst == null) { - optionalButtonFirst = this - } - } - } - - btnTranslate.vg(Pref.bpShowTranslateButton(activity.pref)) + btnTranslate.vg(PrefB.bpShowTranslateButton(activity.pref)) ?.showCustomShare(CustomShareTarget.Translate) - btnCustomShare1.showCustomShare(CustomShareTarget.CustomShare1) btnCustomShare2.showCustomShare(CustomShareTarget.CustomShare2) btnCustomShare3.showCustomShare(CustomShareTarget.CustomShare3) + val updateAdditionalButton: (btn: ImageButton) -> Unit = + getUpdateAdditionalButton(optionalButtonCount, optionalButtonFirst) + + updateAdditionalButton(btnTranslate) + updateAdditionalButton(btnCustomShare1) + updateAdditionalButton(btnCustomShare2) + updateAdditionalButton(btnCustomShare3) + } + + private fun ImageButton.showCustomShare(target: CustomShareTarget) { + val (label, icon) = CustomShare.getCache(target) + ?: error("showCustomShare: invalid target") + + vg(label != null || icon != null)?.apply { + isEnabled = true + contentDescription = label ?: "?" + setImageDrawable( + icon ?: createColoredDrawable( + this@StatusButtons.activity, + R.drawable.ic_question, + colorNormal, + Styler.boostAlpha + ) + ) + ++optionalButtonCount + if (optionalButtonFirst == null) { + optionalButtonFirst = this + } + } + } + + private fun getLambda(): (ImageButton) -> Unit { + return when ("1".toInt()) { + 1 -> { + println("1") + ({ btn -> + println(btn) + }) + } + else -> { + println("1") + ({ btn -> + println(btn) + }) + } + } + } + + private fun getUpdateAdditionalButton( + optionalButtonCount: Int, + optionalButtonFirst: View?, + ): (btn: ImageButton) -> Unit { val lpConversation = btnConversation.layoutParams as? FlexboxLayout.LayoutParams - val updateAdditionalButton: (btn: ImageButton) -> Unit - when (Pref.ipAdditionalButtonsPosition(activity.pref)) { - Pref.ABP_TOP -> { + return when (AdditionalButtonsPosition.fromIndex(PrefI.ipAdditionalButtonsPosition(activity.pref))) { + AdditionalButtonsPosition.Top -> { // 1行目に追加ボタンが並ぶ - updateAdditionalButton = { btn -> - (btn.layoutParams as? FlexboxLayout.LayoutParams)?.let { lp -> - lp.isWrapBefore = false - lp.startMargin = when (btn) { - optionalButtonFirst -> 0 - else -> holder.marginBetween - } - } - } // 2行目は通常ボタンが並ぶ // 2行目最初のボタンのstartMarginは追加ボタンの有無で変化する lpConversation?.startMargin = 0 lpConversation?.isWrapBefore = (optionalButtonCount != 0) - } - - Pref.ABP_START -> { - // 始端に追加ボタンが並ぶ - updateAdditionalButton = { btn -> + // ラムダを返したいが、上の文との区切りでセミコロンか()が必要らしい + ({ btn -> (btn.layoutParams as? FlexboxLayout.LayoutParams)?.let { lp -> lp.isWrapBefore = false lp.startMargin = when (btn) { @@ -356,31 +428,47 @@ class StatusButtons( else -> holder.marginBetween } } - } + }) + } + + AdditionalButtonsPosition.Start -> { + // 始端に追加ボタンが並ぶ // 続いて通常ボタンが並ぶ lpConversation?.startMargin = holder.marginBetween lpConversation?.isWrapBefore = false + // ラムダを返したいが、上の文との区切りでセミコロンか()が必要らしい + ({ btn -> + (btn.layoutParams as? FlexboxLayout.LayoutParams)?.let { lp -> + lp.isWrapBefore = false + lp.startMargin = when (btn) { + optionalButtonFirst -> 0 + else -> holder.marginBetween + } + } + }) } - Pref.ABP_END -> { + AdditionalButtonsPosition.End -> { // 始端に通常ボタンが並ぶ + // 続いて追加ボタンが並ぶ lpConversation?.startMargin = 0 lpConversation?.isWrapBefore = false - // 続いて追加ボタンが並ぶ - updateAdditionalButton = { btn -> + // ラムダを返したいが、上の文との区切りでセミコロンか()が必要らしい + ({ btn -> (btn.layoutParams as? FlexboxLayout.LayoutParams)?.let { lp -> lp.isWrapBefore = false lp.startMargin = holder.marginBetween } - } + }) } - else /* Pref.ABP_BOTTOM */ -> { + AdditionalButtonsPosition.Bottom -> { // 1行目は通常ボタンが並ぶ + // 2行目は追加ボタンが並ぶ lpConversation?.startMargin = 0 lpConversation?.isWrapBefore = false - // 2行目は追加ボタンが並ぶ - updateAdditionalButton = { btn -> + // ラムダを返したいが、上の文との区切りでセミコロンか()が必要らしい + ({ btn -> (btn.layoutParams as? FlexboxLayout.LayoutParams)?.let { lp -> lp.isWrapBefore = btn == optionalButtonFirst lp.startMargin = when (btn) { @@ -388,14 +476,9 @@ class StatusButtons( else -> holder.marginBetween } } - } + }) } } - - updateAdditionalButton(btnTranslate) - updateAdditionalButton(btnCustomShare1) - updateAdditionalButton(btnCustomShare2) - updateAdditionalButton(btnCustomShare3) } private fun setButton( @@ -440,265 +523,71 @@ class StatusButtons( } override fun onClick(v: View) { - closeWindow?.dismiss() closeWindow = null val status = this.status ?: return - when (v) { - - btnConversation -> { - - val cs = status.conversationSummary - if (activity.conversationUnreadClear(accessInfo, cs)) { - // 表示の更新 - itemViewHolder.listAdapter.notifyChange( - reason = "ConversationSummary reset unread", - reset = true - ) - } - - activity.conversation( - activity.nextPosition(column), - accessInfo, - status - ) + with(activity) { + val pos = nextPosition(column) + when (v) { + btnMore -> clickMore(status) + btnConversation -> clickConversation(pos, accessInfo, itemViewHolder.listAdapter, status = status) + btnReply -> clickReply(accessInfo, status) + btnQuote -> clickQuote(accessInfo, status) + btnBoost -> clickBoost(accessInfo, status, willToast = bSimpleList) + btnFavourite -> clickFavourite(accessInfo, status, willToast = bSimpleList) + btnBookmark -> clickBookmark(accessInfo, status, willToast = bSimpleList) + btnReaction -> clickReaction(accessInfo, column, status) + btnFollow2 -> clickFollow(pos, accessInfo, status.accountRef, relation) + btnTranslate -> shareUrl(status, CustomShareTarget.Translate) + btnCustomShare1 -> shareUrl(status, CustomShareTarget.CustomShare1) + btnCustomShare2 -> shareUrl(status, CustomShareTarget.CustomShare2) + btnCustomShare3 -> shareUrl(status, CustomShareTarget.CustomShare3) } - - btnReply -> if (!accessInfo.isPseudo) { - activity.reply(accessInfo, status) - } else { - activity.replyFromAnotherAccount(accessInfo, status) - } - - btnQuote -> if (!accessInfo.isPseudo) { - activity.reply(accessInfo, status, quote = true) - } else { - activity.quoteFromAnotherAccount(accessInfo, status) - } - - btnBoost -> { - if (accessInfo.isPseudo) { - activity.boostFromAnotherAccount(accessInfo, status) - } else { - - // トグル動作 - val bSet = !status.reblogged - - activity.boost( - - accessInfo, - status, - accessInfo.getFullAcct(status.account), - CrossAccountMode.SameAccount, - bSet = bSet, - callback = when { - !bSimpleList -> emptyCallback - // 簡略表示なら結果をトースト表示 - bSet -> activity.boostCompleteCallback - else -> activity.unboostCompleteCallback - }, - ) - } - } - - btnFavourite -> { - if (accessInfo.isPseudo) { - activity.favouriteFromAnotherAccount(accessInfo, status) - } else { - - // トグル動作 - val bSet = !status.favourited - - activity.favourite( - accessInfo, - status, - CrossAccountMode.SameAccount, - bSet = bSet, - callback = when { - !bSimpleList -> emptyCallback - // 簡略表示なら結果をトースト表示 - bSet -> activity.favouriteCompleteCallback - else -> activity.unfavouriteCompleteCallback - }, - ) - } - } - - btnBookmark -> { - if (accessInfo.isPseudo) { - activity.bookmarkFromAnotherAccount(accessInfo, status) - } else { - - // トグル動作 - val bSet = !status.bookmarked - - activity.bookmark( - accessInfo, - status, - CrossAccountMode.SameAccount, - bSet = bSet, - callback = when { - !bSimpleList -> emptyCallback - // 簡略表示なら結果をトースト表示 - bSet -> activity.bookmarkCompleteCallback - else -> activity.unbookmarkCompleteCallback - }, - ) - } - } - - btnReaction -> { - val canMultipleReaction = InstanceCapability.canMultipleReaction(accessInfo) - val hasMyReaction = status.reactionSet?.hasMyReaction() == true - val bRemoveButton = hasMyReaction && !canMultipleReaction - when { - !TootReaction.canReaction(accessInfo) -> - activity.reactionFromAnotherAccount( - accessInfo, - status - ) - bRemoveButton -> - activity.reactionRemove(column, status) - else -> - activity.reactionAdd(column, status) - } - } - - btnFollow2 -> { - val accountRef = status.accountRef - val account = accountRef.get() - val relation = this.relation ?: return - - when { - accessInfo.isPseudo -> { - // 別アカでフォロー - activity.followFromAnotherAccount( - - activity.nextPosition(column), - accessInfo, - account - ) - } - - relation.blocking || relation.muting -> { - // 何もしない - } - - accessInfo.isMisskey && relation.getRequested(account) && !relation.getFollowing( - account - ) -> - activity.followRequestDelete( - - activity.nextPosition(column), - accessInfo, - accountRef, - callback = activity.cancelFollowRequestCompleteCallback - ) - - relation.getFollowing(account) || relation.getRequested(account) -> { - // フォロー解除 - activity.follow( - - activity.nextPosition(column), - accessInfo, - accountRef, - bFollow = false, - callback = activity.unfollowCompleteCallback - ) - } - - else -> { - // フォロー - activity.follow( - activity.nextPosition(column), - accessInfo, - accountRef, - bFollow = true, - callback = activity.followCompleteCallback - ) - } - } - } - - btnTranslate -> CustomShare.invoke( - activity, - accessInfo, - status, - CustomShareTarget.Translate - ) - - btnCustomShare1 -> CustomShare.invoke( - activity, - accessInfo, - status, - CustomShareTarget.CustomShare1 - ) - - btnCustomShare2 -> CustomShare.invoke( - activity, - accessInfo, - status, - CustomShareTarget.CustomShare2 - ) - - btnCustomShare3 -> CustomShare.invoke( - activity, - accessInfo, - status, - CustomShareTarget.CustomShare3 - ) - - btnMore -> DlgContextMenu( - activity, - column, - status.accountRef, - status, - notification, - itemViewHolder.tvContent - ).show() } } override fun onLongClick(v: View): Boolean { - closeWindow?.dismiss() closeWindow = null val status = this.status ?: return true - when (v) { - btnBoost -> activity.boostFromAnotherAccount(accessInfo, status) - btnFavourite -> activity.favouriteFromAnotherAccount(accessInfo, status) - btnBookmark -> activity.bookmarkFromAnotherAccount(accessInfo, status) - - btnReply -> activity.replyFromAnotherAccount(accessInfo, status) - btnQuote -> activity.quoteFromAnotherAccount(accessInfo, status) - - btnReaction -> activity.reactionFromAnotherAccount(accessInfo, status) - - btnConversation -> activity.conversationOtherInstance(activity.nextPosition(column), status) - - btnFollow2 -> - activity.followFromAnotherAccount(activity.nextPosition(column), accessInfo, status.account) - - btnTranslate -> shareUrl(status, CustomShareTarget.Translate) - btnCustomShare1 -> shareUrl(status, CustomShareTarget.CustomShare1) - btnCustomShare2 -> shareUrl(status, CustomShareTarget.CustomShare2) - btnCustomShare3 -> shareUrl(status, CustomShareTarget.CustomShare3) + with(activity) { + when (v) { + btnBoost -> boostFromAnotherAccount(accessInfo, status) + btnFavourite -> favouriteFromAnotherAccount(accessInfo, status) + btnBookmark -> bookmarkFromAnotherAccount(accessInfo, status) + btnReply -> replyFromAnotherAccount(accessInfo, status) + btnQuote -> quoteFromAnotherAccount(accessInfo, status) + btnReaction -> reactionFromAnotherAccount(accessInfo, status) + btnConversation -> conversationOtherInstance(nextPosition(column), status) + btnFollow2 -> followFromAnotherAccount(nextPosition(column), accessInfo, status.account) + btnTranslate -> shareUrl(status, CustomShareTarget.Translate) + btnCustomShare1 -> shareUrl(status, CustomShareTarget.CustomShare1) + btnCustomShare2 -> shareUrl(status, CustomShareTarget.CustomShare2) + btnCustomShare3 -> shareUrl(status, CustomShareTarget.CustomShare3) + } } return true } - private fun shareUrl( - status: TootStatus, - target: CustomShareTarget, - ) { + private fun shareUrl(status: TootStatus, target: CustomShareTarget) { val url = status.url ?: status.uri - CustomShare.invoke(activity, url, target) } + + private fun clickMore(status: TootStatus) { + DlgContextMenu( + activity, + column, + status.accountRef, + status, + notification, + itemViewHolder.tvContent + ).show() + } } open class AnkoFlexboxLayout(ctx: Context) : FlexboxLayout(ctx) { @@ -748,9 +637,179 @@ class StatusButtonsViewHolder( lateinit var btnCustomShare3: ImageButton lateinit var btnMore: ImageButton + fun AnkoFlexboxLayout.normalButtons() { + btnConversation = imageButton { + background = ContextCompat.getDrawable( + context, + R.drawable.btn_bg_transparent_round6dp + ) + contentDescription = context.getString(R.string.conversation_view) + + setPadding(paddingH, paddingV, paddingH, paddingV) + scaleType = ImageView.ScaleType.FIT_CENTER + imageResource = R.drawable.ic_forum + }.lparams(buttonHeight, buttonHeight) + + btnReply = customView { + background = ContextCompat.getDrawable( + context, + R.drawable.btn_bg_transparent_round6dp + ) + setPadding(paddingH, paddingV, paddingH, paddingV) + scaleType = ImageView.ScaleType.FIT_CENTER + minimumWidth = buttonHeight + }.lparams(wrapContent, buttonHeight) { + startMargin = marginBetween + } + + btnBoost = customView { + background = ContextCompat.getDrawable( + context, + R.drawable.btn_bg_transparent_round6dp + ) + setPadding(paddingH, paddingV, paddingH, paddingV) + scaleType = ImageView.ScaleType.FIT_CENTER + minimumWidth = buttonHeight + }.lparams(wrapContent, buttonHeight) { + startMargin = marginBetween + } + + btnFavourite = customView { + background = ContextCompat.getDrawable( + context, + R.drawable.btn_bg_transparent_round6dp + ) + setPadding(paddingH, paddingV, paddingH, paddingV) + scaleType = ImageView.ScaleType.FIT_CENTER + minimumWidth = buttonHeight + }.lparams(wrapContent, buttonHeight) { + startMargin = marginBetween + } + + btnBookmark = imageButton { + background = ContextCompat.getDrawable( + context, + R.drawable.btn_bg_transparent_round6dp + ) + setPadding(paddingH, paddingV, paddingH, paddingV) + scaleType = ImageView.ScaleType.FIT_CENTER + minimumWidth = buttonHeight + }.lparams(wrapContent, buttonHeight) { + startMargin = marginBetween + } + + btnQuote = imageButton { + background = ContextCompat.getDrawable( + context, + R.drawable.btn_bg_transparent_round6dp + ) + setPadding(paddingH, paddingV, paddingH, paddingV) + scaleType = ImageView.ScaleType.FIT_CENTER + minimumWidth = buttonHeight + }.lparams(wrapContent, buttonHeight) { + startMargin = marginBetween + } + + btnReaction = imageButton { + background = ContextCompat.getDrawable( + context, + R.drawable.btn_bg_transparent_round6dp + ) + setPadding(paddingH, paddingV, paddingH, paddingV) + scaleType = ImageView.ScaleType.FIT_CENTER + minimumWidth = buttonHeight + }.lparams(wrapContent, buttonHeight) { + startMargin = marginBetween + } + + llFollow2 = frameLayout { + lparams(buttonHeight, buttonHeight) { + startMargin = marginBetween + } + + btnFollow2 = imageButton { + background = ContextCompat.getDrawable( + context, + R.drawable.btn_bg_transparent_round6dp + ) + setPadding(paddingH, paddingV, paddingH, paddingV) + scaleType = ImageView.ScaleType.FIT_CENTER + + contentDescription = context.getString(R.string.follow) + }.lparams(matchParent, matchParent) + + ivFollowedBy2 = imageView { + + setPadding(paddingH, paddingV, paddingH, paddingV) + scaleType = ImageView.ScaleType.FIT_CENTER + + importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_NO + }.lparams(matchParent, matchParent) + } + + btnMore = imageButton { + background = ContextCompat.getDrawable( + context, + R.drawable.btn_bg_transparent_round6dp + ) + setPadding(paddingH, paddingV, paddingH, paddingV) + scaleType = ImageView.ScaleType.FIT_CENTER + + contentDescription = context.getString(R.string.more) + imageResource = R.drawable.ic_more + }.lparams(buttonHeight, buttonHeight) { + startMargin = marginBetween + } + } + + private fun AnkoFlexboxLayout.additionalButtons() { + btnTranslate = imageButton { + background = ContextCompat.getDrawable( + context, + R.drawable.btn_bg_transparent_round6dp + ) + setPadding(paddingH, paddingV, paddingH, paddingV) + scaleType = ImageView.ScaleType.FIT_CENTER + }.lparams(buttonHeight, buttonHeight) { + startMargin = marginBetween + } + + btnCustomShare1 = imageButton { + background = ContextCompat.getDrawable( + context, + R.drawable.btn_bg_transparent_round6dp + ) + setPadding(paddingH, paddingV, paddingH, paddingV) + scaleType = ImageView.ScaleType.FIT_CENTER + }.lparams(buttonHeight, buttonHeight) { + startMargin = marginBetween + } + + btnCustomShare2 = imageButton { + background = ContextCompat.getDrawable( + context, + R.drawable.btn_bg_transparent_round6dp + ) + setPadding(paddingH, paddingV, paddingH, paddingV) + scaleType = ImageView.ScaleType.FIT_CENTER + }.lparams(buttonHeight, buttonHeight) { + startMargin = marginBetween + } + + btnCustomShare3 = imageButton { + background = ContextCompat.getDrawable( + context, + R.drawable.btn_bg_transparent_round6dp + ) + setPadding(paddingH, paddingV, paddingH, paddingV) + scaleType = ImageView.ScaleType.FIT_CENTER + }.lparams(buttonHeight, buttonHeight) { + startMargin = marginBetween + } + } + init { viewRoot = with(activity.UI {}) { - customView { // トップレベルのViewGroupのlparamsはイニシャライザ内部に置くしかないみたい layoutParams = LinearLayout.LayoutParams(lpWidth, wrapContent).apply { @@ -758,184 +817,11 @@ class StatusButtonsViewHolder( } flexWrap = FlexWrap.WRAP this.justifyContent = justifyContent - - fun normalButtons() { - - btnConversation = imageButton { - background = ContextCompat.getDrawable( - context, - R.drawable.btn_bg_transparent_round6dp - ) - contentDescription = context.getString(R.string.conversation_view) - - setPadding(paddingH, paddingV, paddingH, paddingV) - scaleType = ImageView.ScaleType.FIT_CENTER - imageResource = R.drawable.ic_forum - }.lparams(buttonHeight, buttonHeight) - - btnReply = customView { - background = ContextCompat.getDrawable( - context, - R.drawable.btn_bg_transparent_round6dp - ) - setPadding(paddingH, paddingV, paddingH, paddingV) - scaleType = ImageView.ScaleType.FIT_CENTER - minimumWidth = buttonHeight - }.lparams(wrapContent, buttonHeight) { - startMargin = marginBetween - } - - btnBoost = customView { - background = ContextCompat.getDrawable( - context, - R.drawable.btn_bg_transparent_round6dp - ) - setPadding(paddingH, paddingV, paddingH, paddingV) - scaleType = ImageView.ScaleType.FIT_CENTER - minimumWidth = buttonHeight - }.lparams(wrapContent, buttonHeight) { - startMargin = marginBetween - } - - btnFavourite = customView { - background = ContextCompat.getDrawable( - context, - R.drawable.btn_bg_transparent_round6dp - ) - setPadding(paddingH, paddingV, paddingH, paddingV) - scaleType = ImageView.ScaleType.FIT_CENTER - minimumWidth = buttonHeight - }.lparams(wrapContent, buttonHeight) { - startMargin = marginBetween - } - - btnBookmark = imageButton { - background = ContextCompat.getDrawable( - context, - R.drawable.btn_bg_transparent_round6dp - ) - setPadding(paddingH, paddingV, paddingH, paddingV) - scaleType = ImageView.ScaleType.FIT_CENTER - minimumWidth = buttonHeight - }.lparams(wrapContent, buttonHeight) { - startMargin = marginBetween - } - - btnQuote = imageButton { - background = ContextCompat.getDrawable( - context, - R.drawable.btn_bg_transparent_round6dp - ) - setPadding(paddingH, paddingV, paddingH, paddingV) - scaleType = ImageView.ScaleType.FIT_CENTER - minimumWidth = buttonHeight - }.lparams(wrapContent, buttonHeight) { - startMargin = marginBetween - } - - btnReaction = imageButton { - background = ContextCompat.getDrawable( - context, - R.drawable.btn_bg_transparent_round6dp - ) - setPadding(paddingH, paddingV, paddingH, paddingV) - scaleType = ImageView.ScaleType.FIT_CENTER - minimumWidth = buttonHeight - }.lparams(wrapContent, buttonHeight) { - startMargin = marginBetween - } - - llFollow2 = frameLayout { - lparams(buttonHeight, buttonHeight) { - startMargin = marginBetween - } - - btnFollow2 = imageButton { - background = ContextCompat.getDrawable( - context, - R.drawable.btn_bg_transparent_round6dp - ) - setPadding(paddingH, paddingV, paddingH, paddingV) - scaleType = ImageView.ScaleType.FIT_CENTER - - contentDescription = context.getString(R.string.follow) - }.lparams(matchParent, matchParent) - - ivFollowedBy2 = imageView { - - setPadding(paddingH, paddingV, paddingH, paddingV) - scaleType = ImageView.ScaleType.FIT_CENTER - - importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_NO - }.lparams(matchParent, matchParent) - } - - btnMore = imageButton { - background = ContextCompat.getDrawable( - context, - R.drawable.btn_bg_transparent_round6dp - ) - setPadding(paddingH, paddingV, paddingH, paddingV) - scaleType = ImageView.ScaleType.FIT_CENTER - - contentDescription = context.getString(R.string.more) - imageResource = R.drawable.ic_more - }.lparams(buttonHeight, buttonHeight) { - startMargin = marginBetween - } - } - - fun additionalButtons() { - btnTranslate = imageButton { - background = ContextCompat.getDrawable( - context, - R.drawable.btn_bg_transparent_round6dp - ) - setPadding(paddingH, paddingV, paddingH, paddingV) - scaleType = ImageView.ScaleType.FIT_CENTER - }.lparams(buttonHeight, buttonHeight) { - startMargin = marginBetween - } - - btnCustomShare1 = imageButton { - background = ContextCompat.getDrawable( - context, - R.drawable.btn_bg_transparent_round6dp - ) - setPadding(paddingH, paddingV, paddingH, paddingV) - scaleType = ImageView.ScaleType.FIT_CENTER - }.lparams(buttonHeight, buttonHeight) { - startMargin = marginBetween - } - - btnCustomShare2 = imageButton { - background = ContextCompat.getDrawable( - context, - R.drawable.btn_bg_transparent_round6dp - ) - setPadding(paddingH, paddingV, paddingH, paddingV) - scaleType = ImageView.ScaleType.FIT_CENTER - }.lparams(buttonHeight, buttonHeight) { - startMargin = marginBetween - } - - btnCustomShare3 = imageButton { - background = ContextCompat.getDrawable( - context, - R.drawable.btn_bg_transparent_round6dp - ) - setPadding(paddingH, paddingV, paddingH, paddingV) - scaleType = ImageView.ScaleType.FIT_CENTER - }.lparams(buttonHeight, buttonHeight) { - startMargin = marginBetween - } - } - when (Pref.ipAdditionalButtonsPosition(activity.pref)) { - Pref.ABP_TOP, Pref.ABP_START -> { + when (AdditionalButtonsPosition.fromIndex(PrefI.ipAdditionalButtonsPosition(activity.pref))) { + AdditionalButtonsPosition.Top, AdditionalButtonsPosition.Start -> { additionalButtons() normalButtons() } - else -> { normalButtons() additionalButtons() diff --git a/app/src/main/java/jp/juggler/subwaytooter/Styler.kt b/app/src/main/java/jp/juggler/subwaytooter/Styler.kt index 34fd3c87..b890ba5b 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/Styler.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/Styler.kt @@ -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 diff --git a/app/src/main/java/jp/juggler/subwaytooter/ViewHolderHeaderProfile.kt b/app/src/main/java/jp/juggler/subwaytooter/ViewHolderHeaderProfile.kt index 3c077fc2..1c14c72d 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/ViewHolderHeaderProfile.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/ViewHolderHeaderProfile.kt @@ -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) { + 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) + } + } } diff --git a/app/src/main/java/jp/juggler/subwaytooter/action/Action_Account.kt b/app/src/main/java/jp/juggler/subwaytooter/action/Action_Account.kt index 794e9632..29b4bd9e 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/action/Action_Account.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/action/Action_Account.kt @@ -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() diff --git a/app/src/main/java/jp/juggler/subwaytooter/action/Action_Boost.kt b/app/src/main/java/jp/juggler/subwaytooter/action/Action_Boost.kt new file mode 100644 index 00000000..187c46ff --- /dev/null +++ b/app/src/main/java/jp/juggler/subwaytooter/action/Action_Boost.kt @@ -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() + 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 + }, + ) + } +} diff --git a/app/src/main/java/jp/juggler/subwaytooter/action/Action_Conversation.kt b/app/src/main/java/jp/juggler/subwaytooter/action/Action_Conversation.kt index 2e14119e..49755299 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/action/Action_Conversation.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/action/Action_Conversation.kt @@ -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 diff --git a/app/src/main/java/jp/juggler/subwaytooter/action/Action_Filter.kt b/app/src/main/java/jp/juggler/subwaytooter/action/Action_Filter.kt index 90e692af..637e33c2 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/action/Action_Filter.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/action/Action_Filter.kt @@ -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, diff --git a/app/src/main/java/jp/juggler/subwaytooter/action/Action_Follow.kt b/app/src/main/java/jp/juggler/subwaytooter/action/Action_Follow.kt index bc42d8f6..7cc4fd55 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/action/Action_Follow.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/action/Action_Follow.kt @@ -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, diff --git a/app/src/main/java/jp/juggler/subwaytooter/action/Action_List.kt b/app/src/main/java/jp/juggler/subwaytooter/action/Action_List.kt index 457fe871..dfbb8a9d 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/action/Action_List.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/action/Action_List.kt @@ -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) } diff --git a/app/src/main/java/jp/juggler/subwaytooter/action/Action_ListMember.kt b/app/src/main/java/jp/juggler/subwaytooter/action/Action_ListMember.kt index febfb323..192c56d6 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/action/Action_ListMember.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/action/Action_ListMember.kt @@ -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) } } } diff --git a/app/src/main/java/jp/juggler/subwaytooter/action/Action_OpenPost.kt b/app/src/main/java/jp/juggler/subwaytooter/action/Action_OpenPost.kt index 6a3d6b1a..7c06c245 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/action/Action_OpenPost.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/action/Action_OpenPost.kt @@ -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) + } +} diff --git a/app/src/main/java/jp/juggler/subwaytooter/action/Action_Reaction.kt b/app/src/main/java/jp/juggler/subwaytooter/action/Action_Reaction.kt index 1b7df1d8..5d88f8d7 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/action/Action_Reaction.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/action/Action_Reaction.kt @@ -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) + } +} diff --git a/app/src/main/java/jp/juggler/subwaytooter/action/Action_Server.kt b/app/src/main/java/jp/juggler/subwaytooter/action/Action_Server.kt index ef6a105d..46ec2dd7 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/action/Action_Server.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/action/Action_Server.kt @@ -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, diff --git a/app/src/main/java/jp/juggler/subwaytooter/action/Action_Status.kt b/app/src/main/java/jp/juggler/subwaytooter/action/Action_Status.kt index 70b23d61..f7875b20 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/action/Action_Status.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/action/Action_Status.kt @@ -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() - 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, diff --git a/app/src/main/java/jp/juggler/subwaytooter/action/Action_Tag.kt b/app/src/main/java/jp/juggler/subwaytooter/action/Action_Tag.kt index 28ea7e71..c5ba8d2f 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/action/Action_Tag.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/action/Action_Tag.kt @@ -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, diff --git a/app/src/main/java/jp/juggler/subwaytooter/api/entity/TootAccount.kt b/app/src/main/java/jp/juggler/subwaytooter/api/entity/TootAccount.kt index 8f56c6db..02be8041 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/api/entity/TootAccount.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/api/entity/TootAccount.kt @@ -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, diff --git a/app/src/main/java/jp/juggler/subwaytooter/api/entity/TootAttachment.kt b/app/src/main/java/jp/juggler/subwaytooter/api/entity/TootAttachment.kt index e48b8e43..61537f57 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/api/entity/TootAttachment.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/api/entity/TootAttachment.kt @@ -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().apply { - if (Pref.bpPriorLocalURL(pref)) { + if (PrefB.bpPriorLocalURL(pref)) { url.notEmpty()?.addTo(this) remote_url.notEmpty()?.addTo(this) } else { diff --git a/app/src/main/java/jp/juggler/subwaytooter/api/entity/TootInstance.kt b/app/src/main/java/jp/juggler/subwaytooter/api/entity/TootInstance.kt index 8266d0b6..1e15cfed 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/api/entity/TootInstance.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/api/entity/TootInstance.kt @@ -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.") diff --git a/app/src/main/java/jp/juggler/subwaytooter/api/entity/TootReaction.kt b/app/src/main/java/jp/juggler/subwaytooter/api/entity/TootReaction.kt index 297f563e..d994eb5d 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/api/entity/TootReaction.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/api/entity/TootReaction.kt @@ -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 } diff --git a/app/src/main/java/jp/juggler/subwaytooter/api/entity/TootStatus.kt b/app/src/main/java/jp/juggler/subwaytooter/api/entity/TootStatus.kt index 8b54e71d..068813b5 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/api/entity/TootStatus.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/api/entity/TootStatus.kt @@ -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) diff --git a/app/src/main/java/jp/juggler/subwaytooter/dialog/DlgListMember.kt b/app/src/main/java/jp/juggler/subwaytooter/dialog/DlgListMember.kt index 13e609b6..f12e3fbe 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/dialog/DlgListMember.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/dialog/DlgListMember.kt @@ -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() diff --git a/app/src/main/java/jp/juggler/subwaytooter/dialog/DlgQuickTootMenu.kt b/app/src/main/java/jp/juggler/subwaytooter/dialog/DlgQuickTootMenu.kt index 3a47906a..2feb14a8 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/dialog/DlgQuickTootMenu.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/dialog/DlgQuickTootMenu.kt @@ -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", " ") } diff --git a/app/src/main/java/jp/juggler/subwaytooter/dialog/EmojiPicker.kt b/app/src/main/java/jp/juggler/subwaytooter/dialog/EmojiPicker.kt index cc02e7da..27a0dcc4 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/dialog/EmojiPicker.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/dialog/EmojiPicker.kt @@ -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 = 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) { } diff --git a/app/src/main/java/jp/juggler/subwaytooter/emoji/EmojiBase.kt b/app/src/main/java/jp/juggler/subwaytooter/emoji/EmojiBase.kt index 37583ea6..d4cdd1c3 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/emoji/EmojiBase.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/emoji/EmojiBase.kt @@ -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 } diff --git a/app/src/main/java/jp/juggler/subwaytooter/notification/PollingWorker.kt b/app/src/main/java/jp/juggler/subwaytooter/notification/PollingWorker.kt index 5f2cde1a..cdff0c3b 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/notification/PollingWorker.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/notification/PollingWorker.kt @@ -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( diff --git a/app/src/main/java/jp/juggler/subwaytooter/notification/PushSubscriptionHelper.kt b/app/src/main/java/jp/juggler/subwaytooter/notification/PushSubscriptionHelper.kt index 1f4583f0..b82044ce 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/notification/PushSubscriptionHelper.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/notification/PushSubscriptionHelper.kt @@ -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.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.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 + } + } + } } diff --git a/app/src/main/java/jp/juggler/subwaytooter/notification/TaskRunner.kt b/app/src/main/java/jp/juggler/subwaytooter/notification/TaskRunner.kt index 1c08fa4b..4143f768 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/notification/TaskRunner.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/notification/TaskRunner.kt @@ -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().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 -> { diff --git a/app/src/main/java/jp/juggler/subwaytooter/search/MspHelper.kt b/app/src/main/java/jp/juggler/subwaytooter/search/MspHelper.kt index b1492fa0..aa8e525c 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/search/MspHelper.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/search/MspHelper.kt @@ -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() } } diff --git a/app/src/main/java/jp/juggler/subwaytooter/span/NetworkEmojiSpan.kt b/app/src/main/java/jp/juggler/subwaytooter/span/NetworkEmojiSpan.kt index 29ff8cb6..e515cc7a 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/span/NetworkEmojiSpan.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/span/NetworkEmojiSpan.kt @@ -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) } } diff --git a/app/src/main/java/jp/juggler/subwaytooter/streaming/StreamConnection.kt b/app/src/main/java/jp/juggler/subwaytooter/streaming/StreamConnection.kt index a3026d6f..f6576b98 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/streaming/StreamConnection.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/streaming/StreamConnection.kt @@ -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 { diff --git a/app/src/main/java/jp/juggler/subwaytooter/streaming/StreamManager.kt b/app/src/main/java/jp/juggler/subwaytooter/streaming/StreamManager.kt index 43f9d025..53bcf24b 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/streaming/StreamManager.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/streaming/StreamManager.kt @@ -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 diff --git a/app/src/main/java/jp/juggler/subwaytooter/util/AppOpener.kt b/app/src/main/java/jp/juggler/subwaytooter/util/AppOpener.kt index 037e05f5..bae8d2f6 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/util/AppOpener.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/util/AppOpener.kt @@ -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( diff --git a/app/src/main/java/jp/juggler/subwaytooter/util/CompletionHelper.kt b/app/src/main/java/jp/juggler/subwaytooter/util/CompletionHelper.kt index 5a6e5cc7..6ea6ee4f 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/util/CompletionHelper.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/util/CompletionHelper.kt @@ -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 diff --git a/app/src/main/java/jp/juggler/subwaytooter/util/CustomShare.kt b/app/src/main/java/jp/juggler/subwaytooter/util/CustomShare.kt index 60c58245..d737c247 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/util/CustomShare.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/util/CustomShare.kt @@ -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 } } diff --git a/app/src/main/java/jp/juggler/subwaytooter/util/EmojiDecoder.kt b/app/src/main/java/jp/juggler/subwaytooter/util/EmojiDecoder.kt index 6db4ddbe..f53c80dd 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/util/EmojiDecoder.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/util/EmojiDecoder.kt @@ -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? = null ): String { - val decodeEmojioneShortcode = Pref.bpEmojioneShortcode(App1.pref) + val decodeEmojioneShortcode = PrefB.bpEmojioneShortcode(App1.pref) val sb = StringBuilder() diff --git a/app/src/main/java/jp/juggler/subwaytooter/util/HTMLDecoder.kt b/app/src/main/java/jp/juggler/subwaytooter/util/HTMLDecoder.kt index e36f254a..22c68977 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/util/HTMLDecoder.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/util/HTMLDecoder.kt @@ -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("") + } + + 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 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("") + 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}" } } diff --git a/app/src/main/java/jp/juggler/subwaytooter/util/MisskeyMarkdownDecoder.kt b/app/src/main/java/jp/juggler/subwaytooter/util/MisskeyMarkdownDecoder.kt index 7ae13d2f..00c134fc 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/util/MisskeyMarkdownDecoder.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/util/MisskeyMarkdownDecoder.kt @@ -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 }" diff --git a/app/src/main/java/jp/juggler/subwaytooter/util/PostImpl.kt b/app/src/main/java/jp/juggler/subwaytooter/util/PostImpl.kt index 35ab134e..444d99d6 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/util/PostImpl.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/util/PostImpl.kt @@ -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) } diff --git a/app/src/main/java/jp/juggler/subwaytooter/util/TootTextEncoder.kt b/app/src/main/java/jp/juggler/subwaytooter/util/TootTextEncoder.kt index ee123ead..0db0963c 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/util/TootTextEncoder.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/util/TootTextEncoder.kt @@ -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, diff --git a/app/src/main/java/jp/juggler/subwaytooter/view/MyNetworkImageView.kt b/app/src/main/java/jp/juggler/subwaytooter/view/MyNetworkImageView.kt index a252a4e3..687ce105 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/view/MyNetworkImageView.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/view/MyNetworkImageView.kt @@ -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 diff --git a/app/src/main/java/jp/juggler/util/ViewUtils.kt b/app/src/main/java/jp/juggler/util/ViewUtils.kt index 587f20c0..181bd0d0 100644 --- a/app/src/main/java/jp/juggler/util/ViewUtils.kt +++ b/app/src/main/java/jp/juggler/util/ViewUtils.kt @@ -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) {