More animation

This commit is contained in:
Niels Andriesse 2021-06-17 10:53:56 +10:00
parent 4587f40128
commit 7cacee8499
5 changed files with 74 additions and 5 deletions

View File

@ -2,19 +2,23 @@ package org.thoughtcrime.securesms.conversation.v2
import android.animation.FloatEvaluator import android.animation.FloatEvaluator
import android.animation.ValueAnimator import android.animation.ValueAnimator
import android.content.res.Resources
import android.database.Cursor import android.database.Cursor
import android.os.Bundle import android.os.Bundle
import android.util.Log
import android.view.ActionMode import android.view.ActionMode
import android.view.Menu import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
import android.view.MotionEvent
import android.widget.RelativeLayout import android.widget.RelativeLayout
import androidx.core.view.isVisible
import androidx.loader.app.LoaderManager import androidx.loader.app.LoaderManager
import androidx.loader.content.Loader import androidx.loader.content.Loader
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import kotlinx.android.synthetic.main.activity_conversation_v2.* import kotlinx.android.synthetic.main.activity_conversation_v2.*
import kotlinx.android.synthetic.main.activity_conversation_v2.view.*
import kotlinx.android.synthetic.main.activity_conversation_v2_action_bar.* import kotlinx.android.synthetic.main.activity_conversation_v2_action_bar.*
import kotlinx.android.synthetic.main.view_input_bar.view.* import kotlinx.android.synthetic.main.view_input_bar.view.*
import kotlinx.android.synthetic.main.view_input_bar_recording.view.*
import network.loki.messenger.R import network.loki.messenger.R
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity
import org.thoughtcrime.securesms.conversation.v2.input_bar.InputBarDelegate import org.thoughtcrime.securesms.conversation.v2.input_bar.InputBarDelegate
@ -22,8 +26,9 @@ import org.thoughtcrime.securesms.conversation.v2.menus.ConversationActionModeCa
import org.thoughtcrime.securesms.conversation.v2.menus.ConversationMenuHelper import org.thoughtcrime.securesms.conversation.v2.menus.ConversationMenuHelper
import org.thoughtcrime.securesms.database.DatabaseFactory import org.thoughtcrime.securesms.database.DatabaseFactory
import org.thoughtcrime.securesms.database.model.MessageRecord import org.thoughtcrime.securesms.database.model.MessageRecord
import org.thoughtcrime.securesms.loki.views.NewConversationButtonSetView
import org.thoughtcrime.securesms.mms.GlideApp import org.thoughtcrime.securesms.mms.GlideApp
import kotlin.math.abs
import kotlin.math.sqrt
class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDelegate { class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDelegate {
private var threadID: Long = -1 private var threadID: Long = -1
@ -57,6 +62,10 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
private val glide by lazy { GlideApp.with(this) } private val glide by lazy { GlideApp.with(this) }
private val screenWidth by lazy {
Resources.getSystem().displayMetrics.widthPixels
}
// region Settings // region Settings
companion object { companion object {
const val THREAD_ID = "thread_id" const val THREAD_ID = "thread_id"
@ -167,5 +176,45 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
} }
} }
} }
override fun onMicrophoneButtonMove(event: MotionEvent) {
val rawX = event.rawX
val chevronImageView = inputBarRecordingView.inputBarChevronImageView
val slideToCancelTextView = inputBarRecordingView.inputBarSlideToCancelTextView
if (rawX < screenWidth / 2) {
val translationX = rawX - screenWidth / 2
val sign = -1.0f
val chevronDamping = 4.0f
val labelDamping = 3.0f
val chevronX = (chevronDamping * (sqrt(abs(translationX)) / sqrt(chevronDamping))) * sign
val labelX = (labelDamping * (sqrt(abs(translationX)) / sqrt(labelDamping))) * sign
chevronImageView.translationX = chevronX
slideToCancelTextView.translationX = labelX
} else {
chevronImageView.translationX = 0.0f
slideToCancelTextView.translationX = 0.0f
}
}
override fun onMicrophoneButtonCancel(event: MotionEvent) {
resetVoiceMessageUI()
}
override fun onMicrophoneButtonUp(event: MotionEvent) {
resetVoiceMessageUI()
}
private fun resetVoiceMessageUI() {
val chevronImageView = inputBarRecordingView.inputBarChevronImageView
val slideToCancelTextView = inputBarRecordingView.inputBarSlideToCancelTextView
listOf( chevronImageView, slideToCancelTextView ).forEach { view ->
val animation = ValueAnimator.ofObject(FloatEvaluator(), view.translationX, 0.0f)
animation.duration = 250L
animation.addUpdateListener { animator ->
view.translationX = animator.animatedValue as Float
}
animation.start()
}
}
// endregion // endregion
} }

View File

@ -3,6 +3,7 @@ package org.thoughtcrime.securesms.conversation.v2.input_bar
import android.content.Context import android.content.Context
import android.util.AttributeSet import android.util.AttributeSet
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.MotionEvent
import android.widget.RelativeLayout import android.widget.RelativeLayout
import androidx.core.view.isVisible import androidx.core.view.isVisible
import kotlinx.android.synthetic.main.view_input_bar.view.* import kotlinx.android.synthetic.main.view_input_bar.view.*
@ -34,6 +35,9 @@ class InputBar : RelativeLayout, InputBarEditTextDelegate {
microphoneButton.onLongPress = { microphoneButton.onLongPress = {
showVoiceMessageUI() showVoiceMessageUI()
} }
microphoneButton.onMove = { delegate?.onMicrophoneButtonMove(it) }
microphoneButton.onCancel = { delegate?.onMicrophoneButtonCancel(it) }
microphoneButton.onUp = { delegate?.onMicrophoneButtonUp(it) }
// Send button // Send button
microphoneOrSendButtonContainer.addView(sendButton) microphoneOrSendButtonContainer.addView(sendButton)
sendButton.layoutParams = RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.MATCH_PARENT) sendButton.layoutParams = RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.MATCH_PARENT)
@ -69,4 +73,7 @@ interface InputBarDelegate {
fun inputBarHeightChanged(newValue: Int) fun inputBarHeightChanged(newValue: Int)
fun showVoiceMessageUI() fun showVoiceMessageUI()
fun onMicrophoneButtonMove(event: MotionEvent)
fun onMicrophoneButtonCancel(event: MotionEvent)
fun onMicrophoneButtonUp(event: MotionEvent)
} }

View File

@ -32,6 +32,9 @@ class InputBarButton : RelativeLayout {
private var onDownTimestamp = 0L private var onDownTimestamp = 0L
var onPress: (() -> Unit)? = null var onPress: (() -> Unit)? = null
var onMove: ((MotionEvent) -> Unit)? = null
var onCancel: ((MotionEvent) -> Unit)? = null
var onUp: ((MotionEvent) -> Unit)? = null
var onLongPress: (() -> Unit)? = null var onLongPress: (() -> Unit)? = null
companion object { companion object {
@ -112,6 +115,7 @@ class InputBarButton : RelativeLayout {
override fun onTouchEvent(event: MotionEvent): Boolean { override fun onTouchEvent(event: MotionEvent): Boolean {
when (event.action) { when (event.action) {
MotionEvent.ACTION_DOWN -> onDown(event) MotionEvent.ACTION_DOWN -> onDown(event)
MotionEvent.ACTION_MOVE -> onMove(event)
MotionEvent.ACTION_UP -> onUp(event) MotionEvent.ACTION_UP -> onUp(event)
MotionEvent.ACTION_CANCEL -> onCancel(event) MotionEvent.ACTION_CANCEL -> onCancel(event)
} }
@ -132,12 +136,18 @@ class InputBarButton : RelativeLayout {
onDownTimestamp = Date().time onDownTimestamp = Date().time
} }
private fun onMove(event: MotionEvent) {
onMove?.invoke(event)
}
private fun onCancel(event: MotionEvent) { private fun onCancel(event: MotionEvent) {
onCancel?.invoke(event)
collapse() collapse()
longPressCallback?.let { gestureHandler.removeCallbacks(it) } longPressCallback?.let { gestureHandler.removeCallbacks(it) }
} }
private fun onUp(event: MotionEvent) { private fun onUp(event: MotionEvent) {
onUp?.invoke(event)
collapse() collapse()
if ((Date().time - onDownTimestamp) < VisibleMessageView.longPressDurationThreshold) { if ((Date().time - onDownTimestamp) < VisibleMessageView.longPressDurationThreshold) {
longPressCallback?.let { gestureHandler.removeCallbacks(it) } longPressCallback?.let { gestureHandler.removeCallbacks(it) }

View File

@ -1,19 +1,17 @@
package org.thoughtcrime.securesms.conversation.v2.input_bar package org.thoughtcrime.securesms.conversation.v2.input_bar
import android.animation.Animator
import android.animation.FloatEvaluator import android.animation.FloatEvaluator
import android.animation.ValueAnimator import android.animation.ValueAnimator
import android.content.Context import android.content.Context
import android.util.AttributeSet import android.util.AttributeSet
import android.util.Log
import android.view.LayoutInflater import android.view.LayoutInflater
import android.widget.RelativeLayout import android.widget.RelativeLayout
import androidx.core.view.isVisible import androidx.core.view.isVisible
import kotlinx.android.synthetic.main.view_input_bar_recording.view.* import kotlinx.android.synthetic.main.view_input_bar_recording.view.*
import network.loki.messenger.R import network.loki.messenger.R
import org.thoughtcrime.securesms.loki.utilities.animateSizeChange import org.thoughtcrime.securesms.loki.utilities.animateSizeChange
import org.thoughtcrime.securesms.loki.utilities.disableClipping
import org.thoughtcrime.securesms.loki.utilities.toPx import org.thoughtcrime.securesms.loki.utilities.toPx
import kotlin.math.roundToInt
class InputBarRecordingView : RelativeLayout { class InputBarRecordingView : RelativeLayout {
@ -23,6 +21,7 @@ class InputBarRecordingView : RelativeLayout {
private fun initialize() { private fun initialize() {
LayoutInflater.from(context).inflate(R.layout.view_input_bar_recording, this) LayoutInflater.from(context).inflate(R.layout.view_input_bar_recording, this)
inputBarMiddleContentContainer.disableClipping()
} }
fun show() { fun show() {

View File

@ -50,13 +50,16 @@
<!-- The middle content (left arrow + slide to cancel) --> <!-- The middle content (left arrow + slide to cancel) -->
<LinearLayout <LinearLayout
android:id="@+id/inputBarMiddleContentContainer"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_centerInParent="true" android:layout_centerInParent="true"
android:paddingHorizontal="40dp"
android:orientation="horizontal" android:orientation="horizontal"
android:gravity="center_vertical"> android:gravity="center_vertical">
<ImageView <ImageView
android:id="@+id/inputBarChevronImageView"
android:layout_width="16dp" android:layout_width="16dp"
android:layout_height="16dp" android:layout_height="16dp"
android:src="@drawable/ic_keyboard_arrow_left_grey600_24dp" android:src="@drawable/ic_keyboard_arrow_left_grey600_24dp"
@ -65,6 +68,7 @@
android:alpha="0.6" /> android:alpha="0.6" />
<TextView <TextView
android:id="@+id/inputBarSlideToCancelTextView"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginLeft="4dp" android:layout_marginLeft="4dp"