diff --git a/app/src/main/java/jp/juggler/subwaytooter/ActMain.kt b/app/src/main/java/jp/juggler/subwaytooter/ActMain.kt index 669e3713..994acc12 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/ActMain.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/ActMain.kt @@ -1113,7 +1113,6 @@ class ActMain : AppCompatActivity() } catch(ex : Throwable) { log.trace(ex) } - } sv = Pref.spTimelineFontBold(pref) @@ -1124,13 +1123,12 @@ class ActMain : AppCompatActivity() log.trace(ex) } - } else if(timeline_font != null) { + } else { try { timeline_font_bold = Typeface.create(timeline_font, Typeface.BOLD) } catch(ex : Throwable) { log.trace(ex) } - } fun parseIconSize(stringPref : Pref.StringPref) : Int { diff --git a/app/src/main/java/jp/juggler/subwaytooter/ColumnViewHolder.kt b/app/src/main/java/jp/juggler/subwaytooter/ColumnViewHolder.kt index b0f88985..657bc2b7 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/ColumnViewHolder.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/ColumnViewHolder.kt @@ -163,17 +163,15 @@ class ColumnViewHolder( init { - if(ActMain.timeline_font != null) { - viewRoot.scan { v -> - try { - if(v is Button) { - // ボタンは触らない - } else if(v is TextView) { - v.typeface = ActMain.timeline_font - } - } catch(ex : Throwable) { - log.trace(ex) + viewRoot.scan { v -> + try { + if(v is Button) { + // ボタンは触らない + } else if(v is TextView) { + v.typeface = ActMain.timeline_font } + } catch(ex : Throwable) { + log.trace(ex) } } diff --git a/app/src/main/java/jp/juggler/subwaytooter/ItemViewHolder.kt b/app/src/main/java/jp/juggler/subwaytooter/ItemViewHolder.kt index 3feebf4c..a3b830e3 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/ItemViewHolder.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/ItemViewHolder.kt @@ -242,36 +242,31 @@ internal class ItemViewHolder( this.access_info = column.access_info - if(ActMain.timeline_font != null || ActMain.timeline_font_bold != null) { - val font_bold = ActMain.timeline_font_bold ?: ActMain.timeline_font - val font_normal = ActMain.timeline_font ?: ActMain.timeline_font_bold - viewRoot.scan { v -> - try { - if(v is CountImageButton) { - // ボタンは太字なので触らない - } else if(v is Button) { - // ボタンは太字なので触らない - } else if(v is TextView) { - val typeface = when { - v === tvName || - v === tvFollowerName || - v === tvBoosted || - v === tvTrendTagCount || - v === tvTrendTagName || - v === tvFilterPhrase -> font_bold - else -> font_normal - } - if(typeface != null) v.typeface = typeface + val font_bold = ActMain.timeline_font_bold + val font_normal = ActMain.timeline_font + viewRoot.scan { v -> + try { + when(v) { + // ボタンは太字なので触らない + is CountImageButton -> { + } + // ボタンは太字なので触らない + is Button -> { + } + + is TextView -> v.typeface = when { + v === tvName || + v === tvFollowerName || + v === tvBoosted || + v === tvTrendTagCount || + v === tvTrendTagName || + v === tvFilterPhrase -> font_bold + else -> font_normal } - } catch(ex : Throwable) { - log.trace(ex) } + } catch(ex : Throwable) { + log.trace(ex) } - } else { - tvName.typeface = Typeface.DEFAULT_BOLD - tvFollowerName.typeface = Typeface.DEFAULT_BOLD - tvBoosted.typeface = Typeface.DEFAULT_BOLD - tvTrendTagCount.typeface = Typeface.DEFAULT_BOLD } if(bSimpleList) { diff --git a/app/src/main/java/jp/juggler/subwaytooter/ViewHolderHeaderBase.kt b/app/src/main/java/jp/juggler/subwaytooter/ViewHolderHeaderBase.kt index 78a1fac6..90c4d98b 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/ViewHolderHeaderBase.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/ViewHolderHeaderBase.kt @@ -24,9 +24,7 @@ internal abstract class ViewHolderHeaderBase(val activity : ActMain, val viewRoo if(v is Button) { // ボタンは太字なので触らない } else if(v is TextView) { - if(ActMain.timeline_font != null) { - v.typeface = ActMain.timeline_font - } + v.typeface = ActMain.timeline_font if(! activity.timeline_font_size_sp.isNaN()) { v.textSize = activity.timeline_font_size_sp } diff --git a/app/src/main/java/jp/juggler/subwaytooter/ViewHolderHeaderProfile.kt b/app/src/main/java/jp/juggler/subwaytooter/ViewHolderHeaderProfile.kt index ae6e495f..abae1f3c 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/ViewHolderHeaderProfile.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/ViewHolderHeaderProfile.kt @@ -262,8 +262,8 @@ internal class ViewHolderHeaderProfile( val content_color = column.content_color val c = if(content_color != 0) content_color else default_color - val nameTypeface = ActMain.timeline_font_bold ?: Typeface.DEFAULT_BOLD - val valueTypeface = ActMain.timeline_font ?: Typeface.DEFAULT + val nameTypeface = ActMain.timeline_font_bold + val valueTypeface = ActMain.timeline_font for(item in who.fields) { diff --git a/app/src/main/java/jp/juggler/subwaytooter/span/AnimatableSpan.kt b/app/src/main/java/jp/juggler/subwaytooter/span/AnimatableSpan.kt new file mode 100644 index 00000000..78fac150 --- /dev/null +++ b/app/src/main/java/jp/juggler/subwaytooter/span/AnimatableSpan.kt @@ -0,0 +1,16 @@ +package jp.juggler.subwaytooter.span + + + +interface AnimatableSpanInvalidator { + val timeFromStart : Long + fun delayInvalidate(delay : Long) +} + +interface AnimatableSpan{ + + fun setInvalidateCallback( + draw_target_tag : Any, + invalidate_callback : AnimatableSpanInvalidator + ) +} diff --git a/app/src/main/java/jp/juggler/subwaytooter/span/MisskeySpan.kt b/app/src/main/java/jp/juggler/subwaytooter/span/MisskeySpan.kt new file mode 100644 index 00000000..5dca5212 --- /dev/null +++ b/app/src/main/java/jp/juggler/subwaytooter/span/MisskeySpan.kt @@ -0,0 +1,129 @@ +package jp.juggler.subwaytooter.span + +import android.graphics.Color +import android.graphics.Paint +import android.graphics.Typeface +import android.text.TextPaint +import android.text.style.MetricAffectingSpan +import java.lang.ref.WeakReference +import java.util.* + +class MisskeyBigSpan ( + private val typeface : Typeface +): MetricAffectingSpan() ,AnimatableSpan{ + + private var invalidate_callback : AnimatableSpanInvalidator? = null + private var refDrawTarget : WeakReference? = null + + override fun setInvalidateCallback( + draw_target_tag : Any, + invalidate_callback : AnimatableSpanInvalidator + ) { + this.refDrawTarget = WeakReference(draw_target_tag) + this.invalidate_callback = invalidate_callback + } + + private val textScalingMax = 1.5f + + override fun updateMeasureState(paint : TextPaint) { + apply(paint,bDrawing=false) + } + + override fun updateDrawState(drawState : TextPaint) { + apply(drawState,bDrawing=true) + } + + + private fun apply(paint : Paint,bDrawing:Boolean ) { + + + val textScaling = when{ + !bDrawing -> textScalingMax + else -> { + invalidate_callback?.delayInvalidate(100L) + val t = (invalidate_callback?.timeFromStart ?: 0L) /500f + + textScalingMax * ( Math.sin(t.toDouble()).toFloat() * 0.1f + 0.9f) + } + } + + paint.textSize = paint.textSize * textScaling + + val oldTypeface = paint.typeface + val oldStyle = oldTypeface?.style ?: 0 + val fakeStyle = oldStyle and typeface.style.inv() + + if(fakeStyle and Typeface.BOLD != 0) { + paint.isFakeBoldText = true + } + + if(fakeStyle and Typeface.ITALIC != 0) { + paint.textSkewX = - 0.25f + } + + paint.typeface = typeface + + } + + +} + +class MisskeyMotionSpan ( + private val typeface : Typeface +): MetricAffectingSpan() ,AnimatableSpan{ + + private var invalidate_callback : AnimatableSpanInvalidator? = null + private var refDrawTarget : WeakReference? = null + + override fun setInvalidateCallback( + draw_target_tag : Any, + invalidate_callback : AnimatableSpanInvalidator + ) { + this.refDrawTarget = WeakReference(draw_target_tag) + this.invalidate_callback = invalidate_callback + } + + private val textScalingMax = 1.0f + + override fun updateMeasureState(paint : TextPaint) { + apply(paint,bDrawing=false) + } + + override fun updateDrawState(drawState : TextPaint) { + apply(drawState,bDrawing=true) + } + + + private fun apply(paint : Paint,bDrawing:Boolean ) { + + + val textScaling = when{ + !bDrawing -> textScalingMax + else -> { + invalidate_callback?.delayInvalidate(100L) + val t = (invalidate_callback?.timeFromStart ?: 0L) /100f + + textScalingMax * ( Math.sin(t.toDouble()).toFloat() * 0.1f + 0.9f) + } + } + + paint.textSize = paint.textSize * textScaling + + val oldTypeface = paint.typeface + val oldStyle = oldTypeface?.style ?: 0 + val fakeStyle = oldStyle and typeface.style.inv() + + if(fakeStyle and Typeface.BOLD != 0) { + paint.isFakeBoldText = true + } + + if(fakeStyle and Typeface.ITALIC != 0) { + paint.textSkewX = - 0.25f + } + + paint.typeface = typeface + + } + + +} \ No newline at end of file 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 c7ebac35..33f69cf5 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/span/NetworkEmojiSpan.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/span/NetworkEmojiSpan.kt @@ -13,7 +13,7 @@ import jp.juggler.subwaytooter.Pref import jp.juggler.subwaytooter.util.LogCategory import java.lang.ref.WeakReference -class NetworkEmojiSpan internal constructor(private val url : String) : ReplacementSpan() { +class NetworkEmojiSpan internal constructor(private val url : String) : ReplacementSpan(),AnimatableSpan { companion object { @@ -27,9 +27,7 @@ class NetworkEmojiSpan internal constructor(private val url : String) : Replacem private val rect_src = Rect() private val rect_dst = RectF() - private var invalidate_callback : InvalidateCallback? = null - private var refDrawTarget : WeakReference? = null - + // フレーム探索結果を格納する構造体を確保しておく private val mFrameFindResult = ApngFrames.FindFrameResult() @@ -37,18 +35,17 @@ class NetworkEmojiSpan internal constructor(private val url : String) : Replacem mPaint.isFilterBitmap = true } - interface InvalidateCallback { - val timeFromStart : Long - fun delayInvalidate(delay : Long) - } + private var invalidate_callback : AnimatableSpanInvalidator? = null + private var refDrawTarget : WeakReference? = null - fun setInvalidateCallback( + override fun setInvalidateCallback( draw_target_tag : Any, - invalidate_callback : InvalidateCallback + invalidate_callback : AnimatableSpanInvalidator ) { this.refDrawTarget = WeakReference(draw_target_tag) this.invalidate_callback = invalidate_callback } + override fun getSize( paint : Paint, 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 487e6328..203d8e26 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/util/MisskeyMarkdownDecoder.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/util/MisskeyMarkdownDecoder.kt @@ -15,9 +15,7 @@ import jp.juggler.subwaytooter.ActMain import jp.juggler.subwaytooter.App1 import jp.juggler.subwaytooter.Pref import jp.juggler.subwaytooter.R -import jp.juggler.subwaytooter.span.EmojiImageSpan -import jp.juggler.subwaytooter.span.HighlightSpan -import jp.juggler.subwaytooter.span.MyClickableSpan +import jp.juggler.subwaytooter.span.* import jp.juggler.subwaytooter.table.HighlightWord import uk.co.chrisjenx.calligraphy.CalligraphyTypefaceSpan import java.util.regex.Pattern @@ -656,6 +654,16 @@ object MisskeyMarkdownDecoder { generateMotionNodeParser(NodeType.Motion) ) + + private val reStartEmptyLines = """\A(?:[  ]*?[\x0d\x0a]+)+""".toRegex() + private val reEndEmptyLines = """[\s\x0d\x0a]+\z""".toRegex() + private fun trimBlock(s:String?):String?{ + s?:return null + return s + .replace(reStartEmptyLines,"") + .replace(reEndEmptyLines,"") + } + private fun parse(source : String?) : ArrayList { val result = ArrayList() @@ -762,7 +770,7 @@ object MisskeyMarkdownDecoder { try { if(src != null) { - val font_bold = ActMain.timeline_font_bold ?: ActMain.timeline_font + val font_bold = ActMain.timeline_font_bold for( node in parse(src) ){ val nodeSource = src.substring(node.sourceStart,node.sourceStart+node.sourceLength) @@ -884,58 +892,13 @@ object MisskeyMarkdownDecoder { appendLink(acct,"https://${linkHelper}/$acct") } } + NodeType.Hashtag ->{ val tag = data?.get(0) if(tag?.isNotEmpty()==true){ appendLink("#$tag","https://misskey.m544.net/tags/"+tag.encodePercent()) } } - NodeType.Text->{ - appendText(nodeSource) - } - NodeType.Big ->{ - appendText(data?.get(0)) - setSpan(RelativeSizeSpan(1.5f)) - setSpan(CalligraphyTypefaceSpan(font_bold)) - // TODO アニメーション - } - - NodeType.Bold->{ - appendText(data?.get(0)) - setSpan(CalligraphyTypefaceSpan(font_bold)) - } - NodeType.Title->{ - appendText(data?.get(0)?.trim{it<=' '}) - setSpan(AlignmentSpan.Standard(Layout.Alignment.ALIGN_CENTER)) - // TODO 見出し背景に色をつける - - // ブロック要素なので改行必須 - appendText("\n") - } - NodeType.CodeBlock->{ - appendTextCode(data?.get(0)?.trimEnd()) - setSpan(BackgroundColorSpan(0x40808080)) - setSpan(CalligraphyTypefaceSpan(Typeface.MONOSPACE)) - - // ブロック要素なので改行必須 - appendText("\n") - - // TODO Syntax highlight - } - - NodeType.CodeInline->{ - appendTextCode(data?.get(0)) - setSpan(BackgroundColorSpan(0x40808080)) - setSpan(CalligraphyTypefaceSpan(Typeface.MONOSPACE)) - // TODO Syntax highlight - } - - NodeType.Quote->{ - appendText(data?.get(0)?.trim()) - setSpan(CalligraphyTypefaceSpan(Typeface.defaultFromStyle(Typeface.ITALIC))) - // ブロック要素なので改行必須 - appendText("\n") - } NodeType.Emoji->{ val code = data?.get(0) @@ -944,23 +907,74 @@ object MisskeyMarkdownDecoder { } } - NodeType.Search->{ - val text = data?.get(0) - if( text?.isNotEmpty()==true){ - appendText(text) - start = sb.length - appendLink( - context.getString(R.string.search), - "https://www.google.co.jp/search?q="+text.encodePercent() - ) - // ブロック要素なので改行必須 - appendText("\n") - } + //////////////////////////////////////////// + // 装飾インライン要素 + + NodeType.Text->{ + appendText(nodeSource) } + + NodeType.Big ->{ + appendText(data?.get(0)) + setSpan(MisskeyBigSpan(font_bold)) + } + NodeType.Motion->{ val code = data?.get(0) appendText(code) - // TODO なんかする + setSpan(MisskeyMotionSpan(ActMain.timeline_font)) + } + + NodeType.Bold->{ + appendText(data?.get(0)) + setSpan(CalligraphyTypefaceSpan(font_bold)) + } + + NodeType.CodeInline->{ + appendTextCode(data?.get(0)) + setSpan(BackgroundColorSpan(0x40808080)) + setSpan(CalligraphyTypefaceSpan(Typeface.MONOSPACE)) + } + + //////////////////////////////////////////// + // ブロック要素 + + NodeType.Title->{ + + appendText(trimBlock(data?.get(0))) + setSpan(AlignmentSpan.Standard(Layout.Alignment.ALIGN_CENTER)) + setSpan(BackgroundColorSpan(0x20808080)) + setSpan(RelativeSizeSpan(1.5f)) + appendText("\n") + } + + NodeType.CodeBlock->{ + appendTextCode(trimBlock(data?.get(0))) + setSpan(BackgroundColorSpan(0x40808080)) + setSpan(RelativeSizeSpan(0.7f)) + setSpan(CalligraphyTypefaceSpan(Typeface.MONOSPACE)) + appendText("\n") + } + + NodeType.Quote->{ + appendText(trimBlock(data?.get(0))) + setSpan(BackgroundColorSpan(0x20808080)) + setSpan(CalligraphyTypefaceSpan(Typeface.defaultFromStyle(Typeface.ITALIC))) + appendText("\n") + } + + NodeType.Search->{ + val text = data?.get(0) + val kw_start = sb.length // キーワードの開始位置 + appendText(text) + appendText(" ") + start = sb.length // 検索リンクの開始位置 + appendLink( + context.getString(R.string.search), + "https://www.google.co.jp/search?q="+(text?:"Subway Tooter").encodePercent() + ) + sb.setSpan(RelativeSizeSpan(1.5f), kw_start, sb.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) + appendText("\n") } } } diff --git a/app/src/main/java/jp/juggler/subwaytooter/util/NetworkEmojiInvalidator.kt b/app/src/main/java/jp/juggler/subwaytooter/util/NetworkEmojiInvalidator.kt index 5eaf852f..a0bc15cd 100644 --- a/app/src/main/java/jp/juggler/subwaytooter/util/NetworkEmojiInvalidator.kt +++ b/app/src/main/java/jp/juggler/subwaytooter/util/NetworkEmojiInvalidator.kt @@ -8,10 +8,11 @@ import android.view.View import java.util.ArrayList import jp.juggler.subwaytooter.App1 -import jp.juggler.subwaytooter.span.NetworkEmojiSpan +import jp.juggler.subwaytooter.span.AnimatableSpan +import jp.juggler.subwaytooter.span.AnimatableSpanInvalidator import java.lang.ref.WeakReference -class NetworkEmojiInvalidator(internal val handler : Handler, internal val view : View) : Runnable, NetworkEmojiSpan.InvalidateCallback { +class NetworkEmojiInvalidator(internal val handler : Handler, internal val view : View) : Runnable, AnimatableSpanInvalidator { private val draw_target_list = ArrayList>() @@ -41,7 +42,7 @@ class NetworkEmojiInvalidator(internal val handler : Handler, internal val view draw_target_list.clear() if(dst != null) { - for(span in dst.getSpans(0, dst.length, NetworkEmojiSpan::class.java)) { + for(span in dst.getSpans(0, dst.length, AnimatableSpan::class.java)) { val tag = WeakReference(Any()) draw_target_list.add(tag) span.setInvalidateCallback(tag, this)