improved user fragment layout
This commit is contained in:
parent
a3547e82a0
commit
0ec9096a9d
|
@ -345,9 +345,10 @@ class SignInActivity : BaseActivity(), OnClickListener, TextWatcher,
|
||||||
this[EXTRA_REQUEST_TOKEN_SECRET] = requestToken.oauthTokenSecret
|
this[EXTRA_REQUEST_TOKEN_SECRET] = requestToken.oauthTokenSecret
|
||||||
})
|
})
|
||||||
activity.startActivityForResult(intent, REQUEST_BROWSER_TWITTER_SIGN_IN)
|
activity.startActivityForResult(intent, REQUEST_BROWSER_TWITTER_SIGN_IN)
|
||||||
}.failUi {
|
}.failUi { exception ->
|
||||||
val activity = weakThis.get() ?: return@failUi
|
val activity = weakThis.get() ?: return@failUi
|
||||||
// TODO show error message
|
Toast.makeText(activity, exception.getErrorMessage(activity), Toast.LENGTH_LONG).show()
|
||||||
|
DebugLog.w(tr = exception)
|
||||||
}.alwaysUi {
|
}.alwaysUi {
|
||||||
executeAfterFragmentResumed {
|
executeAfterFragmentResumed {
|
||||||
it.supportFragmentManager.dismissDialogFragment("get_request_token")
|
it.supportFragmentManager.dismissDialogFragment("get_request_token")
|
||||||
|
|
|
@ -20,10 +20,10 @@
|
||||||
package org.mariotaku.twidere.extension.model
|
package org.mariotaku.twidere.extension.model
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.graphics.drawable.Drawable
|
|
||||||
import android.text.Spannable
|
import android.text.Spannable
|
||||||
import android.text.Spanned
|
import android.text.Spanned
|
||||||
import android.text.style.URLSpan
|
import android.text.style.URLSpan
|
||||||
|
import android.widget.TextView
|
||||||
import com.bumptech.glide.RequestManager
|
import com.bumptech.glide.RequestManager
|
||||||
import org.mariotaku.twidere.model.CustomEmoji
|
import org.mariotaku.twidere.model.CustomEmoji
|
||||||
import org.mariotaku.twidere.model.SpanItem
|
import org.mariotaku.twidere.model.SpanItem
|
||||||
|
@ -36,7 +36,7 @@ val SpanItem.length: Int get() = end - start
|
||||||
|
|
||||||
@SuppressLint("SwitchIntDef")
|
@SuppressLint("SwitchIntDef")
|
||||||
fun Array<SpanItem>.applyTo(spannable: Spannable, emojis: Map<String, CustomEmoji>?,
|
fun Array<SpanItem>.applyTo(spannable: Spannable, emojis: Map<String, CustomEmoji>?,
|
||||||
requestManager: RequestManager, callback: Drawable.Callback) {
|
requestManager: RequestManager, textView: TextView) {
|
||||||
forEach { span ->
|
forEach { span ->
|
||||||
when (span.type) {
|
when (span.type) {
|
||||||
SpanItem.SpanType.HIDE -> {
|
SpanItem.SpanType.HIDE -> {
|
||||||
|
@ -54,7 +54,7 @@ fun Array<SpanItem>.applyTo(spannable: Spannable, emojis: Map<String, CustomEmoj
|
||||||
SpanItem.SpanType.EMOJI -> {
|
SpanItem.SpanType.EMOJI -> {
|
||||||
val shortCode = span.link ?: return@forEach
|
val shortCode = span.link ?: return@forEach
|
||||||
val emoji = emojis?.get(shortCode) ?: return@forEach
|
val emoji = emojis?.get(shortCode) ?: return@forEach
|
||||||
spannable.setSpan(CustomEmojiSpan(emoji.url, requestManager, callback), span.start,
|
spannable.setSpan(CustomEmojiSpan(emoji.url, requestManager, textView), span.start,
|
||||||
span.end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
|
span.end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
|
|
|
@ -22,24 +22,19 @@ package org.mariotaku.twidere.fragment
|
||||||
import android.accounts.AccountManager
|
import android.accounts.AccountManager
|
||||||
import android.animation.ArgbEvaluator
|
import android.animation.ArgbEvaluator
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.annotation.TargetApi
|
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.app.Dialog
|
import android.app.Dialog
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.DialogInterface
|
import android.content.DialogInterface
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.graphics.Color
|
import android.graphics.Color
|
||||||
import android.graphics.Outline
|
|
||||||
import android.graphics.PorterDuff
|
import android.graphics.PorterDuff
|
||||||
import android.graphics.Rect
|
import android.graphics.Rect
|
||||||
import android.graphics.drawable.ColorDrawable
|
import android.graphics.drawable.ColorDrawable
|
||||||
import android.graphics.drawable.Drawable
|
|
||||||
import android.graphics.drawable.LayerDrawable
|
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.nfc.NdefMessage
|
import android.nfc.NdefMessage
|
||||||
import android.nfc.NdefRecord
|
import android.nfc.NdefRecord
|
||||||
import android.nfc.NfcAdapter.CreateNdefMessageCallback
|
import android.nfc.NfcAdapter.CreateNdefMessageCallback
|
||||||
import android.os.Build
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
import android.support.annotation.ColorRes
|
import android.support.annotation.ColorRes
|
||||||
|
@ -118,8 +113,8 @@ import org.mariotaku.twidere.fragment.timeline.AbsTimelineFragment
|
||||||
import org.mariotaku.twidere.fragment.timeline.FavoritesTimelineFragment
|
import org.mariotaku.twidere.fragment.timeline.FavoritesTimelineFragment
|
||||||
import org.mariotaku.twidere.fragment.timeline.UserMediaTimelineFragment
|
import org.mariotaku.twidere.fragment.timeline.UserMediaTimelineFragment
|
||||||
import org.mariotaku.twidere.fragment.timeline.UserTimelineFragment
|
import org.mariotaku.twidere.fragment.timeline.UserTimelineFragment
|
||||||
import org.mariotaku.twidere.graphic.ActionBarColorDrawable
|
|
||||||
import org.mariotaku.twidere.graphic.ActionIconDrawable
|
import org.mariotaku.twidere.graphic.ActionIconDrawable
|
||||||
|
import org.mariotaku.twidere.graphic.drawable.userprofile.ActionBarDrawable
|
||||||
import org.mariotaku.twidere.loader.ParcelableUserLoader
|
import org.mariotaku.twidere.loader.ParcelableUserLoader
|
||||||
import org.mariotaku.twidere.model.*
|
import org.mariotaku.twidere.model.*
|
||||||
import org.mariotaku.twidere.model.event.FriendshipTaskEvent
|
import org.mariotaku.twidere.model.event.FriendshipTaskEvent
|
||||||
|
@ -637,7 +632,7 @@ class UserFragment : BaseFragment(), OnClickListener, OnLinkClickListener,
|
||||||
profileBanner.onSizeChangedListener = this
|
profileBanner.onSizeChangedListener = this
|
||||||
profileBannerSpace.setOnTouchListener(this)
|
profileBannerSpace.setOnTouchListener(this)
|
||||||
|
|
||||||
profileNameBackground.setBackgroundColor(cardBackgroundColor)
|
profileHeaderBackground.setBackgroundColor(cardBackgroundColor)
|
||||||
toolbarTabs.setBackgroundColor(cardBackgroundColor)
|
toolbarTabs.setBackgroundColor(cardBackgroundColor)
|
||||||
|
|
||||||
actionBarBackground = ActionBarDrawable(ResourcesCompat.getDrawable(activity.resources,
|
actionBarBackground = ActionBarDrawable(ResourcesCompat.getDrawable(activity.resources,
|
||||||
|
@ -1301,8 +1296,6 @@ class UserFragment : BaseFragment(), OnClickListener, OnLinkClickListener,
|
||||||
location.setLinkTextColor(optimalAccentColor)
|
location.setLinkTextColor(optimalAccentColor)
|
||||||
url.setLinkTextColor(optimalAccentColor)
|
url.setLinkTextColor(optimalAccentColor)
|
||||||
profileBanner.setBackgroundColor(color)
|
profileBanner.setBackgroundColor(color)
|
||||||
|
|
||||||
toolbarTabs.setBackgroundColor(primaryColor)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setupBaseActionBar() {
|
private fun setupBaseActionBar() {
|
||||||
|
@ -1370,11 +1363,6 @@ class UserFragment : BaseFragment(), OnClickListener, OnLinkClickListener,
|
||||||
private fun updateScrollOffset(offset: Int) {
|
private fun updateScrollOffset(offset: Int) {
|
||||||
val spaceHeight = profileBannerSpace.height
|
val spaceHeight = profileBannerSpace.height
|
||||||
val factor = (if (spaceHeight == 0) 0f else offset / spaceHeight.toFloat()).coerceIn(0f, 1f)
|
val factor = (if (spaceHeight == 0) 0f else offset / spaceHeight.toFloat()).coerceIn(0f, 1f)
|
||||||
profileBannerContainer.translationY = (-offset).toFloat()
|
|
||||||
profileBanner.translationY = (offset / 2).toFloat()
|
|
||||||
if (profileBirthdayStub == null) {
|
|
||||||
profileBirthdayBanner.translationY = (offset / 2).toFloat()
|
|
||||||
}
|
|
||||||
|
|
||||||
val activity = activity as BaseActivity
|
val activity = activity as BaseActivity
|
||||||
|
|
||||||
|
@ -1386,7 +1374,7 @@ class UserFragment : BaseFragment(), OnClickListener, OnLinkClickListener,
|
||||||
val stackedTabColor = primaryColor
|
val stackedTabColor = primaryColor
|
||||||
|
|
||||||
|
|
||||||
val profileContentHeight = profileNameBackground.height.toFloat()
|
val profileContentHeight = profileHeaderBackground.height.toFloat()
|
||||||
val tabOutlineAlphaFactor: Float
|
val tabOutlineAlphaFactor: Float
|
||||||
if (offset - spaceHeight > 0) {
|
if (offset - spaceHeight > 0) {
|
||||||
tabOutlineAlphaFactor = 1f - ((offset - spaceHeight) / profileContentHeight).coerceIn(0f, 1f)
|
tabOutlineAlphaFactor = 1f - ((offset - spaceHeight) / profileContentHeight).coerceIn(0f, 1f)
|
||||||
|
@ -1514,70 +1502,6 @@ class UserFragment : BaseFragment(), OnClickListener, OnLinkClickListener,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class ActionBarDrawable(shadow: Drawable) : LayerDrawable(arrayOf(shadow, ActionBarColorDrawable.create(true))) {
|
|
||||||
|
|
||||||
private val shadowDrawable = getDrawable(0)
|
|
||||||
private val colorDrawable = getDrawable(1) as ColorDrawable
|
|
||||||
private var alphaValue: Int = 0
|
|
||||||
|
|
||||||
var factor: Float = 0f
|
|
||||||
set(value) {
|
|
||||||
field = value
|
|
||||||
updateValue()
|
|
||||||
}
|
|
||||||
|
|
||||||
var color: Int = 0
|
|
||||||
set(value) {
|
|
||||||
field = value
|
|
||||||
colorDrawable.color = value
|
|
||||||
updateValue()
|
|
||||||
}
|
|
||||||
|
|
||||||
var outlineAlphaFactor: Float = 0f
|
|
||||||
set(value) {
|
|
||||||
field = value
|
|
||||||
updateValue()
|
|
||||||
}
|
|
||||||
|
|
||||||
init {
|
|
||||||
alpha = 0xFF
|
|
||||||
updateValue()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun setAlpha(alpha: Int) {
|
|
||||||
alphaValue = alpha
|
|
||||||
updateValue()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getAlpha(): Int {
|
|
||||||
return alphaValue
|
|
||||||
}
|
|
||||||
|
|
||||||
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
|
||||||
override fun getOutline(outline: Outline) {
|
|
||||||
colorDrawable.getOutline(outline)
|
|
||||||
outline.alpha = factor * outlineAlphaFactor * 0.99f
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getIntrinsicWidth(): Int {
|
|
||||||
return colorDrawable.intrinsicWidth
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getIntrinsicHeight(): Int {
|
|
||||||
return colorDrawable.intrinsicHeight
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun updateValue() {
|
|
||||||
val shadowAlpha = Math.round(alpha * (1 - factor).coerceIn(0f, 1f))
|
|
||||||
shadowDrawable.alpha = shadowAlpha
|
|
||||||
val hasColor = color != 0
|
|
||||||
val colorAlpha = if (hasColor) Math.round(alpha * factor.coerceIn(0f, 1f)) else 0
|
|
||||||
colorDrawable.alpha = colorAlpha
|
|
||||||
invalidateSelf()
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
internal class UserRelationshipLoader(
|
internal class UserRelationshipLoader(
|
||||||
context: Context,
|
context: Context,
|
||||||
|
|
|
@ -0,0 +1,92 @@
|
||||||
|
/*
|
||||||
|
* 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.graphic.drawable.userprofile
|
||||||
|
|
||||||
|
import android.annotation.TargetApi
|
||||||
|
import android.graphics.Outline
|
||||||
|
import android.graphics.drawable.ColorDrawable
|
||||||
|
import android.graphics.drawable.Drawable
|
||||||
|
import android.graphics.drawable.LayerDrawable
|
||||||
|
import android.os.Build
|
||||||
|
import org.mariotaku.twidere.graphic.ActionBarColorDrawable
|
||||||
|
|
||||||
|
internal class ActionBarDrawable(shadow: Drawable) : LayerDrawable(arrayOf(shadow, ActionBarColorDrawable.create(true))) {
|
||||||
|
|
||||||
|
private val shadowDrawable = getDrawable(0)
|
||||||
|
private val colorDrawable = getDrawable(1) as ColorDrawable
|
||||||
|
private var alphaValue: Int = 0
|
||||||
|
|
||||||
|
var factor: Float = 0f
|
||||||
|
set(value) {
|
||||||
|
field = value
|
||||||
|
updateValue()
|
||||||
|
}
|
||||||
|
|
||||||
|
var color: Int = 0
|
||||||
|
set(value) {
|
||||||
|
field = value
|
||||||
|
colorDrawable.color = value
|
||||||
|
updateValue()
|
||||||
|
}
|
||||||
|
|
||||||
|
var outlineAlphaFactor: Float = 0f
|
||||||
|
set(value) {
|
||||||
|
field = value
|
||||||
|
updateValue()
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
alpha = 0xFF
|
||||||
|
updateValue()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setAlpha(alpha: Int) {
|
||||||
|
alphaValue = alpha
|
||||||
|
updateValue()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getAlpha(): Int {
|
||||||
|
return alphaValue
|
||||||
|
}
|
||||||
|
|
||||||
|
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
||||||
|
override fun getOutline(outline: Outline) {
|
||||||
|
colorDrawable.getOutline(outline)
|
||||||
|
outline.alpha = factor * outlineAlphaFactor * 0.99f
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getIntrinsicWidth(): Int {
|
||||||
|
return colorDrawable.intrinsicWidth
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getIntrinsicHeight(): Int {
|
||||||
|
return colorDrawable.intrinsicHeight
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateValue() {
|
||||||
|
val shadowAlpha = Math.round(alpha * (1 - factor).coerceIn(0f, 1f))
|
||||||
|
shadowDrawable.alpha = shadowAlpha
|
||||||
|
val hasColor = color != 0
|
||||||
|
val colorAlpha = if (hasColor) Math.round(alpha * factor.coerceIn(0f, 1f)) else 0
|
||||||
|
colorDrawable.alpha = colorAlpha
|
||||||
|
invalidateSelf()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -19,77 +19,89 @@
|
||||||
|
|
||||||
package org.mariotaku.twidere.text.style
|
package org.mariotaku.twidere.text.style
|
||||||
|
|
||||||
|
import android.graphics.Bitmap
|
||||||
import android.graphics.Canvas
|
import android.graphics.Canvas
|
||||||
import android.graphics.Paint
|
import android.graphics.Paint
|
||||||
import android.graphics.Rect
|
import android.graphics.drawable.BitmapDrawable
|
||||||
import android.graphics.drawable.Animatable
|
|
||||||
import android.graphics.drawable.Drawable
|
import android.graphics.drawable.Drawable
|
||||||
import android.support.v4.widget.CircularProgressDrawable
|
import android.text.style.ReplacementSpan
|
||||||
import android.text.style.DynamicDrawableSpan
|
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import com.bumptech.glide.RequestManager
|
import com.bumptech.glide.RequestManager
|
||||||
import com.bumptech.glide.load.resource.drawable.GlideDrawable
|
|
||||||
import com.bumptech.glide.request.animation.GlideAnimation
|
import com.bumptech.glide.request.animation.GlideAnimation
|
||||||
import com.bumptech.glide.request.target.BaseTarget
|
import com.bumptech.glide.request.target.BaseTarget
|
||||||
import com.bumptech.glide.request.target.SizeReadyCallback
|
import com.bumptech.glide.request.target.SizeReadyCallback
|
||||||
import org.mariotaku.twidere.R
|
import org.mariotaku.twidere.R
|
||||||
import org.mariotaku.twidere.graphic.DrawableWrapper
|
import java.lang.ref.WeakReference
|
||||||
|
|
||||||
class CustomEmojiSpan(
|
class CustomEmojiSpan(
|
||||||
val uri: String,
|
val uri: String,
|
||||||
requestManager: RequestManager,
|
requestManager: RequestManager,
|
||||||
callback: Drawable.Callback
|
val textView: TextView,
|
||||||
) : DynamicDrawableSpan(DynamicDrawableSpan.ALIGN_BASELINE) {
|
val alignBaseline: Boolean = false
|
||||||
|
) : ReplacementSpan() {
|
||||||
|
|
||||||
private val textSize = (callback as TextView).textSize.toInt()
|
private val textSize = textView.textSize.toInt()
|
||||||
|
|
||||||
private val emojiDrawable = EmojiDrawable(textSize)
|
private val target = GlideTarget(textSize)
|
||||||
private val emojiDirtyBounds = Rect()
|
|
||||||
|
|
||||||
private val target = GlideTarget()
|
|
||||||
|
|
||||||
init {
|
init {
|
||||||
emojiDrawable.callback = callback
|
|
||||||
requestManager.load(uri)
|
requestManager.load(uri)
|
||||||
.placeholder(CircularProgressDrawable((callback as TextView).context))
|
.asBitmap()
|
||||||
|
.placeholder(R.mipmap.ic_emoji_loading)
|
||||||
.error(R.mipmap.ic_emoji_error)
|
.error(R.mipmap.ic_emoji_error)
|
||||||
.fitCenter()
|
.fitCenter()
|
||||||
|
.dontAnimate()
|
||||||
.into(target)
|
.into(target)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun getSize(paint: Paint, text: CharSequence, start: Int, end: Int,
|
||||||
|
fm: Paint.FontMetricsInt?): Int {
|
||||||
|
return textSize
|
||||||
|
}
|
||||||
|
|
||||||
override fun draw(canvas: Canvas, text: CharSequence, start: Int, end: Int, x: Float, top: Int,
|
override fun draw(canvas: Canvas, text: CharSequence, start: Int, end: Int, x: Float, top: Int,
|
||||||
y: Int, bottom: Int, paint: Paint) {
|
y: Int, bottom: Int, paint: Paint) {
|
||||||
|
val b = target.drawable ?: return
|
||||||
|
canvas.save()
|
||||||
|
|
||||||
var transY = bottom - emojiDrawable.bounds.bottom
|
var transY = bottom - b.bounds.bottom
|
||||||
if (verticalAlignment == ALIGN_BASELINE) {
|
if (alignBaseline) {
|
||||||
transY -= paint.fontMetricsInt.descent
|
transY -= paint.fontMetricsInt.descent
|
||||||
}
|
}
|
||||||
emojiDrawable.copyBounds(emojiDirtyBounds)
|
|
||||||
emojiDirtyBounds.offsetTo(x.toInt(), transY)
|
canvas.translate(x, transY.toFloat())
|
||||||
super.draw(canvas, text, start, end, x, top, y, bottom, paint)
|
b.setBounds(0, 0, textSize, textSize)
|
||||||
|
b.draw(canvas)
|
||||||
|
canvas.restore()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getDrawable(): Drawable = emojiDrawable
|
private inner class GlideTarget(
|
||||||
|
val textSize: Int
|
||||||
|
) : BaseTarget<Bitmap>() {
|
||||||
|
|
||||||
fun verify(who: Drawable): Boolean = who === emojiDrawable
|
var drawable: Drawable?
|
||||||
|
get() = drawableRef?.get()
|
||||||
|
private set(value) {
|
||||||
|
drawableRef = if (value != null) WeakReference(value) else null
|
||||||
|
textView.invalidate()
|
||||||
|
}
|
||||||
|
|
||||||
private inner class GlideTarget : BaseTarget<GlideDrawable>() {
|
private var drawableRef: WeakReference<Drawable>? = null
|
||||||
|
|
||||||
override fun onResourceReady(resource: GlideDrawable, glideAnimation: GlideAnimation<in GlideDrawable>) {
|
override fun onResourceReady(resource: Bitmap, glideAnimation: GlideAnimation<in Bitmap>) {
|
||||||
resource.setLoopCount(GlideDrawable.LOOP_FOREVER)
|
drawable = BitmapDrawable(textView.resources, resource)
|
||||||
emojiDrawable.setDrawable(resource)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onLoadCleared(placeholder: Drawable?) {
|
override fun onLoadCleared(placeholder: Drawable?) {
|
||||||
emojiDrawable.setDrawable(placeholder)
|
drawable = placeholder
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onLoadStarted(placeholder: Drawable?) {
|
override fun onLoadStarted(placeholder: Drawable?) {
|
||||||
emojiDrawable.setDrawable(placeholder)
|
drawable = placeholder
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onLoadFailed(e: Exception?, errorDrawable: Drawable?) {
|
override fun onLoadFailed(e: Exception?, errorDrawable: Drawable?) {
|
||||||
emojiDrawable.setDrawable(errorDrawable)
|
drawable = errorDrawable
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getSize(cb: SizeReadyCallback) {
|
override fun getSize(cb: SizeReadyCallback) {
|
||||||
|
@ -98,20 +110,4 @@ class CustomEmojiSpan(
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private inner class EmojiDrawable(val textSize: Int) : DrawableWrapper() {
|
|
||||||
|
|
||||||
override fun getDirtyBounds(): Rect {
|
|
||||||
return emojiDirtyBounds
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setDrawable(drawable: Drawable?) {
|
|
||||||
wrapped = drawable
|
|
||||||
drawable?.setBounds(0, 0, textSize, textSize)
|
|
||||||
setBounds(0, 0, textSize, textSize)
|
|
||||||
if (drawable is Animatable) {
|
|
||||||
drawable.start()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,9 +21,7 @@ package org.mariotaku.twidere.view
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.graphics.drawable.Drawable
|
|
||||||
import android.text.Spannable
|
import android.text.Spannable
|
||||||
import android.text.Spanned
|
|
||||||
import android.text.method.BaseMovementMethod
|
import android.text.method.BaseMovementMethod
|
||||||
import android.text.method.MovementMethod
|
import android.text.method.MovementMethod
|
||||||
import android.text.style.ClickableSpan
|
import android.text.style.ClickableSpan
|
||||||
|
@ -32,7 +30,6 @@ import android.view.MotionEvent
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import org.mariotaku.chameleon.view.ChameleonTextView
|
import org.mariotaku.chameleon.view.ChameleonTextView
|
||||||
import org.mariotaku.twidere.extension.setupEmojiFactory
|
import org.mariotaku.twidere.extension.setupEmojiFactory
|
||||||
import org.mariotaku.twidere.text.style.CustomEmojiSpan
|
|
||||||
import java.lang.ref.WeakReference
|
import java.lang.ref.WeakReference
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -90,16 +87,6 @@ class TimelineContentTextView(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun verifyDrawable(who: Drawable): Boolean {
|
|
||||||
val result = super.verifyDrawable(who)
|
|
||||||
if (result) return true
|
|
||||||
val spanned = text as? Spanned ?: return false
|
|
||||||
val spans = spanned.getSpans(0, length(), CustomEmojiSpan::class.java)
|
|
||||||
return spans.any {
|
|
||||||
it.verify(who)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal class InternalMovementMethod : BaseMovementMethod() {
|
internal class InternalMovementMethod : BaseMovementMethod() {
|
||||||
private var targetSpan: WeakReference<ClickableSpan?>? = null
|
private var targetSpan: WeakReference<ClickableSpan?>? = null
|
||||||
|
|
||||||
|
|
|
@ -19,20 +19,40 @@
|
||||||
|
|
||||||
package org.mariotaku.twidere.view.behavior.userprofile
|
package org.mariotaku.twidere.view.behavior.userprofile
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.support.design.widget.AccessorHeaderBehavior
|
import android.support.design.widget.AccessorHeaderBehavior
|
||||||
import android.support.design.widget.CoordinatorLayout
|
import android.support.design.widget.CoordinatorLayout
|
||||||
import android.support.v4.math.MathUtils
|
import android.support.graphics.drawable.ArgbEvaluator
|
||||||
import android.util.AttributeSet
|
import android.util.AttributeSet
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
import kotlinx.android.synthetic.main.fragment_user.view.*
|
||||||
|
import kotlinx.android.synthetic.main.header_user.view.*
|
||||||
|
import org.mariotaku.chameleon.Chameleon
|
||||||
|
import org.mariotaku.chameleon.ChameleonUtils
|
||||||
|
import org.mariotaku.kpreferences.get
|
||||||
import org.mariotaku.twidere.R
|
import org.mariotaku.twidere.R
|
||||||
|
import org.mariotaku.twidere.constant.themeBackgroundAlphaKey
|
||||||
|
import org.mariotaku.twidere.constant.themeBackgroundOptionKey
|
||||||
import org.mariotaku.twidere.extension.view.measureChildIgnoringInsets
|
import org.mariotaku.twidere.extension.view.measureChildIgnoringInsets
|
||||||
|
import org.mariotaku.twidere.graphic.drawable.userprofile.ActionBarDrawable
|
||||||
|
import org.mariotaku.twidere.util.ThemeUtils
|
||||||
|
import org.mariotaku.twidere.util.dagger.DependencyHolder
|
||||||
|
|
||||||
internal class HeaderBehavior(context: Context, attrs: AttributeSet? = null) : AccessorHeaderBehavior<View>(context, attrs) {
|
internal class HeaderBehavior(context: Context, attrs: AttributeSet? = null) : AccessorHeaderBehavior<View>(context, attrs) {
|
||||||
|
|
||||||
var offsetDelta: Int = 0
|
var offsetDelta: Int = 0
|
||||||
private set
|
private set
|
||||||
|
|
||||||
|
private val cardBackgroundColor: Int
|
||||||
|
private var tabItemIsDark: Int = 0
|
||||||
|
|
||||||
|
init {
|
||||||
|
val preferences = DependencyHolder.get(context).preferences
|
||||||
|
cardBackgroundColor = ThemeUtils.getCardBackgroundColor(context,
|
||||||
|
preferences[themeBackgroundOptionKey], preferences[themeBackgroundAlphaKey])
|
||||||
|
}
|
||||||
|
|
||||||
override fun onMeasureChild(parent: CoordinatorLayout, child: View,
|
override fun onMeasureChild(parent: CoordinatorLayout, child: View,
|
||||||
parentWidthMeasureSpec: Int, widthUsed: Int, parentHeightMeasureSpec: Int,
|
parentWidthMeasureSpec: Int, widthUsed: Int, parentHeightMeasureSpec: Int,
|
||||||
heightUsed: Int): Boolean {
|
heightUsed: Int): Boolean {
|
||||||
|
@ -40,6 +60,12 @@ internal class HeaderBehavior(context: Context, attrs: AttributeSet? = null) : A
|
||||||
parentHeightMeasureSpec, heightUsed)
|
parentHeightMeasureSpec, heightUsed)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onLayoutChild(parent: CoordinatorLayout, child: View, layoutDirection: Int): Boolean {
|
||||||
|
val result = super.onLayoutChild(parent, child, layoutDirection)
|
||||||
|
updateTabColor(parent, child, topAndBottomOffset)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
override fun layoutChild(parent: CoordinatorLayout, child: View, layoutDirection: Int) {
|
override fun layoutChild(parent: CoordinatorLayout, child: View, layoutDirection: Int) {
|
||||||
child.layout(0, 0, child.measuredWidth, child.measuredHeight)
|
child.layout(0, 0, child.measuredWidth, child.measuredHeight)
|
||||||
}
|
}
|
||||||
|
@ -62,38 +88,19 @@ internal class HeaderBehavior(context: Context, attrs: AttributeSet? = null) : A
|
||||||
val curOffset = topBottomOffsetForScrollingSibling
|
val curOffset = topBottomOffsetForScrollingSibling
|
||||||
var consumed = 0
|
var consumed = 0
|
||||||
|
|
||||||
var newOffset = newOffset
|
|
||||||
if (minOffset != 0 && curOffset >= minOffset && curOffset <= maxOffset) {
|
if (minOffset != 0 && curOffset >= minOffset && curOffset <= maxOffset) {
|
||||||
// If we have some scrolling range, and we're currently within the min and max
|
// If we have some scrolling range, and we're currently within the min and max
|
||||||
// offsets, calculate a new offset
|
// offsets, calculate a new offset
|
||||||
newOffset = MathUtils.clamp(newOffset, minOffset, maxOffset)
|
val clampedOffset = newOffset.coerceIn(minOffset, maxOffset)
|
||||||
if (curOffset != newOffset) {
|
if (curOffset != clampedOffset) {
|
||||||
val interpolatedOffset = if (header.hasChildWithInterpolator)
|
|
||||||
interpolateOffset(header, newOffset)
|
|
||||||
else
|
|
||||||
newOffset
|
|
||||||
|
|
||||||
val offsetChanged = setTopAndBottomOffset(interpolatedOffset)
|
|
||||||
|
|
||||||
|
topAndBottomOffset = clampedOffset
|
||||||
|
updateTabColor(parent, header, clampedOffset)
|
||||||
// Update how much dy we have consumed
|
// Update how much dy we have consumed
|
||||||
consumed = curOffset - newOffset
|
consumed = curOffset - clampedOffset
|
||||||
// Update the stored sibling offset
|
// Update the stored sibling offset
|
||||||
offsetDelta = newOffset - interpolatedOffset
|
offsetDelta = clampedOffset - clampedOffset
|
||||||
|
|
||||||
if (!offsetChanged && header.hasChildWithInterpolator) {
|
|
||||||
// If the offset hasn't changed and we're using an interpolated scroll
|
|
||||||
// then we need to keep any dependent views updated. CoL will do this for
|
|
||||||
// us when we move, but we need to do it manually when we don't (as an
|
|
||||||
// interpolated scroll may finish early).
|
|
||||||
parent.dispatchDependentViewsChanged(header)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Dispatch the updates to any listeners
|
|
||||||
// header.dispatchOffsetUpdates(topAndBottomOffset)
|
|
||||||
|
|
||||||
// Update the AppBarLayout's drawable state (for any elevation changes)
|
|
||||||
// updateAppBarLayoutDrawableState(parent, header, newOffset,
|
|
||||||
// if (newOffset < curOffset) -1 else 1, false)
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Reset the offset delta
|
// Reset the offset delta
|
||||||
|
@ -103,13 +110,42 @@ internal class HeaderBehavior(context: Context, attrs: AttributeSet? = null) : A
|
||||||
return consumed
|
return consumed
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun canDragView(view: View) = true
|
@SuppressLint("RestrictedApi")
|
||||||
|
private fun updateTabColor(parent: CoordinatorLayout, header: View, offset: Int) {
|
||||||
|
val actionBarBackground = parent.toolbar.background as? ActionBarDrawable ?: return
|
||||||
|
val profileHeaderBackground = parent.profileHeaderBackground
|
||||||
|
|
||||||
private fun interpolateOffset(header: View, offset: Int): Int {
|
val toolbarBottom = parent.toolbar.bottom
|
||||||
return offset
|
val headerBackgroundOffset = offset + profileHeaderBackground.top
|
||||||
|
|
||||||
|
val factor = ((toolbarBottom - headerBackgroundOffset) / profileHeaderBackground.height.toFloat()).coerceIn(0f, 1f)
|
||||||
|
|
||||||
|
val toolbarTabs = header.toolbarTabs
|
||||||
|
|
||||||
|
val colorPrimary = actionBarBackground.color
|
||||||
|
val currentTabColor = ArgbEvaluator.getInstance().evaluate(factor,
|
||||||
|
cardBackgroundColor, colorPrimary) as Int
|
||||||
|
|
||||||
|
toolbarTabs.setBackgroundColor(currentTabColor)
|
||||||
|
|
||||||
|
val tabItemIsDark = if (ThemeUtils.isLightColor(currentTabColor)) 1 else -1
|
||||||
|
if (this.tabItemIsDark != tabItemIsDark) {
|
||||||
|
val context = parent.context
|
||||||
|
val activity = ChameleonUtils.getActivity(context)
|
||||||
|
val tabContrastColor = ThemeUtils.getColorDependent(currentTabColor)
|
||||||
|
toolbarTabs.setIconColor(tabContrastColor)
|
||||||
|
toolbarTabs.setLabelColor(tabContrastColor)
|
||||||
|
val theme = Chameleon.getOverrideTheme(context, activity)
|
||||||
|
if (theme.isToolbarColored) {
|
||||||
|
toolbarTabs.setStripColor(tabContrastColor)
|
||||||
|
} else {
|
||||||
|
toolbarTabs.setStripColor(ThemeUtils.getOptimalAccentColor(colorPrimary, tabContrastColor))
|
||||||
|
}
|
||||||
|
toolbarTabs.updateAppearance()
|
||||||
|
}
|
||||||
|
this.tabItemIsDark = tabItemIsDark
|
||||||
}
|
}
|
||||||
|
|
||||||
private val View.hasChildWithInterpolator: Boolean
|
override fun canDragView(view: View) = true
|
||||||
get() = false
|
|
||||||
|
|
||||||
}
|
}
|
|
@ -0,0 +1,75 @@
|
||||||
|
/*
|
||||||
|
* 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.view.behavior.userprofile
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.content.Context
|
||||||
|
import android.support.design.widget.CoordinatorLayout
|
||||||
|
import android.support.design.widget.lastWindowInsetsCompat
|
||||||
|
import android.support.graphics.drawable.ArgbEvaluator
|
||||||
|
import android.util.AttributeSet
|
||||||
|
import android.view.View
|
||||||
|
import android.view.Window
|
||||||
|
import kotlinx.android.synthetic.main.fragment_user.view.*
|
||||||
|
import org.mariotaku.chameleon.ChameleonUtils
|
||||||
|
import org.mariotaku.twidere.R
|
||||||
|
import org.mariotaku.twidere.graphic.drawable.userprofile.ActionBarDrawable
|
||||||
|
import org.mariotaku.twidere.util.ThemeUtils
|
||||||
|
import org.mariotaku.twidere.util.support.WindowSupport
|
||||||
|
|
||||||
|
internal class StatusBarBehavior(context: Context, attrs: AttributeSet? = null) : CoordinatorLayout.Behavior<View>(context, attrs) {
|
||||||
|
|
||||||
|
private val window: Window = ChameleonUtils.getActivity(context)!!.window
|
||||||
|
private var lightStatusBar: Int = 0
|
||||||
|
|
||||||
|
override fun layoutDependsOn(parent: CoordinatorLayout, child: View, dependency: View): Boolean {
|
||||||
|
return dependency.id == R.id.profileHeader
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onLayoutChild(parent: CoordinatorLayout, child: View, layoutDirection: Int): Boolean {
|
||||||
|
val lastInsets = parent.lastWindowInsetsCompat ?: return true
|
||||||
|
val height = lastInsets.systemWindowInsetTop
|
||||||
|
child.layout(0, 0, child.measuredWidth, height)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("RestrictedApi")
|
||||||
|
override fun onDependentViewChanged(parent: CoordinatorLayout, child: View, dependency: View): Boolean {
|
||||||
|
if (child.height == 0) return true
|
||||||
|
val actionBarBackground = parent.toolbar.background as? ActionBarDrawable ?: return true
|
||||||
|
val bannerContainer = parent.profileBannerContainer
|
||||||
|
val bannerBottom = dependency.top + bannerContainer.height
|
||||||
|
val currentOffset = bannerBottom - child.bottom
|
||||||
|
val maxOffset = (bannerContainer.height - child.bottom).toFloat()
|
||||||
|
val factor = (1 - currentOffset / maxOffset).coerceIn(0f, 1f)
|
||||||
|
|
||||||
|
val primaryColorDark = ChameleonUtils.darkenColor(actionBarBackground.color)
|
||||||
|
val statusBarColor = ArgbEvaluator.getInstance().evaluate(factor, 0xA0000000.toInt(),
|
||||||
|
ChameleonUtils.darkenColor(primaryColorDark))
|
||||||
|
child.setBackgroundColor(statusBarColor as Int)
|
||||||
|
val lightStatusBar = if (ThemeUtils.isLightColor(statusBarColor)) 1 else -1
|
||||||
|
if (this.lightStatusBar != lightStatusBar) {
|
||||||
|
WindowSupport.setLightStatusBar(window, lightStatusBar == 1)
|
||||||
|
}
|
||||||
|
this.lightStatusBar = lightStatusBar
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -19,11 +19,47 @@
|
||||||
|
|
||||||
package org.mariotaku.twidere.view.behavior.userprofile
|
package org.mariotaku.twidere.view.behavior.userprofile
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.support.design.widget.CoordinatorLayout
|
import android.support.design.widget.CoordinatorLayout
|
||||||
|
import android.support.graphics.drawable.ArgbEvaluator
|
||||||
import android.support.v7.widget.Toolbar
|
import android.support.v7.widget.Toolbar
|
||||||
import android.util.AttributeSet
|
import android.util.AttributeSet
|
||||||
|
import android.view.View
|
||||||
|
import kotlinx.android.synthetic.main.fragment_user.view.*
|
||||||
|
import org.mariotaku.twidere.R
|
||||||
|
import org.mariotaku.twidere.graphic.drawable.userprofile.ActionBarDrawable
|
||||||
|
import org.mariotaku.twidere.util.ThemeUtils
|
||||||
|
|
||||||
internal class ToolbarBehavior(context: Context?, attrs: AttributeSet? = null) : CoordinatorLayout.Behavior<Toolbar>(context, attrs) {
|
internal class ToolbarBehavior(context: Context?, attrs: AttributeSet? = null) : CoordinatorLayout.Behavior<Toolbar>(context, attrs) {
|
||||||
|
|
||||||
|
private val actionBarShadowColor: Int = 0xA0000000.toInt()
|
||||||
|
private var actionItemIsDark: Int = 0
|
||||||
|
|
||||||
|
override fun layoutDependsOn(parent: CoordinatorLayout, child: Toolbar, dependency: View): Boolean {
|
||||||
|
return dependency.id == R.id.profileHeader
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("RestrictedApi")
|
||||||
|
override fun onDependentViewChanged(parent: CoordinatorLayout, child: Toolbar, dependency: View): Boolean {
|
||||||
|
val actionBarBackground = child.background as? ActionBarDrawable ?: return false
|
||||||
|
val bannerContainer = parent.profileBannerContainer
|
||||||
|
val bannerBottom = dependency.top + bannerContainer.height
|
||||||
|
val currentOffset = bannerBottom - child.bottom
|
||||||
|
val maxOffset = (bannerContainer.height - child.bottom).toFloat()
|
||||||
|
val factor = (1 - currentOffset / maxOffset).coerceIn(0f, 1f)
|
||||||
|
actionBarBackground.factor = factor
|
||||||
|
actionBarBackground.outlineAlphaFactor = factor
|
||||||
|
|
||||||
|
val colorPrimary = actionBarBackground.color
|
||||||
|
val currentActionBarColor = ArgbEvaluator.getInstance().evaluate(factor, actionBarShadowColor,
|
||||||
|
colorPrimary) as Int
|
||||||
|
val actionItemIsDark = if (ThemeUtils.isLightColor(currentActionBarColor)) 1 else -1
|
||||||
|
if (this.actionItemIsDark != actionItemIsDark) {
|
||||||
|
ThemeUtils.applyToolbarItemColor(parent.context, child, currentActionBarColor)
|
||||||
|
}
|
||||||
|
this.actionItemIsDark = actionItemIsDark
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -117,7 +117,12 @@
|
||||||
|
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
|
|
||||||
<!-- Don't change view order, since this view consumes window insets-->
|
<View
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:layout_behavior="org.mariotaku.twidere.view.behavior.userprofile.StatusBarBehavior"
|
||||||
|
tools:layout_height="25dp"/>
|
||||||
|
|
||||||
<android.support.v7.widget.Toolbar
|
<android.support.v7.widget.Toolbar
|
||||||
android:id="@+id/toolbar"
|
android:id="@+id/toolbar"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
|
|
@ -39,7 +39,7 @@
|
||||||
app:layout_constraintTop_toTopOf="parent"/>
|
app:layout_constraintTop_toTopOf="parent"/>
|
||||||
|
|
||||||
<View
|
<View
|
||||||
android:id="@+id/profileNameBackground"
|
android:id="@+id/profileHeaderBackground"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="0dp"
|
android:layout_height="0dp"
|
||||||
app:layout_constraintBottom_toBottomOf="@+id/countsContainer"
|
app:layout_constraintBottom_toBottomOf="@+id/countsContainer"
|
||||||
|
|
Loading…
Reference in New Issue