implementing conversation info ui

This commit is contained in:
Mariotaku Lee 2017-02-20 23:13:48 +08:00
parent 2507d91949
commit a8c5fca745
No known key found for this signature in database
GPG Key ID: 15C10F89D7C33535
21 changed files with 596 additions and 52 deletions

View File

@ -0,0 +1,31 @@
/*
* 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.support.test.runner.AndroidJUnit4
import org.junit.runner.RunWith
/**
* Created by mariotaku on 2017/2/20.
*/
@RunWith(AndroidJUnit4::class)
class ViewExtensionsKtTest {
}

View File

@ -81,6 +81,7 @@ public class ShapedImageView extends ImageView {
private float mShadowRadius; private float mShadowRadius;
private int mStyle; private int mStyle;
private float mCornerRadius, mCornerRadiusRatio; private float mCornerRadius, mCornerRadiusRatio;
private boolean mDrawShadow;
private RectF mTransitionSource, mTransitionDestination; private RectF mTransitionSource, mTransitionDestination;
private int mStrokeWidth, mBorderAlpha; private int mStrokeWidth, mBorderAlpha;
private int[] mBorderColors; private int[] mBorderColors;
@ -126,6 +127,7 @@ public class ShapedImageView extends ImageView {
setStyle(shapeStyle); setStyle(shapeStyle);
setCornerRadius(a.getDimension(R.styleable.ShapedImageView_sivCornerRadius, 0)); setCornerRadius(a.getDimension(R.styleable.ShapedImageView_sivCornerRadius, 0));
setCornerRadiusRatio(a.getFraction(R.styleable.ShapedImageView_sivCornerRadiusRatio, 1, 1, -1)); setCornerRadiusRatio(a.getFraction(R.styleable.ShapedImageView_sivCornerRadiusRatio, 1, 1, -1));
setDrawShadow(a.getBoolean(R.styleable.ShapedImageView_sivDrawShadow, true));
if (useOutline()) { if (useOutline()) {
if (a.hasValue(R.styleable.ShapedImageView_sivElevation)) { if (a.hasValue(R.styleable.ShapedImageView_sivElevation)) {
@ -265,6 +267,10 @@ public class ShapedImageView extends ImageView {
mCornerRadiusRatio = ratio; mCornerRadiusRatio = ratio;
} }
public void setDrawShadow(final boolean drawShadow) {
mDrawShadow = drawShadow;
}
public void setTransitionDestination(RectF dstBounds) { public void setTransitionDestination(RectF dstBounds) {
mTransitionDestination = dstBounds; mTransitionDestination = dstBounds;
} }
@ -423,7 +429,7 @@ public class ShapedImageView extends ImageView {
private void initOutlineProvider() { private void initOutlineProvider() {
if (!useOutline()) return; if (!useOutline()) return;
ViewSupport.setClipToOutline(this, true); ViewSupport.setClipToOutline(this, true);
ViewSupport.setOutlineProvider(this, new CircularOutlineProvider()); ViewSupport.setOutlineProvider(this, new CircularOutlineProvider(mDrawShadow));
} }
private void setBorderColorsInternal(int alpha, int... colors) { private void setBorderColorsInternal(int alpha, int... colors) {
@ -504,6 +510,12 @@ public class ShapedImageView extends ImageView {
} }
private static class CircularOutlineProvider extends ViewOutlineProviderCompat { private static class CircularOutlineProvider extends ViewOutlineProviderCompat {
boolean drawShadow;
public CircularOutlineProvider(final boolean drawShadow) {
this.drawShadow = drawShadow;
}
@Override @Override
public void getOutline(View view, OutlineCompat outline) { public void getOutline(View view, OutlineCompat outline) {
final int viewWidth = view.getWidth(), viewHeight = view.getHeight(); final int viewWidth = view.getWidth(), viewHeight = view.getHeight();
@ -523,6 +535,11 @@ public class ShapedImageView extends ImageView {
final float radius = imageView.getCalculatedCornerRadius(); final float radius = imageView.getCalculatedCornerRadius();
outline.setRoundRect(contentLeft, contentTop, contentRight, contentBottom, radius); outline.setRoundRect(contentLeft, contentTop, contentRight, contentBottom, radius);
} }
if (drawShadow) {
outline.setAlpha(1);
} else {
outline.setAlpha(0);
}
} }
} }
} }

View File

@ -26,7 +26,7 @@ import org.mariotaku.twidere.R
import org.mariotaku.twidere.adapter.iface.ILoadMoreSupportAdapter import org.mariotaku.twidere.adapter.iface.ILoadMoreSupportAdapter
import org.mariotaku.twidere.model.ItemCounts import org.mariotaku.twidere.model.ItemCounts
import org.mariotaku.twidere.model.ParcelableUserList import org.mariotaku.twidere.model.ParcelableUserList
import org.mariotaku.twidere.util.view.display import org.mariotaku.twidere.extension.view.holder.display
import org.mariotaku.twidere.view.holder.SimpleUserListViewHolder import org.mariotaku.twidere.view.holder.SimpleUserListViewHolder
class SimpleParcelableUserListsAdapter( class SimpleParcelableUserListsAdapter(

View File

@ -0,0 +1,29 @@
/*
* 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.Point
import android.graphics.Rect
/**
* Created by mariotaku on 2017/2/20.
*/
val Rect.origin: Point get() = Point(left, top)

View File

@ -0,0 +1,69 @@
/*
* 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.Rect
import android.graphics.RectF
import android.support.annotation.MainThread
import android.view.View
private val tempLocation = IntArray(2)
private val tempRect = Rect()
@MainThread
fun View.getBounds(rect: RectF) {
rect.set(x, y, x + width, y + height)
}
@MainThread
fun View.getFrame(rect: Rect) {
rect.set(left, top, right, bottom)
}
@MainThread
fun View.getFrameRelatedTo(rect: Rect, view: View? = null) {
if (view != null) {
view.getFrame(tempRect)
offsetToRoot(view, tempRect)
}
this.getFrame(rect)
offsetToRoot(this, rect)
if (view != null) {
rect.offset(-tempRect.left, -tempRect.top)
}
}
fun View.getLocationOnScreen(rect: Rect) {
getLocationOnScreen(tempLocation)
rect.set(tempLocation[0], tempLocation[1], tempLocation[0] + width, tempLocation[1] + height)
}
fun View.getLocationInWindow(rect: Rect) {
getLocationInWindow(tempLocation)
rect.set(tempLocation[0], tempLocation[1], tempLocation[0] + width, tempLocation[1] + height)
}
private fun offsetToRoot(view: View, rect: Rect) {
var parent = view.parent as? View
while (parent != null) {
rect.offset(parent.left, parent.top)
parent = parent.parent as? View
}
}

View File

@ -0,0 +1,40 @@
/*
* 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.view.holder
import android.view.View
import org.mariotaku.twidere.R
import org.mariotaku.twidere.model.ParcelableUserList
import org.mariotaku.twidere.util.MediaLoaderWrapper
import org.mariotaku.twidere.util.UserColorNameManager
import org.mariotaku.twidere.view.holder.SimpleUserListViewHolder
fun SimpleUserListViewHolder.display(userList: ParcelableUserList, mediaLoader: MediaLoaderWrapper,
userColorNameManager: UserColorNameManager, displayProfileImage: Boolean) {
nameView.text = userList.name
createdByView.text = createdByView.context.getString(R.string.created_by,
userColorNameManager.getDisplayName(userList, false))
profileImageView.visibility = if (displayProfileImage) View.VISIBLE else View.GONE
if (displayProfileImage) {
mediaLoader.displayProfileImage(profileImageView, userList)
} else {
mediaLoader.cancelDisplayTask(profileImageView)
}
}

View File

@ -40,7 +40,6 @@ import org.mariotaku.ktextension.coerceInOr
import org.mariotaku.ktextension.isNullOrEmpty import org.mariotaku.ktextension.isNullOrEmpty
import org.mariotaku.ktextension.rangeOfSize import org.mariotaku.ktextension.rangeOfSize
import org.mariotaku.twidere.R import org.mariotaku.twidere.R
import org.mariotaku.twidere.TwidereConstants
import org.mariotaku.twidere.adapter.ParcelableActivitiesAdapter import org.mariotaku.twidere.adapter.ParcelableActivitiesAdapter
import org.mariotaku.twidere.adapter.ParcelableActivitiesAdapter.Companion.ITEM_VIEW_TYPE_GAP import org.mariotaku.twidere.adapter.ParcelableActivitiesAdapter.Companion.ITEM_VIEW_TYPE_GAP
import org.mariotaku.twidere.adapter.ParcelableActivitiesAdapter.Companion.ITEM_VIEW_TYPE_STATUS import org.mariotaku.twidere.adapter.ParcelableActivitiesAdapter.Companion.ITEM_VIEW_TYPE_STATUS
@ -305,7 +304,7 @@ abstract class AbsActivitiesFragment protected constructor() :
override fun onGapClick(holder: GapViewHolder, position: Int) { override fun onGapClick(holder: GapViewHolder, position: Int) {
val activity = adapter.getActivity(position) ?: return val activity = adapter.getActivity(position) ?: return
DebugLog.v(TwidereConstants.LOGTAG, "Load activity gap $activity") DebugLog.v(msg = "Load activity gap $activity")
val accountIds = arrayOf(activity.account_key) val accountIds = arrayOf(activity.account_key)
val maxIds = arrayOf(activity.min_position) val maxIds = arrayOf(activity.min_position)
val maxSortIds = longArrayOf(activity.min_sort_position) val maxSortIds = longArrayOf(activity.min_sort_position)

View File

@ -39,7 +39,6 @@ import kotlinx.android.synthetic.main.fragment_content_recyclerview.*
import org.mariotaku.kpreferences.get import org.mariotaku.kpreferences.get
import org.mariotaku.ktextension.* import org.mariotaku.ktextension.*
import org.mariotaku.twidere.R import org.mariotaku.twidere.R
import org.mariotaku.twidere.TwidereConstants
import org.mariotaku.twidere.activity.AccountSelectorActivity import org.mariotaku.twidere.activity.AccountSelectorActivity
import org.mariotaku.twidere.adapter.ParcelableStatusesAdapter import org.mariotaku.twidere.adapter.ParcelableStatusesAdapter
import org.mariotaku.twidere.adapter.decorator.DividerItemDecoration import org.mariotaku.twidere.adapter.decorator.DividerItemDecoration
@ -353,7 +352,7 @@ abstract class AbsStatusesFragment : AbsContentListRecyclerViewFragment<Parcelab
override fun onGapClick(holder: GapViewHolder, position: Int) { override fun onGapClick(holder: GapViewHolder, position: Int) {
val adapter = this.adapter val adapter = this.adapter
val status = adapter.getStatus(position) ?: return val status = adapter.getStatus(position) ?: return
DebugLog.v(TwidereConstants.LOGTAG, "Load activity gap " + status) DebugLog.v(msg = "Load activity gap $status")
adapter.addGapLoadingId(ObjectId(status.account_key, status.id)) adapter.addGapLoadingId(ObjectId(status.account_key, status.id))
val accountIds = arrayOf(status.account_key) val accountIds = arrayOf(status.account_key)
val maxIds = arrayOf<String?>(status.id) val maxIds = arrayOf<String?>(status.id)

View File

@ -19,29 +19,79 @@
package org.mariotaku.twidere.fragment.message package org.mariotaku.twidere.fragment.message
import android.content.Context
import android.os.Bundle import android.os.Bundle
import android.support.v4.app.FragmentActivity import android.support.v4.app.FragmentActivity
import android.support.v4.app.LoaderManager
import android.support.v4.content.AsyncTaskLoader
import android.support.v4.content.Loader
import android.support.v7.app.AppCompatActivity
import android.support.v7.widget.Toolbar import android.support.v7.widget.Toolbar
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import kotlinx.android.synthetic.main.activity_home_content.view.* import kotlinx.android.synthetic.main.activity_home_content.view.*
import kotlinx.android.synthetic.main.fragment_messages_conversation_info.* import kotlinx.android.synthetic.main.fragment_messages_conversation_info.*
import kotlinx.android.synthetic.main.layout_toolbar_message_conversation_title.*
import org.mariotaku.chameleon.Chameleon
import org.mariotaku.chameleon.ChameleonUtils
import org.mariotaku.kpreferences.get
import org.mariotaku.ktextension.useCursor
import org.mariotaku.sqliteqb.library.Expression
import org.mariotaku.twidere.R import org.mariotaku.twidere.R
import org.mariotaku.twidere.constant.IntentConstants.EXTRA_ACCOUNT_KEY
import org.mariotaku.twidere.constant.IntentConstants.EXTRA_CONVERSATION_ID
import org.mariotaku.twidere.constant.nameFirstKey
import org.mariotaku.twidere.constant.profileImageStyleKey
import org.mariotaku.twidere.extension.model.displayAvatarTo
import org.mariotaku.twidere.extension.model.getConversationName
import org.mariotaku.twidere.fragment.BaseFragment import org.mariotaku.twidere.fragment.BaseFragment
import org.mariotaku.twidere.fragment.iface.IToolBarSupportFragment import org.mariotaku.twidere.fragment.iface.IToolBarSupportFragment
import org.mariotaku.twidere.model.ParcelableMessageConversation
import org.mariotaku.twidere.model.ParcelableMessageConversationCursorIndices
import org.mariotaku.twidere.model.UserKey
import org.mariotaku.twidere.provider.TwidereDataStore.Messages.Conversations
/** /**
* Created by mariotaku on 2017/2/15. * Created by mariotaku on 2017/2/15.
*/ */
class MessageConversationInfoFragment : BaseFragment(), IToolBarSupportFragment { class MessageConversationInfoFragment : BaseFragment(), IToolBarSupportFragment,
LoaderManager.LoaderCallbacks<ParcelableMessageConversation?> {
private val accountKey: UserKey get() = arguments.getParcelable(EXTRA_ACCOUNT_KEY)
private val conversationId: String get() = arguments.getString(EXTRA_CONVERSATION_ID)
override val controlBarHeight: Int get() = toolbar.measuredHeight override val controlBarHeight: Int get() = toolbar.measuredHeight
override var controlBarOffset: Float = 0f override var controlBarOffset: Float = 0f
override val toolbar: Toolbar override val toolbar: Toolbar
get() = toolbarLayout.toolbar get() = toolbarLayout.toolbar
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
val activity = this.activity
if (activity is AppCompatActivity) {
activity.supportActionBar?.setDisplayShowTitleEnabled(false)
}
val theme = Chameleon.getOverrideTheme(context, activity)
val profileImageStyle = preferences[profileImageStyleKey]
appBarConversationAvatar.style = profileImageStyle
conversationAvatar.style = profileImageStyle
val avatarBackground = ChameleonUtils.getColorDependent(theme.colorToolbar)
appBarConversationAvatar.setBackgroundColor(avatarBackground)
appBarConversationName.setTextColor(ChameleonUtils.getColorDependent(theme.colorToolbar))
conversationAvatar.setBackgroundColor(avatarBackground)
conversationName.setTextColor(ChameleonUtils.getColorDependent(theme.colorToolbar))
loaderManager.initLoader(0, null, this)
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
return inflater.inflate(R.layout.fragment_messages_conversation_info, container, false) return inflater.inflate(R.layout.fragment_messages_conversation_info, container, false)
} }
@ -49,4 +99,45 @@ class MessageConversationInfoFragment : BaseFragment(), IToolBarSupportFragment
override fun setupWindow(activity: FragmentActivity): Boolean { override fun setupWindow(activity: FragmentActivity): Boolean {
return false return false
} }
override fun onCreateLoader(id: Int, args: Bundle?): Loader<ParcelableMessageConversation?> {
return ConversationInfoLoader(context, accountKey, conversationId)
}
override fun onLoaderReset(loader: Loader<ParcelableMessageConversation?>?) {
}
override fun onLoadFinished(loader: Loader<ParcelableMessageConversation?>?, data: ParcelableMessageConversation?) {
if (data == null) {
activity?.finish()
return
}
val name = data.getConversationName(context, userColorNameManager, preferences[nameFirstKey]).first
data.displayAvatarTo(mediaLoader, conversationAvatar)
data.displayAvatarTo(mediaLoader, appBarConversationAvatar)
appBarConversationName.text = name
conversationName.text = name
}
class ConversationInfoLoader(
context: Context,
val accountKey: UserKey,
val conversationId: String) : AsyncTaskLoader<ParcelableMessageConversation?>(context) {
override fun loadInBackground(): ParcelableMessageConversation? {
val where = Expression.and(Expression.equalsArgs(Conversations.ACCOUNT_KEY),
Expression.equalsArgs(Conversations.CONVERSATION_ID)).sql
val whereArgs = arrayOf(accountKey.toString(), conversationId)
context.contentResolver.query(Conversations.CONTENT_URI, Conversations.COLUMNS, where,
whereArgs, null).useCursor { cur ->
if (cur.moveToFirst()) {
return ParcelableMessageConversationCursorIndices.fromCursor(cur)
}
}
return null
}
override fun onStartLoading() {
forceLoad()
}
}
} }

View File

@ -163,6 +163,10 @@ class MessagesConversationFragment : AbsContentListRecyclerViewFragment<Messages
addMedia.setOnClickListener { addMedia.setOnClickListener {
openMediaPicker() openMediaPicker()
} }
conversationTitleContainer.setOnClickListener {
val intent = IntentUtils.messageConversationInfo(accountKey, conversationId)
startActivity(intent)
}
val activity = this.activity val activity = this.activity
if (activity is AppCompatActivity) { if (activity is AppCompatActivity) {
@ -228,11 +232,6 @@ class MessagesConversationFragment : AbsContentListRecyclerViewFragment<Messages
override fun onOptionsItemSelected(item: MenuItem): Boolean { override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) { when (item.itemId) {
R.id.info -> {
val intent = IntentUtils.messageConversationInfo(accountKey, conversationId)
startActivity(intent)
return true
}
} }
return false return false
} }

View File

@ -15,7 +15,7 @@ import org.mariotaku.twidere.fragment.CustomTabsFragment.TabEditorDialogFragment
import org.mariotaku.twidere.model.ParcelableUserList import org.mariotaku.twidere.model.ParcelableUserList
import org.mariotaku.twidere.model.tab.TabConfiguration import org.mariotaku.twidere.model.tab.TabConfiguration
import org.mariotaku.twidere.util.dagger.DependencyHolder import org.mariotaku.twidere.util.dagger.DependencyHolder
import org.mariotaku.twidere.util.view.display import org.mariotaku.twidere.extension.view.holder.display
import org.mariotaku.twidere.view.holder.SimpleUserListViewHolder import org.mariotaku.twidere.view.holder.SimpleUserListViewHolder
/** /**

View File

@ -2,6 +2,7 @@ package org.mariotaku.twidere.util
import android.util.Log import android.util.Log
import org.mariotaku.twidere.BuildConfig import org.mariotaku.twidere.BuildConfig
import org.mariotaku.twidere.TwidereConstants.LOGTAG
/** /**
* Created by mariotaku on 2017/1/24. * Created by mariotaku on 2017/1/24.
@ -9,7 +10,7 @@ import org.mariotaku.twidere.BuildConfig
object DebugLog { object DebugLog {
@JvmStatic @JvmStatic
fun v(tag: String, msg: String, tr: Throwable? = null): Int { fun v(tag: String = LOGTAG, msg: String, tr: Throwable? = null): Int {
if (!BuildConfig.DEBUG) return 0 if (!BuildConfig.DEBUG) return 0
if (tr != null) { if (tr != null) {
return Log.v(tag, msg, tr) return Log.v(tag, msg, tr)
@ -19,7 +20,7 @@ object DebugLog {
} }
@JvmStatic @JvmStatic
fun d(tag: String, msg: String, tr: Throwable? = null): Int { fun d(tag: String = LOGTAG, msg: String, tr: Throwable? = null): Int {
if (!BuildConfig.DEBUG) return 0 if (!BuildConfig.DEBUG) return 0
if (tr != null) { if (tr != null) {
return Log.d(tag, msg, tr) return Log.d(tag, msg, tr)
@ -29,7 +30,7 @@ object DebugLog {
} }
@JvmStatic @JvmStatic
fun w(tag: String, msg: String? = null, tr: Throwable? = null): Int { fun w(tag: String = LOGTAG, msg: String? = null, tr: Throwable? = null): Int {
if (!BuildConfig.DEBUG) return 0 if (!BuildConfig.DEBUG) return 0
if (msg != null && tr != null) { if (msg != null && tr != null) {
return Log.w(tag, msg, tr) return Log.w(tag, msg, tr)

View File

@ -0,0 +1,122 @@
/*
* 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.util.view
import android.content.Context
import android.graphics.Rect
import android.support.design.widget.AppBarLayout
import android.support.design.widget.CoordinatorLayout
import android.support.v4.view.ViewCompat
import android.util.AttributeSet
import android.view.View
import kotlinx.android.synthetic.main.fragment_messages_conversation_info.view.*
import kotlinx.android.synthetic.main.layout_toolbar_message_conversation_title.view.*
import org.mariotaku.twidere.R
import org.mariotaku.twidere.extension.getFrame
import org.mariotaku.twidere.extension.getFrameRelatedTo
import org.mariotaku.twidere.extension.getLocationOnScreen
import org.mariotaku.twidere.extension.origin
/**
* Created by mariotaku on 2017/2/20.
*/
class ConversationInfoAvatarBehavior(
context: Context,
attrs: AttributeSet? = null
) : CoordinatorLayout.Behavior<View>(context, attrs) {
private val marginBottom: Float
private val marginStart: Float
private val appBarRect = Rect()
private val sourceRect = Rect()
private val destRect = Rect()
private val tempRect = Rect()
private var toolbarLayoutHeight = 0f
var dependentPercent: Float = 0f
private set
init {
val a = context.obtainStyledAttributes(attrs, R.styleable.ConversationInfoAvatarBehavior)
marginBottom = a.getDimension(R.styleable.ConversationInfoAvatarBehavior_behavior_avatarMarginBottom, 0f)
marginStart = a.getDimension(R.styleable.ConversationInfoAvatarBehavior_behavior_avatarMarginStart, 0f)
a.recycle()
}
override fun layoutDependsOn(parent: CoordinatorLayout, child: View, dependency: View): Boolean {
return dependency is AppBarLayout
}
override fun onLayoutChild(parent: CoordinatorLayout, child: View, layoutDirection: Int): Boolean {
val appBar = parent.getDependencies(child).first()
// Get AppBar frame (and reset origin to 0, 0)
appBar.getFrame(appBarRect)
val dependencyOrigin = appBarRect.origin
appBarRect.offsetTo(0, 0)
// Layout avatar
val left: Int
val right: Int
if (layoutDirection == ViewCompat.LAYOUT_DIRECTION_RTL) {
right = appBarRect.right - Math.round(marginStart)
left = right - child.measuredWidth
} else {
left = Math.round(marginStart)
right = left + child.measuredWidth
}
val bottom = Math.round(appBarRect.bottom - marginBottom)
val top = bottom - child.measuredHeight
child.layout(left, top, right, bottom)
val toolbarLayout = appBar.toolbarLayout
toolbarLayoutHeight = toolbarLayout.height.toFloat()
child.getFrameRelatedTo(sourceRect)
appBar.conversationAvatar.getFrameRelatedTo(destRect)
return true
}
override fun onDependentViewChanged(parent: CoordinatorLayout, child: View, dependency: View): Boolean {
val behavior = (dependency.layoutParams as CoordinatorLayout.LayoutParams).behavior as AppBarLayout.Behavior
val toolbar = dependency.toolbar
toolbar.getLocationOnScreen(tempRect)
val percent = behavior.topAndBottomOffset / (tempRect.bottom - toolbarLayoutHeight)
child.pivotX = child.width.toFloat()
child.pivotY = child.height.toFloat()
child.scaleX = 1 - (sourceRect.width() - destRect.width()) * percent / sourceRect.width()
child.scaleY = 1 - (sourceRect.height() - destRect.height()) * percent / sourceRect.height()
child.translationX = (destRect.right - sourceRect.right) * percent
child.translationY = (destRect.bottom - sourceRect.bottom) * percent
if (percent >= 1) {
child.visibility = View.INVISIBLE
dependency.conversationAvatar.visibility = View.VISIBLE
} else {
child.visibility = View.VISIBLE
dependency.conversationAvatar.visibility = View.INVISIBLE
}
dependentPercent = percent
return true
}
}

View File

@ -0,0 +1,107 @@
/*
* 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.util.view
import android.content.Context
import android.graphics.Rect
import android.support.design.widget.CoordinatorLayout
import android.support.v4.view.ViewCompat
import android.util.AttributeSet
import android.util.TypedValue
import android.view.View
import android.widget.TextView
import kotlinx.android.synthetic.main.layout_toolbar_message_conversation_title.view.*
import org.mariotaku.twidere.R
import org.mariotaku.twidere.extension.getFrame
import org.mariotaku.twidere.extension.getFrameRelatedTo
/**
* Created by mariotaku on 2017/2/20.
*/
class ConversationInfoTitleBehavior(
context: Context,
attrs: AttributeSet? = null
) : CoordinatorLayout.Behavior<TextView>(context, attrs) {
private val marginBottom: Float
private val marginStart: Float
private val avatarRect = Rect()
private val sourceRect = Rect()
private val destRect = Rect()
private var sourceSize: Float = Float.NaN
private var destSize: Float = Float.NaN
private var viewLaidOut: Boolean = false
init {
val a = context.obtainStyledAttributes(attrs, R.styleable.ConversationInfoTitleBehavior)
marginBottom = a.getDimension(R.styleable.ConversationInfoTitleBehavior_behavior_titleMarginBottom, 0f)
marginStart = a.getDimension(R.styleable.ConversationInfoTitleBehavior_behavior_titleMarginStart, 0f)
a.recycle()
}
override fun layoutDependsOn(parent: CoordinatorLayout, child: TextView, dependency: View): Boolean {
return dependency.id == R.id.appBarConversationAvatar
}
override fun onLayoutChild(parent: CoordinatorLayout, child: TextView, layoutDirection: Int): Boolean {
val conversationAvatar = parent.getDependencies(child).first()
val appBar = conversationAvatar.parent as View
conversationAvatar.getFrame(avatarRect)
if (!viewLaidOut) {
sourceSize = child.textSize
destSize = parent.conversationName.textSize
viewLaidOut = true
}
if (layoutDirection == ViewCompat.LAYOUT_DIRECTION_RTL) {
child.right = Math.round(avatarRect.left - marginStart)
child.left = child.right - child.measuredWidth
} else {
child.left = Math.round(avatarRect.right + marginStart)
child.right = child.left + child.measuredWidth
}
child.bottom = Math.round(avatarRect.centerY() - marginBottom)
child.top = child.bottom - child.measuredHeight
child.getFrameRelatedTo(sourceRect)
appBar.conversationName.getFrameRelatedTo(destRect)
return true
}
override fun onDependentViewChanged(parent: CoordinatorLayout, child: TextView, dependency: View): Boolean {
val behavior = (dependency.layoutParams as CoordinatorLayout.LayoutParams).behavior as ConversationInfoAvatarBehavior
val percent = behavior.dependentPercent
child.pivotX = child.width.toFloat()
child.pivotY = child.height.toFloat()
child.translationX = (destRect.left - sourceRect.left) * percent
child.translationY = (destRect.top - sourceRect.top) * percent
child.setTextSize(TypedValue.COMPLEX_UNIT_PX, sourceSize + (destSize - sourceSize) * percent)
if (percent >= 1) {
child.visibility = View.INVISIBLE
parent.conversationName.visibility = View.VISIBLE
} else {
child.visibility = View.VISIBLE
parent.conversationName.visibility = View.INVISIBLE
}
return true
}
}

View File

@ -1,22 +0,0 @@
package org.mariotaku.twidere.util.view
import android.view.View
import org.mariotaku.twidere.R
import org.mariotaku.twidere.model.ParcelableUserList
import org.mariotaku.twidere.util.MediaLoaderWrapper
import org.mariotaku.twidere.util.UserColorNameManager
import org.mariotaku.twidere.view.holder.SimpleUserListViewHolder
fun SimpleUserListViewHolder.display(userList: ParcelableUserList, mediaLoader: MediaLoaderWrapper,
userColorNameManager: UserColorNameManager,
displayProfileImage: Boolean) {
nameView.text = userList.name
createdByView.text = createdByView.context.getString(R.string.created_by,
userColorNameManager.getDisplayName(userList, false))
profileImageView.visibility = if (displayProfileImage) View.VISIBLE else View.GONE
if (displayProfileImage) {
mediaLoader.displayProfileImage(profileImageView, userList)
} else {
mediaLoader.cancelDisplayTask(profileImageView)
}
}

View File

@ -167,6 +167,7 @@
android:background="?colorToolbar" android:background="?colorToolbar"
android:elevation="@dimen/toolbar_elevation" android:elevation="@dimen/toolbar_elevation"
app:contentInsetStart="0dp" app:contentInsetStart="0dp"
app:contentInsetStartWithNavigation="0dp"
app:popupTheme="?actionBarPopupTheme" app:popupTheme="?actionBarPopupTheme"
tools:elevation="0dp"> tools:elevation="0dp">

View File

@ -10,7 +10,8 @@
<android.support.design.widget.AppBarLayout <android.support.design.widget.AppBarLayout
android:id="@+id/appBar" android:id="@+id/appBar"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="128dp" android:layout_height="180dp"
android:background="?colorToolbar"
android:fitsSystemWindows="true"> android:fitsSystemWindows="true">
<android.support.design.widget.CollapsingToolbarLayout <android.support.design.widget.CollapsingToolbarLayout
@ -18,14 +19,23 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:fitsSystemWindows="true" android:fitsSystemWindows="true"
app:contentScrim="?attr/colorPrimary" app:layout_scrollFlags="scroll|exitUntilCollapsed"
app:layout_scrollFlags="scroll|exitUntilCollapsed"> app:titleEnabled="false">
<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"
android:layout_height="?attr/actionBarSize" android:layout_height="?attr/actionBarSize"
app:layout_collapseMode="pin"/> android:background="?colorToolbar"
app:contentInsetStart="0dp"
app:contentInsetStartWithNavigation="0dp"
app:layout_collapseMode="pin">
<include
layout="@layout/layout_toolbar_message_conversation_title"
tools:visibility="gone"/>
</android.support.v7.widget.Toolbar>
</android.support.design.widget.CollapsingToolbarLayout> </android.support.design.widget.CollapsingToolbarLayout>
</android.support.design.widget.AppBarLayout> </android.support.design.widget.AppBarLayout>
@ -48,8 +58,42 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_margin="@dimen/element_spacing_large" android:layout_margin="@dimen/element_spacing_large"
android:src="@drawable/ic_action_edit" android:src="@drawable/ic_action_edit"
app:backgroundTint="?android:colorBackground"
app:layout_anchor="@+id/appBar" app:layout_anchor="@+id/appBar"
app:layout_anchorGravity="bottom|end" app:layout_anchorGravity="bottom|end"
tools:backgroundTint="?colorAccent"/> tools:tint="?android:colorForeground"/>
<org.mariotaku.twidere.view.ProfileImageView
android:id="@+id/appBarConversationAvatar"
android:layout_width="@dimen/icon_size_conversation_info"
android:layout_height="@dimen/icon_size_conversation_info"
app:behavior_avatarMarginBottom="@dimen/element_spacing_large"
app:behavior_avatarMarginStart="@dimen/element_spacing_large"
app:layout_behavior="org.mariotaku.twidere.util.view.ConversationInfoAvatarBehavior"
app:sivDrawShadow="false"
app:sivElevation="8dp"
app:sivShape="circle"
tools:src="@drawable/ic_profile_image_default_group"/>
<org.mariotaku.twidere.view.FixedTextView
android:id="@+id/appBarConversationName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:elevation="8dp"
android:ellipsize="end"
android:minLines="1"
android:textAppearance="?android:textAppearanceLarge"
android:textColor="?android:textColorPrimary"
android:textStyle="normal"
app:behavior_titleMarginStart="@dimen/element_spacing_normal"
app:layout_behavior="org.mariotaku.twidere.util.view.ConversationInfoTitleBehavior"
tools:text="Title"/>
<org.mariotaku.twidere.view.FixedTextView
android:id="@+id/appBarConversationSummary"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="?android:textColorSecondary"
tools:text="Subtitle"/>
</android.support.design.widget.CoordinatorLayout> </android.support.design.widget.CoordinatorLayout>

View File

@ -21,6 +21,7 @@
<LinearLayout <LinearLayout
android:id="@+id/conversationTitleContainer" android:id="@+id/conversationTitleContainer"
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
@ -35,6 +36,7 @@
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_margin="@dimen/element_spacing_normal" android:layout_margin="@dimen/element_spacing_normal"
android:layout_weight="0" android:layout_weight="0"
app:sivShape="circle"
tools:src="@drawable/ic_profile_image_default_group"/> tools:src="@drawable/ic_profile_image_default_group"/>
<LinearLayout <LinearLayout
@ -43,7 +45,8 @@
android:layout_marginLeft="@dimen/element_spacing_normal" android:layout_marginLeft="@dimen/element_spacing_normal"
android:layout_marginStart="@dimen/element_spacing_normal" android:layout_marginStart="@dimen/element_spacing_normal"
android:layout_weight="1" android:layout_weight="1"
android:gravity="center_vertical"> android:gravity="center_vertical"
android:orientation="vertical">
<org.mariotaku.twidere.view.FixedTextView <org.mariotaku.twidere.view.FixedTextView
android:id="@+id/conversationName" android:id="@+id/conversationName"
@ -52,5 +55,13 @@
android:textAppearance="?android:textAppearanceMedium" android:textAppearance="?android:textAppearanceMedium"
android:textColor="?android:textColorPrimary" android:textColor="?android:textColorPrimary"
tools:text="Conversation name"/> tools:text="Conversation name"/>
<org.mariotaku.twidere.view.FixedTextView
android:id="@+id/conversationSummary"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:textAppearanceSmall"
android:textColor="?android:textColorSecondary"
tools:text="Conversation summary"/>
</LinearLayout> </LinearLayout>
</LinearLayout> </LinearLayout>

View File

@ -20,10 +20,6 @@
<menu xmlns:android="http://schemas.android.com/apk/res/android" <menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"> xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/info"
android:icon="@drawable/ic_action_info"
android:title="@string/action_conversation_info"/>
<item <item
android:id="@+id/search" android:id="@+id/search"
android:enabled="@bool/debug" android:enabled="@bool/debug"

View File

@ -103,6 +103,7 @@
<attr name="sivElevation" format="dimension"/> <attr name="sivElevation" format="dimension"/>
<attr name="sivCornerRadius" format="dimension"/> <attr name="sivCornerRadius" format="dimension"/>
<attr name="sivCornerRadiusRatio" format="fraction"/> <attr name="sivCornerRadiusRatio" format="fraction"/>
<attr name="sivDrawShadow" format="boolean"/>
<attr name="sivShape"/> <attr name="sivShape"/>
</declare-styleable> </declare-styleable>
<declare-styleable name="HeaderDrawerLayout"> <declare-styleable name="HeaderDrawerLayout">
@ -144,6 +145,14 @@
<declare-styleable name="ProfileBannerImageView"> <declare-styleable name="ProfileBannerImageView">
<attr name="bannerAspectRatio" format="fraction"/> <attr name="bannerAspectRatio" format="fraction"/>
</declare-styleable> </declare-styleable>
<declare-styleable name="ConversationInfoAvatarBehavior">
<attr name="behavior_avatarMarginBottom" format="dimension"/>
<attr name="behavior_avatarMarginStart" format="dimension"/>
</declare-styleable>
<declare-styleable name="ConversationInfoTitleBehavior">
<attr name="behavior_titleMarginBottom" format="dimension"/>
<attr name="behavior_titleMarginStart" format="dimension"/>
</declare-styleable>
<attr name="notificationType"> <attr name="notificationType">
<flag name="none" value="0"/> <flag name="none" value="0"/>
<flag name="ringtone" value="1"/> <flag name="ringtone" value="1"/>

View File

@ -81,6 +81,7 @@
<dimen name="icon_size_profile_type_user_profile">26dp</dimen> <dimen name="icon_size_profile_type_user_profile">26dp</dimen>
<dimen name="icon_size_status_profile_image">48dp</dimen> <dimen name="icon_size_status_profile_image">48dp</dimen>
<dimen name="icon_size_user_profile">84dp</dimen> <dimen name="icon_size_user_profile">84dp</dimen>
<dimen name="icon_size_conversation_info">64dp</dimen>
<dimen name="line_width_compose_account_profile_image">2dp</dimen> <dimen name="line_width_compose_account_profile_image">2dp</dimen>