アプリ設定に絵文字セクションと設定項目をいくつか追加。絵文字のアスペクト比をDBに保存する。

This commit is contained in:
tateisu 2023-02-26 08:15:57 +09:00
parent d83efef4d9
commit aefcc7c798
27 changed files with 312 additions and 86 deletions

View File

@ -236,6 +236,9 @@ class ApngFrames private constructor(
}
}
val aspect:Float?
get() = if( width<=0 || height<=0) null else width.toFloat().div(height)
constructor(bitmap: Bitmap) : this() {
defaultImage = bitmap
}

View File

@ -38,6 +38,7 @@ import jp.juggler.subwaytooter.span.MyClickableSpan
import jp.juggler.subwaytooter.span.MyClickableSpanHandler
import jp.juggler.subwaytooter.table.daoSavedAccount
import jp.juggler.subwaytooter.util.*
import jp.juggler.subwaytooter.util.DecodeOptions.Companion.reloadEmojiScale
import jp.juggler.subwaytooter.view.MyDrawerLayout
import jp.juggler.subwaytooter.view.MyEditText
import jp.juggler.subwaytooter.view.MyNetworkImageView
@ -370,6 +371,7 @@ class ActMain : AppCompatActivity(),
acctPadLr = (0.5f + 4f * density).toInt()
reloadTextSize()
reloadEmojiScale()
initUI()
@ -455,6 +457,7 @@ class ActMain : AppCompatActivity(),
super.onStart()
galaxyBackgroundWorkaround()
benchmark("onStart total") {
reloadEmojiScale()
benchmark("reload color") { reloadColors() }
benchmark("reload timezone") { reloadTimeZone() }

View File

@ -120,8 +120,8 @@ fun ActMain.reactionAdd(
activity,
accessInfo,
decodeEmoji = true,
enlargeEmoji = 1.5f,
enlargeCustomEmoji = 1.5f,
enlargeEmoji = DecodeOptions.emojiScaleReaction,
enlargeCustomEmoji = DecodeOptions.emojiScaleReaction,
emojiSizeMode = accessInfo.emojiSizeMode(),
)
val emojiSpan = TootReaction.toSpannableStringBuilder(options, code, urlArg)
@ -219,8 +219,8 @@ fun ActMain.reactionRemove(
activity,
accessInfo,
decodeEmoji = true,
enlargeEmoji = 1.5f,
enlargeCustomEmoji = 1.5f,
enlargeEmoji = DecodeOptions.emojiScaleReaction,
enlargeCustomEmoji = DecodeOptions.emojiScaleReaction,
emojiSizeMode = accessInfo.emojiSizeMode(),
)
val emojiSpan = reaction.toSpannableStringBuilder(options, status)
@ -330,8 +330,8 @@ private fun ActMain.reactionWithoutUi(
activity,
accessInfo,
decodeEmoji = true,
enlargeEmoji = 1.5f,
enlargeCustomEmoji = 1.5f,
enlargeEmoji = DecodeOptions.emojiScaleReaction,
enlargeCustomEmoji = DecodeOptions.emojiScaleReaction,
emojiSizeMode = accessInfo.emojiSizeMode(),
)
val emojiSpan = TootReaction.toSpannableStringBuilder(options, reactionCode, reactionImage)

View File

@ -14,6 +14,7 @@ import jp.juggler.subwaytooter.util.emojiSizeMode
import jp.juggler.subwaytooter.table.SavedAccount
import jp.juggler.subwaytooter.table.daoUserRelation
import jp.juggler.subwaytooter.util.DecodeOptions
import jp.juggler.subwaytooter.util.DecodeOptions.Companion.emojiScaleUserName
import jp.juggler.subwaytooter.util.LinkHelper
import jp.juggler.subwaytooter.util.NetworkEmojiInvalidator
import jp.juggler.subwaytooter.util.matchHost
@ -168,6 +169,8 @@ open class TootAccount(
emojiMapProfile = profile_emojis,
emojiMapCustom = custom_emojis,
authorDomain = this,
enlargeCustomEmoji = emojiScaleUserName,
enlargeEmoji = emojiScaleUserName,
).decodeEmoji(sv)
}

View File

@ -4,6 +4,8 @@ import android.text.Spannable
import jp.juggler.subwaytooter.api.TootAccountMap
import jp.juggler.subwaytooter.api.TootParser
import jp.juggler.subwaytooter.util.DecodeOptions
import jp.juggler.subwaytooter.util.DecodeOptions.Companion.emojiScaleMastodon
import jp.juggler.subwaytooter.util.DecodeOptions.Companion.emojiScaleMisskey
class TootAccountRef private constructor(
val mapId: Int,
@ -50,6 +52,8 @@ class TootAccountRef private constructor(
unwrapEmojiImageTag = true,
authorDomain = account,
emojiSizeMode = parser.emojiSizeMode,
enlargeEmoji = if(parser.linkHelper.isMisskey) emojiScaleMisskey else emojiScaleMastodon,
enlargeCustomEmoji = if(parser.linkHelper.isMisskey) emojiScaleMisskey else emojiScaleMastodon,
).decodeHTML(account.note),
)
}

View File

@ -343,7 +343,6 @@ val appSettingRoot = AppSettingItem(null, SettingType.Section, R.string.app_sett
}
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(
@ -464,10 +463,6 @@ val appSettingRoot = AppSettingItem(null, SettingType.Section, R.string.app_sett
R.string.warn_hashtag_ascii_and_non_ascii
)
sw(
PrefB.bpEmojiPickerCloseOnSelected,
R.string.close_emoji_picker_when_selected
)
sw(PrefB.bpIgnoreTextInSharedMedia, R.string.ignore_text_in_shared_media)
}
@ -525,6 +520,58 @@ val appSettingRoot = AppSettingItem(null, SettingType.Section, R.string.app_sett
sw(PrefB.bpDisableEmojiAnimation, R.string.disable_custom_emoji_animation)
}
section(R.string.emoji){
sw(PrefB.bpUseTwemoji, R.string.use_twemoji_emoji)
sw(PrefB.bpEmojioneShortcode, R.string.emojione_shortcode_support) {
desc = R.string.emojione_shortcode_support_desc
}
sw(PrefB.bpEmojiPickerCategoryOther, R.string.show_emoji_picker_other_category)
sw(
PrefB.bpEmojiPickerCloseOnSelected,
R.string.close_emoji_picker_when_selected
)
sw(PrefB.bpCustomEmojiSeparatorZwsp, R.string.custom_emoji_separator_zwsp)
text(
PrefS.spEmojiSizeMastodon,
R.string.emoji_size_mastodon,
InputTypeEx.number
)
text(
PrefS.spEmojiSizeMisskey,
R.string.emoji_size_misskey,
InputTypeEx.number
)
text(
PrefS.spEmojiSizeReaction,
R.string.emoji_size_reaction,
InputTypeEx.number
)
text(
PrefS.spEmojiSizeUserName,
R.string.emoji_size_user_name,
InputTypeEx.number
)
spinnerSimple(
PrefI.ipEmojiWideMode,
R.string.emoji_wide_mode,
R.string.auto,
R.string.enabled,
R.string.disabled,
)
text(
PrefS.spEmojiPixels,
R.string.emoji_texture_pixels,
InputTypeEx.number
)
}
section(R.string.appearance) {
sw(PrefB.bpSimpleList, R.string.simple_list)
sw(PrefB.bpShowFollowButtonInButtonBar, R.string.show_follow_button_in_button_bar)
@ -789,10 +836,6 @@ val appSettingRoot = AppSettingItem(null, SettingType.Section, R.string.app_sett
)
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(PrefB.bpUseTwemoji, R.string.use_twemoji_emoji)
sw(PrefB.bpKeepReactionSpace, R.string.keep_reaction_space)
@ -1032,7 +1075,6 @@ val appSettingRoot = AppSettingItem(null, SettingType.Section, R.string.app_sett
sw(PrefB.bpEnableDeprecatedSomething,R.string.enable_deprecated_something)
sw(PrefB.bpEmojiPickerCategoryOther, R.string.show_emoji_picker_other_category)
action(R.string.drawable_list) {
action = { startActivity(Intent(this, ActDrawableList::class.java)) }
}

View File

@ -61,8 +61,8 @@ fun ColumnViewHolder.updateReactionQueryView() {
act,
column.accessInfo,
decodeEmoji = true,
enlargeEmoji = 1.5f,
enlargeCustomEmoji = 1.5f,
enlargeEmoji = DecodeOptions.emojiScaleReaction,
enlargeCustomEmoji = DecodeOptions.emojiScaleReaction,
emojiSizeMode = column.accessInfo.emojiSizeMode(),
)

View File

@ -624,6 +624,8 @@ internal class ViewHolderHeaderProfile(
emojiMapProfile = who.profile_emojis,
authorDomain = who,
emojiSizeMode = accessInfo.emojiSizeMode(),
enlargeCustomEmoji = DecodeOptions.emojiScaleUserName,
enlargeEmoji = DecodeOptions.emojiScaleUserName,
)
val nameTypeface = ActMain.timelineFontBold

View File

@ -290,7 +290,6 @@ private class EmojiPicker(
item.customEmoji.url
},
initialAspect = item.customEmoji.aspect,
defaultWidth = gridSize,
defaultHeight = gridSize,
)
}

View File

@ -10,6 +10,7 @@ import com.google.android.flexbox.FlexWrap
import com.google.android.flexbox.FlexboxLayout
import com.google.android.flexbox.JustifyContent
import jp.juggler.subwaytooter.ActMain
import jp.juggler.subwaytooter.ActMain.Companion.boostButtonSize
import jp.juggler.subwaytooter.R
import jp.juggler.subwaytooter.action.reactionAdd
import jp.juggler.subwaytooter.action.reactionFromAnotherAccount
@ -18,6 +19,7 @@ import jp.juggler.subwaytooter.api.entity.TootReaction
import jp.juggler.subwaytooter.api.entity.TootStatus
import jp.juggler.subwaytooter.pref.PrefB
import jp.juggler.subwaytooter.pref.PrefI
import jp.juggler.subwaytooter.span.NetworkEmojiSpan
import jp.juggler.subwaytooter.util.emojiSizeMode
import jp.juggler.subwaytooter.util.DecodeOptions
import jp.juggler.subwaytooter.util.NetworkEmojiInvalidator
@ -29,6 +31,7 @@ import jp.juggler.util.ui.attrColor
import jp.juggler.util.ui.getAdaptiveRippleDrawableRound
import org.jetbrains.anko.allCaps
import org.jetbrains.anko.dip
import org.jetbrains.anko.wrapContent
private val log = LogCategory("ItemViewHolderReaction")
@ -42,11 +45,10 @@ fun ItemViewHolder.makeReactionsView(status: TootStatus) {
fun Float.round() = (this + 0.5f).toInt()
val imageScale = 1.5f
val buttonHeight = ActMain.boostButtonSize // px
val marginBetween = (buttonHeight * 0.05f).round()
val paddingH = (buttonHeight * 0.1f).round()
val textHeight = (buttonHeight * 0.7f) / imageScale
val imageScale = DecodeOptions.emojiScaleReaction
val textHeight = (boostButtonSize.toFloat()/2)
val marginBetween = (boostButtonSize * 0.05f).round()
val paddingH = (boostButtonSize * 0.1f).round()
val act = this@makeReactionsView.activity // not Button(View).getActivity()
@ -63,7 +65,7 @@ fun ItemViewHolder.makeReactionsView(status: TootStatus) {
if (reactionSet?.isEmpty() != false) {
val v = View(act).apply {
layoutParams = FlexboxLayout.LayoutParams(0, buttonHeight)
layoutParams = FlexboxLayout.LayoutParams(0, wrapContent)
setPadding(paddingH, 0, paddingH, 0)
}
box.addView(v)
@ -85,13 +87,13 @@ fun ItemViewHolder.makeReactionsView(status: TootStatus) {
val b = AppCompatButton(act).apply {
layoutParams = FlexboxLayout.LayoutParams(
FlexboxLayout.LayoutParams.WRAP_CONTENT,
buttonHeight,
wrapContent,
).apply {
if (index > 0) startMargin = marginBetween
bottomMargin = dip(3)
}
gravity = Gravity.CENTER
minWidthCompat = buttonHeight
minWidthCompat = textHeight.round()
background = if (reactionSet.isMyReaction(reaction)) {
// 自分がリアクションしたやつは背景を変える

View File

@ -354,8 +354,8 @@ fun ItemViewHolder.showBoost(
activity,
accessInfo,
decodeEmoji = true,
enlargeEmoji = 1.5f,
enlargeCustomEmoji = 1.5f,
enlargeEmoji = DecodeOptions.emojiScaleReaction,
enlargeCustomEmoji = DecodeOptions.emojiScaleReaction,
emojiSizeMode = accessInfo.emojiSizeMode(),
)
val ssb = reaction.toSpannableStringBuilder(options, boostStatus)

View File

@ -61,7 +61,7 @@ object MisskeyMarkdownDecoder {
fun decodeMarkdown(options: DecodeOptions, src: String?) =
SpannableStringBuilderEx().apply {
val save = options.enlargeCustomEmoji
options.enlargeCustomEmoji = 2.5f
options.enlargeCustomEmoji = DecodeOptions.emojiScaleMisskey
try {
val env = SpanOutputEnv(options, this)

View File

@ -124,4 +124,9 @@ object PrefI {
val ipMediaBackground = IntPref("MediaBackground", 1)
val ipLogSaveLevel = IntPref("LogSaveLevel", Log.WARN)
const val EMOJI_WIDE_AUTO = 0
const val EMOJI_WIDE_ENABLE = 1
const val EMOJI_WIDE_DISABLE = 2
val ipEmojiWideMode = IntPref("EmojiWideMode", EMOJI_WIDE_AUTO)
}

View File

@ -53,4 +53,10 @@ object PrefS {
val spTimelineSpacing = StringPref("TimelineSpacing", "")
val spEventTextAlpha = StringPref("EventTextAlpha", "")
}
val spEmojiSizeMastodon = StringPref("EmojiSizeMastodon", "100")
val spEmojiSizeMisskey = StringPref("EmojiSizeMisskey", "250")
val spEmojiSizeReaction = StringPref("EmojiSizeReaction", "150")
val spEmojiSizeUserName = StringPref("EmojiSizeUserName", "100")
val spEmojiPixels = StringPref("spEmojiPixels", "128")
}

View File

@ -19,4 +19,5 @@ class StringPref(
defVal != pref.getString(key, defVal)
fun toInt() = value.toIntOrNull() ?: defVal.toInt()
fun toFloat() = value.toFloatOrNull() ?: defVal.toFloat()
}

View File

@ -21,13 +21,13 @@ class NetworkEmojiSpan constructor(
private val url: String,
sizeMode: EmojiSizeMode,
scale: Float = 1f,
private val initialAspect:Float? = null,
private val initialAspect: Float? = null,
private val errorDrawableId: Int = R.drawable.outline_broken_image_24,
) : ReplacementSpan(), AnimatableSpan {
companion object {
internal val log = LogCategory("NetworkEmojiSpan")
private const val scaleRatio = 1.14f
const val scaleRatio = 1.14f
private const val descentRatio = 0.211f
// 最大幅
@ -47,13 +47,23 @@ class NetworkEmojiSpan constructor(
private val rectSrc = Rect()
private var lastMeasuredWidth = 0f
private val emojiImageRect = EmojiImageRect(
sizeMode = sizeMode,
scale = scale,
scaleRatio = scaleRatio,
descentRatio = descentRatio,
maxEmojiWidth = maxEmojiWidth,
layout = { _,_-> invalidateCallback?.requestLayout() },
// layout = { _, _ ->
// when (val cb = invalidateCallback) {
// null -> log.w("layoutCb is null")
// else -> {
// log.i("layoutCb requestLayout. url=$url")
// cb.requestLayout()
// }
// }
// }
)
override fun setInvalidateCallback(
@ -72,8 +82,12 @@ class NetworkEmojiSpan constructor(
fm: Paint.FontMetricsInt?,
): Int {
emojiImageRect.updateRect(
url = url, aspectArg = initialAspect, paint.textSize, baseline = 0f
url = url,
aspectArg = initialAspect,
paint.textSize,
baseline = 0f
)
val height = (emojiImageRect.emojiHeight + 0.5f).toInt()
if (fm != null) {
val cDescent = (0.5f + height * descentRatio).toInt()
@ -83,7 +97,10 @@ class NetworkEmojiSpan constructor(
if (fm.descent < cDescent) fm.descent = cDescent
if (fm.bottom < cDescent) fm.bottom = cDescent
}
return (emojiImageRect.emojiWidth + 0.5f).toInt()
val width = emojiImageRect.emojiWidth
lastMeasuredWidth = width
return (width + 0.5f).toInt()
}
override fun draw(
@ -115,6 +132,7 @@ class NetworkEmojiSpan constructor(
// APNGデータの取得
val frames = App1.custom_emoji_cache.getFrames(refDrawTarget, url) {
handleFrameLoaded(it)
invalidateCallback.delayInvalidate(0L)
} ?: return false
@ -144,6 +162,17 @@ class NetworkEmojiSpan constructor(
textPaint.textSize,
baseline.toFloat()
)
val clipBounds = canvas.clipBounds
val clipWidth = clipBounds.width()
// 最後にgetSizeで返した幅と異なるか、現在のTextViewのClip幅より大きいなら
// 再レイアウトを要求する
if (emojiImageRect.emojiWidth != lastMeasuredWidth ){
log.i("requestLayout by width changed")
invalidateCallback.requestLayout()
}else if(emojiImageRect.emojiWidth > clipWidth) {
log.i("requestLayout by clipWidth ${emojiImageRect.emojiWidth}/${clipWidth}")
invalidateCallback.requestLayout()
}
canvas.save()
try {
@ -172,6 +201,12 @@ class NetworkEmojiSpan constructor(
return true
}
private fun handleFrameLoaded(frames: ApngFrames?) {
frames?.aspect?.let {
invalidateCallback?.requestLayout()
}
}
private fun drawError(
canvas: Canvas,
x: Float,
@ -190,7 +225,7 @@ class NetworkEmojiSpan constructor(
url = "",
aspectArg = srcWidth / srcHeight,
textSize = textPaint.textSize,
baseline = baseline.toFloat()
baseline = baseline.toFloat(),
)
canvas.save()

View File

@ -70,7 +70,9 @@ import java.util.concurrent.atomic.AtomicReference
// 2022/3/15 63=>64 SavedAccountテーブルに項目追加
// 2023/2/2 64 => 65 PushMessage, AccountNotificationStatus,NotificationShown テーブルの追加。
// 2023/2/11 65=>66 LogDataがなければ作る
const val DB_VERSION = 66
// 2023/2/26 66=>67 ImageAspect テーブルの追加
const val DB_VERSION = 67
const val DB_NAME = "app_db"
// テーブルのリスト
@ -96,6 +98,7 @@ val TABLE_LIST = arrayOf(
PushMessage.Companion, // v65
AccountNotificationStatus.Companion, // v65,
NotificationShown.Companion, // v65
ImageAspect.Companion, // v67
)
private val log = LogCategory("AppDatabaseHolder")
@ -201,3 +204,6 @@ val daoTagHistory get() = TagHistory.Access(appDatabase)
val daoUserRelation get() = UserRelation.Access(appDatabase)
val daoPushMessage get() = PushMessage.Access(appDatabase)
val daoLogData get() = LogData.Access(appDatabase)
val daoImageAspect by lazy{
ImageAspect.Access(appDatabase)
}

View File

@ -0,0 +1,64 @@
package jp.juggler.subwaytooter.table
import android.content.ContentValues
import android.database.Cursor
import android.database.sqlite.SQLiteDatabase
import jp.juggler.util.data.*
import jp.juggler.util.log.LogCategory
import kotlin.math.abs
// リスト要素のデータ
class ImageAspect(
var id: Long = 0L,
var url: String = "",
var aspect: Float = 1f,
) {
companion object : TableCompanion {
private val log = LogCategory("ImageAspect")
override val table = "image_aspect"
private const val COL_ID = "_id"
private const val COL_URL = "u"
private const val COL_ASPECT = "aspect"
val columnList = MetaColumns(table, 67).apply {
column(0, COL_ID, "INTEGER PRIMARY KEY")
column(0, COL_URL, "text not null")
column(0, COL_ASPECT, "real not null")
createExtra={
arrayOf(
"create unique index if not exists ${table}_u on $table($COL_URL)",
)
}
}
override fun onDBCreate(db: SQLiteDatabase) {
log.d("onDBCreate!")
columnList.onDBCreate(db)
}
override fun onDBUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
columnList.onDBUpgrade(db,oldVersion,newVersion)
}
}
class Access(val db: SQLiteDatabase) {
fun load(url:String) :Float?=
db.rawQuery(
"select $COL_ASPECT from $table where $COL_URL=?",
arrayOf(url),
).use { cursor->
when {
cursor.moveToNext() -> cursor.getFloat(0)
else -> null
}
}
fun save(url:String,aspect:Float) {
val oldAspect = load(url)
if( oldAspect != null && abs(oldAspect-aspect) <= 1.4E-40F) return
ContentValues().apply {
put(COL_URL, url)
put(COL_ASPECT, aspect)
}.replaceTo(db, table)
}
}
}

View File

@ -10,10 +10,14 @@ import android.os.SystemClock
import com.caverock.androidsvg.SVG
import jp.juggler.apng.ApngFrames
import jp.juggler.subwaytooter.App1
import jp.juggler.subwaytooter.pref.PrefS
import jp.juggler.subwaytooter.table.EmojiCacheDbOpenHelper
import jp.juggler.subwaytooter.table.daoImageAspect
import jp.juggler.util.coroutine.EmptyScope
import jp.juggler.util.data.*
import jp.juggler.util.log.*
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.launch
import java.io.ByteArrayInputStream
import java.lang.ref.WeakReference
import java.util.*
@ -45,7 +49,7 @@ class CustomEmojiCache(
private class Request(
val refTarget: WeakReference<Any>,
val url: String,
val onLoadComplete: () -> Unit,
val onLoadComplete: (ApngFrames?) -> Unit,
)
// APNGデコード済のキャッシュデータ
@ -115,7 +119,7 @@ class CustomEmojiCache(
fun getFrames(
refDrawTarget: WeakReference<Any>?,
url: String,
onLoadComplete: () -> Unit,
onLoadComplete: (ApngFrames?) -> Unit,
): ApngFrames? {
try {
if (refDrawTarget?.get() == null) {
@ -200,7 +204,7 @@ class CustomEmojiCache(
val item = getCached(now, request.url)
if (item != null) {
if (item.frames != null) {
fireCallback(request)
fireCallback(request, item.frames)
}
return@synchronized true
}
@ -231,7 +235,7 @@ class CustomEmojiCache(
data = try {
App1.getHttpCached(request.url)
} catch (ex: Throwable) {
log.w( "get failed. url=${request.url}")
log.w("get failed. url=${request.url}")
null
}
te = elapsedTime
@ -267,7 +271,7 @@ class CustomEmojiCache(
item.frames?.dispose()
item.frames = frames
}
fireCallback(request)
fireCallback(request, item.frames)
}
}
te = elapsedTime
@ -285,8 +289,15 @@ class CustomEmojiCache(
}
}
private fun fireCallback(request: Request) {
handler.post { request.onLoadComplete() }
private fun fireCallback(request: Request, frames: ApngFrames?) {
handler.post { request.onLoadComplete(frames) }
EmptyScope.launch {
try {
frames?.aspect?.let { daoImageAspect.save(request.url, it) }
} catch (ex: Throwable) {
log.e(ex, "aspect save failed.")
}
}
}
private fun sweepCache(now: Long) {
@ -295,7 +306,7 @@ class CustomEmojiCache(
val over = cache.size - CACHE_MAX
// 超過した数がある程度大きくなるまで掃除しない
if (over <= 64) return
if (over <= CACHE_MAX / 2) return
// 掃除する候補
val list = ArrayList<CacheItem>()
@ -319,7 +330,7 @@ class CustomEmojiCache(
private fun decodeAPNG(data: ByteArray, url: String): ApngFrames? {
val errors = ArrayList<Throwable>()
val maxSize = 256
val maxSize = PrefS.spEmojiPixels.toInt().clip(16, 1024)
try {
// APNGをデコード AWebPも

View File

@ -9,9 +9,10 @@ import jp.juggler.subwaytooter.api.entity.NicoProfileEmoji
import jp.juggler.subwaytooter.api.entity.TootAttachmentLike
import jp.juggler.subwaytooter.api.entity.TootMention
import jp.juggler.subwaytooter.emoji.CustomEmoji
import jp.juggler.subwaytooter.util.EmojiSizeMode
import jp.juggler.subwaytooter.pref.PrefS
import jp.juggler.subwaytooter.table.HighlightWord
import jp.juggler.util.data.WordTrieTree
import jp.juggler.util.data.clip
import org.jetbrains.anko.collections.forEachReversedByIndex
class DecodeOptions(
@ -25,8 +26,8 @@ class DecodeOptions(
var emojiMapProfile: Map<String, NicoProfileEmoji>? = null,
var highlightTrie: WordTrieTree? = null,
var unwrapEmojiImageTag: Boolean = false,
var enlargeCustomEmoji: Float = 1f,
var enlargeEmoji: Float = 1f,
var enlargeCustomEmoji: Float = emojiScaleMastodon,
var enlargeEmoji: Float = emojiScaleMastodon,
// force use HTML instead of Misskey Markdown
var forceHtml: Boolean = false,
var mentionFullAcct: Boolean = false,
@ -36,6 +37,18 @@ class DecodeOptions(
var authorDomain: HostAndDomain? = null,
var emojiSizeMode: EmojiSizeMode = EmojiSizeMode.Square,
) {
companion object {
var emojiScaleMastodon = 1f
var emojiScaleMisskey = 1f
var emojiScaleReaction = 1f
var emojiScaleUserName = 1f
fun reloadEmojiScale() {
emojiScaleMastodon = PrefS.spEmojiSizeMastodon.toFloat().div(100f).clip(0.5f, 5f)
emojiScaleMisskey = PrefS.spEmojiSizeMisskey.toFloat().div(100f).clip(0.5f, 5f)
emojiScaleReaction = PrefS.spEmojiSizeReaction.toFloat().div(100f).clip(0.5f, 5f)
emojiScaleUserName = PrefS.spEmojiSizeUserName.toFloat().div(100f).clip(0.5f, 5f)
}
}
internal fun isMediaAttachment(url: String?): Boolean =
url?.let { u -> attachmentList?.any { it.hasUrl(u) } } ?: false

View File

@ -380,11 +380,11 @@ object EmojiDecoder {
val userHost = cols.elementAtOrNull(1)
?: options.authorDomain?.apiHost?.ascii
?: apiHostAscii
log.i(
"decodeEmoji Misskey13 c0=${cols.elementAtOrNull(0)} c1=${
cols.elementAtOrNull(1)
} apiHostAscii=$apiHostAscii, userHost=$userHost"
)
// log.i(
// "decodeEmoji Misskey13 c0=${cols.elementAtOrNull(0)} c1=${
// cols.elementAtOrNull(1)
// } apiHostAscii=$apiHostAscii, userHost=$userHost"
// )
when {
// 絵文字プロクシを利用できない

View File

@ -2,7 +2,9 @@ package jp.juggler.subwaytooter.util
import android.graphics.RectF
import jp.juggler.subwaytooter.api.entity.TootInstance
import jp.juggler.subwaytooter.pref.PrefI
import jp.juggler.subwaytooter.table.SavedAccount
import jp.juggler.subwaytooter.table.daoImageAspect
import jp.juggler.util.log.LogCategory
import kotlin.math.min
@ -11,14 +13,19 @@ enum class EmojiSizeMode {
Wide,
}
fun SavedAccount?.emojiSizeMode(): EmojiSizeMode {
val ti = this?.let { TootInstance.getCached(it) }
return when {
ti == null -> EmojiSizeMode.Square
ti.isMisskey || !ti.fedibirdCapabilities.isNullOrEmpty() -> EmojiSizeMode.Wide
else -> EmojiSizeMode.Square
fun SavedAccount?.emojiSizeMode(): EmojiSizeMode =
when (PrefI.ipEmojiWideMode.value) {
PrefI.EMOJI_WIDE_ENABLE -> EmojiSizeMode.Wide
PrefI.EMOJI_WIDE_DISABLE -> EmojiSizeMode.Square
else /* PrefI.EMOJI_WIDE_AUTO */ -> {
val ti = this?.let { TootInstance.getCached(it) }
when {
ti == null -> EmojiSizeMode.Square
ti.isMisskey || !ti.fedibirdCapabilities.isNullOrEmpty() -> EmojiSizeMode.Wide
else -> EmojiSizeMode.Square
}
}
}
}
/**
* カスタム絵文字のSpanやビューの描画領域やサイズを計算する
@ -29,7 +36,7 @@ class EmojiImageRect(
val scaleRatio: Float = 1f,
val descentRatio: Float = 0f,
val maxEmojiWidth: Float,
val layout: (Int, Int) -> Unit,
// val layout: (Int, Int) -> Unit,
) {
companion object {
private val log = LogCategory("EmojiImageRect")
@ -42,8 +49,6 @@ class EmojiImageRect(
var emojiHeight = 0f
var transY = 0f
var lastWidth: Float? = null
/**
* lastAspect に基づいて rectDst transY を更新する
*/
@ -69,9 +74,13 @@ class EmojiImageRect(
) {
this.emojiHeight = h
val aspect = when (aspectArg) {
null -> imageAspectCache[url] ?: 1f
null -> {
imageAspectCache[url]
?: daoImageAspect.load(url)?.also { imageAspectCache[url] = it }
?: 1f
}
else -> {
imageAspectCache.put(url, aspectArg)
if (url.isNotEmpty()) imageAspectCache[url] = aspectArg
aspectArg
}
}.takeIf { it > 0f } ?: 1f
@ -89,7 +98,6 @@ class EmojiImageRect(
}
else -> {
emojiWidth = emojiHeight
// 絵文字のアスペクト比から描画範囲の幅と高さを決める
val dstWidth: Float
val dstHeight: Float
@ -103,14 +111,8 @@ class EmojiImageRect(
val dstX = (emojiHeight - dstWidth) / 2f
val dstY = (emojiHeight - dstHeight) / 2f
rectDst.set(dstX, dstY, dstX + dstWidth, dstY + dstHeight)
emojiWidth = emojiHeight
}
}
// 出力サイズが変化したならrequestLayout
val newWidth = emojiWidth
if (lastWidth != null && lastWidth != newWidth) {
log.i("updateRect: width changed. $lastWidth$newWidth")
layout((emojiWidth + 0.5f).toInt(), (emojiHeight + 0.5f).toInt())
}
lastWidth = newWidth
}
}

View File

@ -8,6 +8,7 @@ import jp.juggler.subwaytooter.App1
import jp.juggler.subwaytooter.span.AnimatableSpan
import jp.juggler.subwaytooter.span.AnimatableSpanInvalidator
import jp.juggler.util.data.clip
import jp.juggler.util.log.LogCategory
import java.lang.ref.WeakReference
class NetworkEmojiInvalidator(
@ -15,6 +16,9 @@ class NetworkEmojiInvalidator(
internal val view: TextView,
// val parent:View? = null,
) : AnimatableSpanInvalidator {
companion object {
private val log = LogCategory("NetworkEmojiInvalidator")
}
var text: CharSequence
get() = view.text

View File

@ -55,25 +55,23 @@ class NetworkEmojiView(
scaleRatio = 1f,
descentRatio = 0f,
maxEmojiWidth = maxEmojiWidth,
layout = { w, h ->
val lp = layoutParams
lp.width = w
lp.height = h
layoutParams = lp
requestLayout()
},
// layout = { w, h ->
// val lp = layoutParams
// lp.width = w
// lp.height = h
// layoutParams = lp
// requestLayout()
// },
)
fun setEmoji(
url: String?,
initialAspect: Float?,
defaultWidth: Int,
defaultHeight: Int,
) {
this.url = url
mPaint.isFilterBitmap = true
invalidate()
emojiImageRect.lastWidth = null
emojiImageRect.updateRect(
url = url ?: "",
aspectArg = initialAspect,
@ -130,6 +128,13 @@ class NetworkEmojiView(
aspectArg = srcAspect,
h = dstHeight,
)
val width = (emojiImageRect.emojiWidth + 0.5f).toInt()
if (width != layoutParams.width) {
val lp = layoutParams
lp.width = width
lp.height = (emojiImageRect.emojiHeight + 0.5f).toInt()
layoutParams = lp
}
canvas.drawBitmap(b, rectSrc, emojiImageRect.rectDst, mPaint)

View File

@ -1253,7 +1253,7 @@
<string name="approved">承認されました。</string>
<string name="account_list">アカウント一覧</string>
<string name="show_username_on_filtered_post">フィルタされた投稿にユーザ名を表示する</string>
<string name="log_save_level" >ログ収集レベル</string>
<string name="log_save_level">ログ収集レベル</string>
<string name="send_log_email">ログを電子メールで送信する</string>
<string name="bug_report">バグ報告</string>
<string name="bug_report_desc">アプリは過去数日間のログを保持していますが、勝手に外部に送信することはありません。ユーザが明示的にログを送信する操作した時だけ参照されます。</string>
@ -1261,4 +1261,12 @@
<string name="image_alpha_too_low">背景画像のアルファが低すぎます。 画像を見ることができますか?</string>
<string name="enable_deprecated_something">陳腐化した何かを有効にする</string>
<string name="misskey_support_end">Misskeyサポートは終了しました</string>
<string name="emoji_size_mastodon">(Mastodon)カスタム絵文字の大きさ(単位:%、デフォルト:100)</string>
<string name="emoji_size_misskey">(Misskey)カスタム絵文字の大きさ(単位:%、デフォルト:250)</string>
<string name="emoji_size_reaction">リアクションの絵文字の大きさ(単位:%、デフォルト:150)</string>
<string name="emoji_size_user_name">ユーザ名の絵文字の大きさ(単位:%、デフォルト:100)</string>
<string name="emoji_wide_mode">ワイドカスタム絵文字</string>
<string name="enabled">有効</string>
<string name="disabled">無効</string>
<string name="emoji_texture_pixels">絵文字テクスチャの最大ピクセル数(単位:ピクセル、デフォルト: 256。 タスクキルが必要。端末のRAMが少ない場合は64程度まで下げることをお勧めします)</string>
</resources>

View File

@ -1269,4 +1269,12 @@
<string name="image_alpha_too_low">background image alpha is too low. can you view the image?</string>
<string name="enable_deprecated_something">Enable deprecated something</string>
<string name="misskey_support_end">Misskey support has ended</string>
<string name="emoji_size_mastodon">(Mastodon)Custom emoji scaling(Unit:%. default:100)</string>
<string name="emoji_size_misskey">(Misskey)Custom emoji scaling(Unit:%。default:250)</string>
<string name="emoji_size_reaction">Reaction emoji scaling(Unit:%。default:150)</string>
<string name="emoji_size_user_name">User name emoji scaling(Unit:%。default:100)</string>
<string name="emoji_wide_mode">Wide custom emoji</string>
<string name="enabled">Enabled</string>
<string name="disabled">Disabled</string>
<string name="emoji_texture_pixels">Emoji texture max pixels(Unix:pixels, default: 256. task kill required. reduce to 64 if your device\'s RAM is not enough)</string>
</resources>

View File

@ -119,7 +119,7 @@ for my $lang ( sort keys %langs ){
my $sv = $value;
$sv =~ s/(%\d+\$[\d\.]*[sdxf])//g;
# Unit:%. や %% を除外したい
$sv =~ s/%[\s.。%]//g;
$sv =~ s/%[\s.,%]//g;
if( $sv =~ /%/ ){
$hasError =1;
print "!! ($lang)$name : broken param: $sv // $value\n";