improved custom emoji #990
This commit is contained in:
parent
e3251bc72b
commit
ed8fbd662e
|
@ -33,6 +33,6 @@ class ComposeActivityTestRule(initialTouchMode: Boolean = false, launchActivity:
|
|||
}
|
||||
|
||||
override fun afterActivityFinished() {
|
||||
TestAccountUtils.removeTestAccounts()
|
||||
TestAccountUtils.removeTestAccounts().get()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
/*
|
||||
* Twidere - Twitter client for Android
|
||||
*
|
||||
* Copyright (C) 2012-2017 Mariotaku Lee <mariotaku.lee@gmail.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.mariotaku.twidere.extension
|
||||
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.ColorFilter
|
||||
import android.graphics.PixelFormat
|
||||
import android.graphics.Rect
|
||||
import android.graphics.drawable.Drawable
|
||||
import junit.framework.Assert
|
||||
import org.junit.Test
|
||||
|
||||
class DrawableExtensionsKtTest {
|
||||
@Test
|
||||
fun setBoundsFitCenter() {
|
||||
val drawable = TestDrawable()
|
||||
drawable.setBoundsFitCenter(0, 0, 100, 100)
|
||||
Assert.assertEquals(Rect(25, 0, 75, 100), drawable.bounds)
|
||||
|
||||
drawable.setBoundsFitCenter(0, 0, 50, 100)
|
||||
Assert.assertEquals(Rect(0, 0, 50, 100), drawable.bounds)
|
||||
|
||||
drawable.setBoundsFitCenter(0, 0, 10, 100)
|
||||
Assert.assertEquals(Rect(0, 40, 10, 60), drawable.bounds)
|
||||
}
|
||||
|
||||
private class TestDrawable : Drawable() {
|
||||
override fun draw(canvas: Canvas) {
|
||||
}
|
||||
|
||||
override fun setAlpha(alpha: Int) {
|
||||
}
|
||||
|
||||
override fun getOpacity(): Int {
|
||||
return PixelFormat.TRANSLUCENT
|
||||
}
|
||||
|
||||
override fun setColorFilter(colorFilter: ColorFilter?) {
|
||||
}
|
||||
|
||||
override fun getIntrinsicWidth(): Int {
|
||||
return 100
|
||||
}
|
||||
|
||||
override fun getIntrinsicHeight(): Int {
|
||||
return 200
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -21,15 +21,15 @@ package org.mariotaku.twidere.util
|
|||
|
||||
import android.accounts.AccountManager
|
||||
import android.support.test.InstrumentationRegistry
|
||||
import nl.komponents.kovenant.Promise
|
||||
import nl.komponents.kovenant.task
|
||||
import org.mariotaku.twidere.extension.model.updateDetails
|
||||
import org.mariotaku.twidere.model.AccountDetails
|
||||
import org.mariotaku.twidere.model.util.AccountUtils
|
||||
import org.mariotaku.twidere.test.R
|
||||
import org.mariotaku.twidere.util.support.removeAccountSupport
|
||||
import java.lang.Exception
|
||||
|
||||
/**
|
||||
* Created by mariotaku on 2017/4/16.
|
||||
*/
|
||||
object TestAccountUtils {
|
||||
|
||||
private val accountResources = intArrayOf(R.raw.account_4223092274_twitter_com)
|
||||
|
@ -51,10 +51,12 @@ object TestAccountUtils {
|
|||
}
|
||||
}
|
||||
|
||||
fun removeTestAccounts() {
|
||||
fun removeTestAccounts(): Promise<Unit, Exception> {
|
||||
val targetContext = InstrumentationRegistry.getTargetContext()
|
||||
val am = AccountManager.get(targetContext)
|
||||
val existingAccounts = AccountUtils.getAllAccountDetails(am, false)
|
||||
existingAccounts.filter { it.test }.forEach { am.removeAccountSupport(it.account) }
|
||||
return task {
|
||||
existingAccounts.filter { it.test }.forEach { am.removeAccountSupport(it.account).result }
|
||||
}
|
||||
}
|
||||
}
|
|
@ -26,7 +26,6 @@ import android.view.View
|
|||
|
||||
internal abstract class AccessorHeaderScrollingViewBehavior(context: Context, attrs: AttributeSet? = null) : HeaderScrollingViewBehavior(context, attrs) {
|
||||
internal val tempRect1 = mTempRect1
|
||||
internal val tempRect2 = mTempRect2
|
||||
|
||||
internal override fun getOverlapRatioForOffset(header: View): Float {
|
||||
return super.getOverlapRatioForOffset(header)
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* Twidere - Twitter client for Android
|
||||
*
|
||||
* Copyright (C) 2012-2017 Mariotaku Lee <mariotaku.lee@gmail.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.mariotaku.twidere.extension
|
||||
|
||||
import android.graphics.drawable.Drawable
|
||||
|
||||
fun Drawable.setBoundsFitCenter(left: Int, top: Int, right: Int, bottom: Int) {
|
||||
val boundsWidth = right - left
|
||||
val boundsHeight = bottom - top
|
||||
if (intrinsicWidth <= 0 || intrinsicHeight <= 0 || boundsWidth <= 0 || boundsHeight <= 0) {
|
||||
setBounds(left, top, right, bottom)
|
||||
return
|
||||
}
|
||||
val intrinsicAspectRatio = intrinsicWidth / intrinsicHeight.toFloat()
|
||||
val boundsAspectRatio = boundsWidth / boundsHeight.toFloat()
|
||||
if (intrinsicAspectRatio < boundsAspectRatio) {
|
||||
// Match height
|
||||
val actualWidth = (boundsWidth * (intrinsicAspectRatio / boundsAspectRatio)).toInt()
|
||||
setBounds(boundsWidth / 2 - actualWidth / 2, top,
|
||||
boundsWidth / 2 + actualWidth / 2, bottom)
|
||||
} else {
|
||||
// Match width
|
||||
val actualHeight = (boundsHeight * (boundsAspectRatio / intrinsicAspectRatio)).toInt()
|
||||
setBounds(left, boundsHeight / 2 - actualHeight / 2, right,
|
||||
boundsHeight / 2 + actualHeight / 2)
|
||||
}
|
||||
}
|
|
@ -54,8 +54,8 @@ fun Array<SpanItem>.applyTo(spannable: Spannable, emojis: Map<String, CustomEmoj
|
|||
SpanItem.SpanType.EMOJI -> {
|
||||
val shortCode = span.link ?: return@forEach
|
||||
val emoji = emojis?.get(shortCode) ?: return@forEach
|
||||
spannable.setSpan(CustomEmojiSpan(emoji.url, requestManager, textView), span.start,
|
||||
span.end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
|
||||
spannable.setSpan(CustomEmojiSpan(emoji.url, requestManager, textView),
|
||||
span.start, span.end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
|
||||
}
|
||||
else -> {
|
||||
spannable.setSpan(URLSpan(span.link), span.start, span.end,
|
||||
|
|
|
@ -501,7 +501,7 @@ class UserFragment : BaseFragment(), OnClickListener, OnLinkClickListener,
|
|||
override fun getSystemWindowInsets(caller: Fragment, insets: Rect): Boolean {
|
||||
insetsCallback?.getSystemWindowInsets(this, insets)
|
||||
if (caller.parentFragment === this) {
|
||||
insets.top = toolbar.height + toolbarTabs.height
|
||||
insets.top = toolbar.measuredHeight + toolbarTabs.measuredHeight
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
|
|
@ -27,59 +27,69 @@ import android.graphics.drawable.Drawable
|
|||
import android.text.style.ReplacementSpan
|
||||
import android.widget.TextView
|
||||
import com.bumptech.glide.RequestManager
|
||||
import com.bumptech.glide.load.DecodeFormat
|
||||
import com.bumptech.glide.request.animation.GlideAnimation
|
||||
import com.bumptech.glide.request.target.BaseTarget
|
||||
import com.bumptech.glide.request.target.SizeReadyCallback
|
||||
import org.mariotaku.ktextension.weak
|
||||
import com.bumptech.glide.request.target.SimpleTarget
|
||||
import org.mariotaku.twidere.R
|
||||
import org.mariotaku.twidere.extension.setBoundsFitCenter
|
||||
|
||||
class CustomEmojiSpan(
|
||||
val uri: String,
|
||||
uri: String,
|
||||
requestManager: RequestManager,
|
||||
val textView: TextView,
|
||||
val alignBaseline: Boolean = false
|
||||
textView: TextView
|
||||
) : ReplacementSpan() {
|
||||
|
||||
private val textSize = textView.textSize.toInt()
|
||||
|
||||
private val target = GlideTarget(textSize)
|
||||
private val emojiSize = textView.textSize.toInt()
|
||||
private val target = GlideTarget(textView, emojiSize, emojiSize)
|
||||
|
||||
init {
|
||||
requestManager.load(uri)
|
||||
.asBitmap()
|
||||
.placeholder(R.mipmap.ic_emoji_loading)
|
||||
.error(R.mipmap.ic_emoji_error)
|
||||
.format(DecodeFormat.PREFER_ARGB_8888)
|
||||
.fitCenter()
|
||||
.dontAnimate()
|
||||
.into(target)
|
||||
}
|
||||
|
||||
override fun getSize(paint: Paint, text: CharSequence, start: Int, end: Int,
|
||||
fm: Paint.FontMetricsInt?): Int {
|
||||
return textSize
|
||||
if (fm != null) {
|
||||
fm.ascent = -target.height
|
||||
fm.descent = 0
|
||||
|
||||
fm.top = fm.ascent
|
||||
fm.bottom = 0
|
||||
}
|
||||
|
||||
return target.width
|
||||
}
|
||||
|
||||
override fun draw(canvas: Canvas, text: CharSequence, start: Int, end: Int, x: Float, top: Int,
|
||||
y: Int, bottom: Int, paint: Paint) {
|
||||
val b = target.drawable ?: return
|
||||
|
||||
canvas.save()
|
||||
|
||||
var transY = bottom - b.bounds.bottom
|
||||
if (alignBaseline) {
|
||||
transY -= paint.fontMetricsInt.descent
|
||||
}
|
||||
val transY = bottom - b.bounds.bottom
|
||||
|
||||
canvas.translate(x, transY.toFloat())
|
||||
b.setBounds(0, 0, textSize, textSize)
|
||||
b.draw(canvas)
|
||||
canvas.restore()
|
||||
}
|
||||
|
||||
private inner class GlideTarget(
|
||||
val textSize: Int
|
||||
) : BaseTarget<Bitmap>() {
|
||||
private class GlideTarget(
|
||||
val textView: TextView,
|
||||
val width: Int,
|
||||
val height: Int
|
||||
) : SimpleTarget<Bitmap>(width, height) {
|
||||
|
||||
var drawable: Drawable? by weak()
|
||||
var drawable: Drawable? = null
|
||||
set(value) {
|
||||
field = value
|
||||
value?.setBoundsFitCenter(0, 0, width, height)
|
||||
textView.postInvalidate()
|
||||
}
|
||||
|
||||
override fun onResourceReady(resource: Bitmap, glideAnimation: GlideAnimation<in Bitmap>) {
|
||||
drawable = BitmapDrawable(textView.resources, resource)
|
||||
|
@ -97,10 +107,7 @@ class CustomEmojiSpan(
|
|||
drawable = errorDrawable
|
||||
}
|
||||
|
||||
override fun getSize(cb: SizeReadyCallback) {
|
||||
cb.onSizeReady(textSize, textSize)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -136,6 +136,7 @@ internal class HeaderBehavior(context: Context, attrs: AttributeSet? = null) :
|
|||
}
|
||||
|
||||
override fun canDragView(view: ViewGroup): Boolean {
|
||||
if (view.translationY != 0f) return false
|
||||
// Else we'll use the default behaviour of seeing if it can scroll down
|
||||
val scrollingView = lastNestedScrollingChild
|
||||
return if (scrollingView != null) {
|
||||
|
|
|
@ -29,11 +29,43 @@
|
|||
app:statusBarBackground="@null"
|
||||
tools:theme="@style/Theme.Twidere.NoActionBar">
|
||||
|
||||
<org.mariotaku.twidere.view.ExtendedViewPager
|
||||
android:id="@+id/viewPager"
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:layout_behavior="org.mariotaku.twidere.view.behavior.userprofile.PagerBehavior"/>
|
||||
android:visibility="visible"
|
||||
app:layout_behavior="org.mariotaku.twidere.view.behavior.userprofile.PagerBehavior">
|
||||
|
||||
<org.mariotaku.twidere.view.ExtendedViewPager
|
||||
android:id="@+id/viewPager"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"/>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/pagesErrorContainer"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical"
|
||||
android:padding="@dimen/element_spacing_large"
|
||||
android:visibility="gone">
|
||||
|
||||
<org.mariotaku.twidere.view.IconActionView
|
||||
android:id="@+id/pagesErrorIcon"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:color="?android:textColorSecondary"
|
||||
android:src="@drawable/ic_info_error_generic"/>
|
||||
|
||||
<org.mariotaku.twidere.view.FixedTextView
|
||||
android:id="@+id/pagesErrorText"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:layout_marginTop="@dimen/element_spacing_normal"
|
||||
android:textAppearance="?android:textAppearanceMedium"/>
|
||||
</LinearLayout>
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
<RelativeLayout
|
||||
android:id="@+id/profileBannerContainer"
|
||||
|
@ -78,44 +110,6 @@
|
|||
android:background="@drawable/shadow_bottom"
|
||||
app:layout_behavior="org.mariotaku.twidere.view.behavior.userprofile.HeaderShadowBehavior"/>
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/pagesErrorContainer"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:visibility="gone">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical"
|
||||
android:padding="@dimen/element_spacing_large">
|
||||
|
||||
<org.mariotaku.twidere.view.IconActionView
|
||||
android:id="@+id/pagesErrorIcon"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:color="?android:textColorSecondary"
|
||||
android:src="@drawable/ic_info_error_generic"/>
|
||||
|
||||
<org.mariotaku.twidere.view.FixedTextView
|
||||
android:id="@+id/pagesErrorText"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:layout_marginTop="@dimen/element_spacing_normal"
|
||||
android:textAppearance="?android:textAppearanceMedium"/>
|
||||
</LinearLayout>
|
||||
|
||||
<View
|
||||
android:id="@+id/errorWindowOverlay"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/element_spacing_normal"
|
||||
android:layout_gravity="top"
|
||||
android:background="@drawable/shadow_bottom"/>
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
|
Loading…
Reference in New Issue