2018-01-04 19:52:25 +01:00
|
|
|
package jp.juggler.subwaytooter
|
|
|
|
|
|
|
|
import android.content.Context
|
2019-12-15 20:12:57 +01:00
|
|
|
import android.content.res.Configuration
|
2019-01-16 13:33:07 +01:00
|
|
|
import android.graphics.PorterDuff
|
|
|
|
import android.graphics.drawable.Drawable
|
2018-01-04 19:52:25 +01:00
|
|
|
import android.text.SpannableStringBuilder
|
|
|
|
import android.text.Spanned
|
|
|
|
import android.view.View
|
|
|
|
import android.view.ViewGroup
|
|
|
|
import android.widget.ImageButton
|
|
|
|
import android.widget.ImageView
|
2021-06-20 15:12:25 +02:00
|
|
|
import androidx.annotation.DrawableRes
|
2020-09-09 21:46:50 +02:00
|
|
|
import androidx.core.content.ContextCompat
|
2018-01-04 19:52:25 +01:00
|
|
|
import jp.juggler.subwaytooter.api.entity.TootAccount
|
2018-08-20 19:37:42 +02:00
|
|
|
import jp.juggler.subwaytooter.api.entity.TootVisibility
|
2021-11-08 10:29:11 +01:00
|
|
|
import jp.juggler.subwaytooter.emoji.EmojiMap
|
|
|
|
import jp.juggler.subwaytooter.pref.PrefB
|
2021-11-06 04:00:29 +01:00
|
|
|
import jp.juggler.subwaytooter.pref.PrefI
|
|
|
|
import jp.juggler.subwaytooter.pref.pref
|
2018-01-04 19:52:25 +01:00
|
|
|
import jp.juggler.subwaytooter.span.EmojiImageSpan
|
2019-09-14 22:09:52 +02:00
|
|
|
import jp.juggler.subwaytooter.span.createSpan
|
2018-08-20 19:37:42 +02:00
|
|
|
import jp.juggler.subwaytooter.table.UserRelation
|
2020-09-09 21:46:50 +02:00
|
|
|
import jp.juggler.util.LogCategory
|
2021-01-04 02:11:45 +01:00
|
|
|
import jp.juggler.util.attrColor
|
2020-09-09 21:46:50 +02:00
|
|
|
import jp.juggler.util.notZero
|
|
|
|
import jp.juggler.util.setIconDrawableId
|
2019-12-15 20:12:57 +01:00
|
|
|
import kotlin.math.max
|
2019-09-14 22:09:52 +02:00
|
|
|
import kotlin.math.min
|
2018-01-04 19:52:25 +01:00
|
|
|
|
2019-12-15 20:12:57 +01:00
|
|
|
private val log = LogCategory("Styler")
|
|
|
|
|
2018-01-04 19:52:25 +01:00
|
|
|
object Styler {
|
2021-06-20 15:12:25 +02:00
|
|
|
|
|
|
|
fun defaultColorIcon(context: Context, iconId: Int): Drawable? =
|
|
|
|
ContextCompat.getDrawable(context, iconId)?.also {
|
|
|
|
it.setTint(context.attrColor(R.attr.colorVectorDrawable))
|
|
|
|
it.setTintMode(PorterDuff.Mode.SRC_IN)
|
|
|
|
}
|
|
|
|
|
|
|
|
fun getVisibilityIconId(isMisskeyData: Boolean, visibility: TootVisibility): Int {
|
2021-11-20 01:36:43 +01:00
|
|
|
val isMisskey = when (PrefI.ipVisibilityStyle()) {
|
2021-06-22 10:31:51 +02:00
|
|
|
PrefI.VS_MASTODON -> false
|
|
|
|
PrefI.VS_MISSKEY -> true
|
2021-06-20 15:12:25 +02:00
|
|
|
else -> isMisskeyData
|
|
|
|
}
|
|
|
|
return when {
|
|
|
|
isMisskey -> when (visibility) {
|
|
|
|
TootVisibility.Public -> R.drawable.ic_public
|
|
|
|
TootVisibility.UnlistedHome -> R.drawable.ic_home
|
|
|
|
TootVisibility.PrivateFollowers -> R.drawable.ic_lock_open
|
|
|
|
TootVisibility.DirectSpecified -> R.drawable.ic_mail
|
|
|
|
TootVisibility.DirectPrivate -> R.drawable.ic_lock
|
|
|
|
TootVisibility.WebSetting -> R.drawable.ic_question
|
|
|
|
TootVisibility.AccountSetting -> R.drawable.ic_question
|
|
|
|
|
|
|
|
TootVisibility.LocalPublic -> R.drawable.ic_local_ltl
|
|
|
|
TootVisibility.LocalHome -> R.drawable.ic_local_home
|
|
|
|
TootVisibility.LocalFollowers -> R.drawable.ic_local_lock_open
|
|
|
|
|
|
|
|
TootVisibility.Unknown -> R.drawable.ic_question
|
|
|
|
TootVisibility.Limited -> R.drawable.ic_account_circle
|
|
|
|
TootVisibility.Mutual -> R.drawable.ic_bidirectional
|
|
|
|
}
|
|
|
|
else -> when (visibility) {
|
|
|
|
TootVisibility.Public -> R.drawable.ic_public
|
|
|
|
TootVisibility.UnlistedHome -> R.drawable.ic_lock_open
|
|
|
|
TootVisibility.PrivateFollowers -> R.drawable.ic_lock
|
|
|
|
TootVisibility.DirectSpecified -> R.drawable.ic_mail
|
|
|
|
TootVisibility.DirectPrivate -> R.drawable.ic_mail
|
|
|
|
TootVisibility.WebSetting -> R.drawable.ic_question
|
|
|
|
TootVisibility.AccountSetting -> R.drawable.ic_question
|
|
|
|
|
|
|
|
TootVisibility.LocalPublic -> R.drawable.ic_local_ltl
|
|
|
|
TootVisibility.LocalHome -> R.drawable.ic_local_lock_open
|
|
|
|
TootVisibility.LocalFollowers -> R.drawable.ic_local_lock
|
|
|
|
|
|
|
|
TootVisibility.Unknown -> R.drawable.ic_question
|
|
|
|
TootVisibility.Limited -> R.drawable.ic_account_circle
|
|
|
|
TootVisibility.Mutual -> R.drawable.ic_bidirectional
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fun getVisibilityString(
|
|
|
|
context: Context,
|
|
|
|
isMisskeyData: Boolean,
|
|
|
|
visibility: TootVisibility
|
|
|
|
): String {
|
2021-11-20 01:36:43 +01:00
|
|
|
val isMisskey = when (PrefI.ipVisibilityStyle()) {
|
2021-06-22 10:31:51 +02:00
|
|
|
PrefI.VS_MASTODON -> false
|
|
|
|
PrefI.VS_MISSKEY -> true
|
2021-06-20 15:12:25 +02:00
|
|
|
else -> isMisskeyData
|
|
|
|
}
|
|
|
|
return context.getString(
|
|
|
|
when {
|
|
|
|
isMisskey -> when (visibility) {
|
|
|
|
TootVisibility.Public -> R.string.visibility_public
|
|
|
|
TootVisibility.UnlistedHome -> R.string.visibility_home
|
|
|
|
TootVisibility.PrivateFollowers -> R.string.visibility_followers
|
|
|
|
TootVisibility.DirectSpecified -> R.string.visibility_direct
|
|
|
|
TootVisibility.DirectPrivate -> R.string.visibility_private
|
|
|
|
TootVisibility.WebSetting -> R.string.visibility_web_setting
|
|
|
|
TootVisibility.AccountSetting -> R.string.visibility_account_setting
|
|
|
|
|
|
|
|
TootVisibility.LocalPublic -> R.string.visibility_local_public
|
|
|
|
TootVisibility.LocalHome -> R.string.visibility_local_home
|
|
|
|
TootVisibility.LocalFollowers -> R.string.visibility_local_followers
|
|
|
|
|
|
|
|
TootVisibility.Unknown -> R.string.visibility_unknown
|
|
|
|
TootVisibility.Limited -> R.string.visibility_limited
|
|
|
|
TootVisibility.Mutual -> R.string.visibility_mutual
|
|
|
|
}
|
|
|
|
else -> when (visibility) {
|
|
|
|
TootVisibility.Public -> R.string.visibility_public
|
|
|
|
TootVisibility.UnlistedHome -> R.string.visibility_unlisted
|
|
|
|
TootVisibility.PrivateFollowers -> R.string.visibility_followers
|
|
|
|
TootVisibility.DirectSpecified -> R.string.visibility_direct
|
|
|
|
TootVisibility.DirectPrivate -> R.string.visibility_direct
|
|
|
|
TootVisibility.WebSetting -> R.string.visibility_web_setting
|
|
|
|
TootVisibility.AccountSetting -> R.string.visibility_account_setting
|
|
|
|
|
|
|
|
TootVisibility.LocalPublic -> R.string.visibility_local_public
|
|
|
|
TootVisibility.LocalHome -> R.string.visibility_local_unlisted
|
|
|
|
TootVisibility.LocalFollowers -> R.string.visibility_local_followers
|
|
|
|
|
|
|
|
TootVisibility.Unknown -> R.string.visibility_unknown
|
|
|
|
TootVisibility.Limited -> R.string.visibility_limited
|
|
|
|
TootVisibility.Mutual -> R.string.visibility_mutual
|
|
|
|
}
|
|
|
|
}
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
// アイコン付きの装飾テキストを返す
|
|
|
|
fun getVisibilityCaption(
|
|
|
|
context: Context,
|
|
|
|
isMisskeyData: Boolean,
|
|
|
|
visibility: TootVisibility
|
|
|
|
): CharSequence {
|
|
|
|
|
|
|
|
val icon_id = getVisibilityIconId(isMisskeyData, visibility)
|
|
|
|
val sv = getVisibilityString(context, isMisskeyData, visibility)
|
|
|
|
val color = context.attrColor(R.attr.colorVectorDrawable)
|
|
|
|
val sb = SpannableStringBuilder()
|
|
|
|
|
|
|
|
// アイコン部分
|
|
|
|
val start = sb.length
|
|
|
|
sb.append(" ")
|
|
|
|
val end = sb.length
|
|
|
|
sb.setSpan(
|
|
|
|
EmojiImageSpan(
|
|
|
|
context,
|
|
|
|
icon_id,
|
|
|
|
useColorShader = true,
|
|
|
|
color = color
|
|
|
|
),
|
|
|
|
start,
|
|
|
|
end,
|
|
|
|
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
|
|
|
|
)
|
|
|
|
|
|
|
|
// 文字列部分
|
|
|
|
sb.append(' ')
|
|
|
|
sb.append(sv)
|
|
|
|
|
|
|
|
return sb
|
|
|
|
}
|
|
|
|
|
|
|
|
fun setFollowIcon(
|
|
|
|
context: Context,
|
|
|
|
ibFollow: ImageButton,
|
|
|
|
ivDot: ImageView,
|
|
|
|
relation: UserRelation,
|
|
|
|
who: TootAccount,
|
|
|
|
defaultColor: Int,
|
|
|
|
alphaMultiplier: Float
|
|
|
|
) {
|
|
|
|
fun colorAccent() =
|
2021-06-22 10:31:51 +02:00
|
|
|
PrefI.ipButtonFollowingColor(context.pref()).notZero()
|
2023-01-13 07:33:28 +01:00
|
|
|
?: context.attrColor(R.attr.colorButtonAccentFollow)
|
2021-06-20 15:12:25 +02:00
|
|
|
|
|
|
|
fun colorError() =
|
2021-06-22 10:31:51 +02:00
|
|
|
PrefI.ipButtonFollowRequestColor(context.pref()).notZero()
|
2023-01-13 07:33:28 +01:00
|
|
|
?: context.attrColor(R.attr.colorButtonAccentFollowRequest)
|
2021-06-20 15:12:25 +02:00
|
|
|
|
|
|
|
// 被フォロー状態
|
|
|
|
when {
|
|
|
|
|
|
|
|
relation.blocked_by -> {
|
|
|
|
ivDot.visibility = View.VISIBLE
|
|
|
|
setIconDrawableId(
|
|
|
|
context,
|
|
|
|
ivDot,
|
|
|
|
R.drawable.ic_blocked_by,
|
|
|
|
color = colorError(),
|
|
|
|
alphaMultiplier = alphaMultiplier
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
relation.requested_by -> {
|
|
|
|
ivDot.visibility = View.VISIBLE
|
|
|
|
setIconDrawableId(
|
|
|
|
context,
|
|
|
|
ivDot,
|
|
|
|
R.drawable.ic_requested_by,
|
|
|
|
color = colorError(),
|
|
|
|
alphaMultiplier = alphaMultiplier
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
relation.followed_by -> {
|
|
|
|
ivDot.visibility = View.VISIBLE
|
|
|
|
setIconDrawableId(
|
|
|
|
context,
|
|
|
|
ivDot,
|
2023-01-08 04:16:29 +01:00
|
|
|
R.drawable.ic_follow_dot,
|
2021-06-20 15:12:25 +02:00
|
|
|
color = colorAccent(),
|
|
|
|
alphaMultiplier = alphaMultiplier
|
|
|
|
)
|
|
|
|
// 被フォローリクエスト状態の時に followed_by が 真と偽の両方がありえるようなので
|
|
|
|
// Relationshipだけを見ても被フォローリクエスト状態は分からないっぽい
|
|
|
|
// 仕方ないので馬鹿正直に「 followed_byが真ならバッジをつける」しかできない
|
|
|
|
}
|
|
|
|
|
|
|
|
else -> {
|
|
|
|
ivDot.visibility = View.GONE
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// フォローボタン
|
|
|
|
// follow button
|
|
|
|
val color: Int
|
|
|
|
val iconId: Int
|
|
|
|
val contentDescription: String
|
|
|
|
|
|
|
|
when {
|
|
|
|
relation.blocking -> {
|
|
|
|
iconId = R.drawable.ic_block
|
|
|
|
color = defaultColor
|
|
|
|
contentDescription = context.getString(R.string.follow)
|
|
|
|
}
|
|
|
|
|
|
|
|
relation.muting -> {
|
|
|
|
iconId = R.drawable.ic_volume_off
|
|
|
|
color = defaultColor
|
|
|
|
contentDescription = context.getString(R.string.follow)
|
|
|
|
}
|
|
|
|
|
|
|
|
relation.getFollowing(who) -> {
|
|
|
|
iconId = R.drawable.ic_follow_cross
|
|
|
|
color = colorAccent()
|
|
|
|
contentDescription = context.getString(R.string.unfollow)
|
|
|
|
}
|
|
|
|
|
|
|
|
relation.getRequested(who) -> {
|
|
|
|
iconId = R.drawable.ic_follow_wait
|
|
|
|
color = colorError()
|
|
|
|
contentDescription = context.getString(R.string.unfollow)
|
|
|
|
}
|
|
|
|
|
|
|
|
else -> {
|
|
|
|
iconId = R.drawable.ic_follow_plus
|
|
|
|
color = defaultColor
|
|
|
|
contentDescription = context.getString(R.string.follow)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
setIconDrawableId(
|
|
|
|
context,
|
|
|
|
ibFollow,
|
|
|
|
iconId,
|
|
|
|
color = color,
|
|
|
|
alphaMultiplier = alphaMultiplier
|
|
|
|
)
|
|
|
|
ibFollow.contentDescription = contentDescription
|
|
|
|
}
|
|
|
|
|
|
|
|
private fun getHorizontalPadding(v: View, dpDelta: Float): Int {
|
|
|
|
// Essential Phone PH-1は 短辺439dp
|
|
|
|
val formWidthMax = 460f
|
|
|
|
val dm = v.resources.displayMetrics
|
|
|
|
val screenW = dm.widthPixels
|
|
|
|
val contentW = (0.5f + formWidthMax * dm.density).toInt()
|
|
|
|
val padW = max(0, (screenW - contentW) / 2)
|
|
|
|
return padW + (0.5f + dpDelta * dm.density).toInt()
|
|
|
|
}
|
|
|
|
|
|
|
|
private fun getOrientationString(orientation: Int?) = when (orientation) {
|
|
|
|
null -> "null"
|
|
|
|
Configuration.ORIENTATION_LANDSCAPE -> "landscape"
|
|
|
|
Configuration.ORIENTATION_PORTRAIT -> "portrait"
|
|
|
|
Configuration.ORIENTATION_UNDEFINED -> "undefined"
|
|
|
|
else -> orientation.toString()
|
|
|
|
}
|
|
|
|
|
|
|
|
fun fixHorizontalPadding(v: View, dpDelta: Float = 12f) {
|
|
|
|
val pad_t = v.paddingTop
|
|
|
|
val pad_b = v.paddingBottom
|
|
|
|
|
|
|
|
val dm = v.resources.displayMetrics
|
|
|
|
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()
|
2021-11-20 01:36:43 +01:00
|
|
|
when (PrefI.ipJustifyWindowContentPortrait()) {
|
2021-06-22 10:31:51 +02:00
|
|
|
PrefI.JWCP_START -> {
|
2021-06-20 15:12:25 +02:00
|
|
|
v.setPaddingRelative(pad_lr, pad_t, pad_lr + dm.widthPixels / 2, pad_b)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2021-06-22 10:31:51 +02:00
|
|
|
PrefI.JWCP_END -> {
|
2021-06-20 15:12:25 +02:00
|
|
|
v.setPaddingRelative(pad_lr + dm.widthPixels / 2, pad_t, pad_lr, pad_b)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
val pad_lr = getHorizontalPadding(v, dpDelta)
|
|
|
|
v.setPaddingRelative(pad_lr, pad_t, pad_lr, pad_b)
|
|
|
|
}
|
|
|
|
|
|
|
|
fun fixHorizontalPadding0(v: View) = fixHorizontalPadding(v, 0f)
|
|
|
|
|
|
|
|
fun fixHorizontalMargin(v: View) {
|
|
|
|
val lp = v.layoutParams
|
|
|
|
if (lp is ViewGroup.MarginLayoutParams) {
|
|
|
|
|
|
|
|
val dm = v.resources.displayMetrics
|
|
|
|
val orientationString = getOrientationString(v.resources?.configuration?.orientation)
|
|
|
|
val widthDp = dm.widthPixels / dm.density
|
|
|
|
log.d("fixHorizontalMargin: orientation=$orientationString, w=${widthDp}dp, h=${dm.heightPixels / dm.density}")
|
|
|
|
|
|
|
|
if (widthDp >= 640f && v.resources?.configuration?.orientation == Configuration.ORIENTATION_PORTRAIT) {
|
2021-11-20 01:36:43 +01:00
|
|
|
when (PrefI.ipJustifyWindowContentPortrait()) {
|
2021-06-22 10:31:51 +02:00
|
|
|
PrefI.JWCP_START -> {
|
2021-06-20 15:12:25 +02:00
|
|
|
lp.marginStart = 0
|
|
|
|
lp.marginEnd = dm.widthPixels / 2
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2021-06-22 10:31:51 +02:00
|
|
|
PrefI.JWCP_END -> {
|
2021-06-20 15:12:25 +02:00
|
|
|
lp.marginStart = dm.widthPixels / 2
|
|
|
|
lp.marginEnd = 0
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
val pad_lr = getHorizontalPadding(v, 0f)
|
|
|
|
lp.leftMargin = pad_lr
|
|
|
|
lp.rightMargin = pad_lr
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// ActMainの初期化時に更新される
|
|
|
|
var round_ratio: Float = 0.33f * 0.5f
|
|
|
|
var boostAlpha: Float = 1f
|
|
|
|
|
|
|
|
fun calcIconRound(wh: Int) = wh.toFloat() * round_ratio
|
|
|
|
|
|
|
|
fun calcIconRound(lp: ViewGroup.LayoutParams) =
|
|
|
|
min(lp.width, lp.height).toFloat() * round_ratio
|
2018-01-04 19:52:25 +01:00
|
|
|
}
|
2018-04-21 01:16:44 +02:00
|
|
|
|
|
|
|
fun SpannableStringBuilder.appendColorShadeIcon(
|
2021-06-20 15:12:25 +02:00
|
|
|
context: Context,
|
|
|
|
@DrawableRes drawableId: Int,
|
|
|
|
text: String,
|
|
|
|
color: Int? = null
|
|
|
|
): SpannableStringBuilder {
|
|
|
|
val start = this.length
|
|
|
|
this.append(text)
|
|
|
|
val end = this.length
|
|
|
|
this.setSpan(
|
|
|
|
EmojiImageSpan(context, drawableId, useColorShader = true, color = color),
|
|
|
|
start,
|
|
|
|
end,
|
|
|
|
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
|
|
|
|
)
|
|
|
|
return this
|
2018-08-21 03:53:52 +02:00
|
|
|
}
|
|
|
|
|
2019-09-14 22:09:52 +02:00
|
|
|
fun SpannableStringBuilder.appendMisskeyReaction(
|
2021-06-20 15:12:25 +02:00
|
|
|
context: Context,
|
|
|
|
emojiUtf16: String,
|
|
|
|
text: String
|
|
|
|
): SpannableStringBuilder {
|
|
|
|
|
2021-11-08 10:29:11 +01:00
|
|
|
val emoji = EmojiMap.unicodeMap[emojiUtf16]
|
|
|
|
when {
|
|
|
|
emoji == null ->
|
|
|
|
append("text")
|
|
|
|
|
|
|
|
PrefB.bpUseTwemoji(context) -> {
|
|
|
|
val start = this.length
|
|
|
|
append(text)
|
|
|
|
val end = this.length
|
|
|
|
this.setSpan(
|
|
|
|
emoji.createSpan(context),
|
|
|
|
start, end,
|
|
|
|
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
|
|
|
|
)
|
|
|
|
}
|
|
|
|
else ->
|
|
|
|
this.append(emoji.unifiedCode)
|
|
|
|
}
|
2021-06-20 15:12:25 +02:00
|
|
|
return this
|
2018-04-21 01:16:44 +02:00
|
|
|
}
|