kopia lustrzana https://github.com/ryukoposting/Signal-Android
Implement chat filter design feedback.
rodzic
495c91ba86
commit
9e8350e8c2
|
@ -115,6 +115,7 @@ import org.thoughtcrime.securesms.contacts.sync.CdsPermanentErrorBottomSheet;
|
|||
import org.thoughtcrime.securesms.contacts.sync.CdsTemporaryErrorBottomSheet;
|
||||
import org.thoughtcrime.securesms.conversation.ConversationFragment;
|
||||
import org.thoughtcrime.securesms.conversationlist.chatfilter.ConversationListFilterPullView;
|
||||
import org.thoughtcrime.securesms.conversationlist.chatfilter.FilterLerp;
|
||||
import org.thoughtcrime.securesms.conversationlist.model.Conversation;
|
||||
import org.thoughtcrime.securesms.conversationlist.model.UnreadPayments;
|
||||
import org.thoughtcrime.securesms.database.MessageTable.MarkedMessageInfo;
|
||||
|
@ -280,7 +281,7 @@ public class ConversationListFragment extends MainFragment implements ActionMode
|
|||
cameraFab.setVisibility(View.VISIBLE);
|
||||
|
||||
CollapsingToolbarLayout collapsingToolbarLayout = view.findViewById(R.id.collapsing_toolbar);
|
||||
int minHeight = (int) DimensionUnit.DP.toPixels(52);
|
||||
int openHeight = (int) DimensionUnit.DP.toPixels(FilterLerp.FILTER_OPEN_HEIGHT);
|
||||
|
||||
pullView.setOnFilterStateChanged(state -> {
|
||||
switch (state) {
|
||||
|
@ -291,7 +292,7 @@ public class ConversationListFragment extends MainFragment implements ActionMode
|
|||
viewModel.setFiltered(true);
|
||||
break;
|
||||
case OPEN_APEX:
|
||||
ViewUtil.setMinimumHeight(collapsingToolbarLayout, minHeight);
|
||||
ViewUtil.setMinimumHeight(collapsingToolbarLayout, openHeight);
|
||||
break;
|
||||
case CLOSE_APEX:
|
||||
ViewUtil.setMinimumHeight(collapsingToolbarLayout, 0);
|
||||
|
|
|
@ -7,9 +7,9 @@ import android.content.Context
|
|||
import android.util.AttributeSet
|
||||
import android.widget.FrameLayout
|
||||
import androidx.core.animation.doOnEnd
|
||||
import org.signal.core.util.dp
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.databinding.ConversationListFilterPullViewBinding
|
||||
import org.thoughtcrime.securesms.util.VibrateUtil
|
||||
|
||||
/**
|
||||
* Encapsulates the push / pull latch for enabling and disabling
|
||||
|
@ -51,13 +51,14 @@ class ConversationListFilterPullView @JvmOverloads constructor(
|
|||
setState(FilterPullState.CLOSED)
|
||||
} else if (state == FilterPullState.CLOSED && progress >= 1f) {
|
||||
setState(FilterPullState.OPEN_APEX)
|
||||
vibrate()
|
||||
} else if (state == FilterPullState.OPEN && progress >= 1f) {
|
||||
setState(FilterPullState.CLOSE_APEX)
|
||||
vibrate()
|
||||
}
|
||||
|
||||
// If we are pulling toward the open apex
|
||||
if (state == FilterPullState.OPEN || state == FilterPullState.CLOSE_APEX || state == FilterPullState.CLOSING) {
|
||||
binding.filterText.translationY = EVAL.evaluate(progress, 26.dp, -24.dp.toFloat())
|
||||
if (state == FilterPullState.OPEN || state == FilterPullState.OPEN_APEX || state == FilterPullState.CLOSE_APEX || state == FilterPullState.CLOSING) {
|
||||
binding.filterText.translationY = FilterLerp.getPillLerp(progress)
|
||||
} else {
|
||||
binding.filterText.translationY = 0f
|
||||
}
|
||||
|
@ -130,6 +131,12 @@ class ConversationListFilterPullView @JvmOverloads constructor(
|
|||
onFilterStateChanged?.newState(state)
|
||||
}
|
||||
|
||||
private fun vibrate() {
|
||||
if (VibrateUtil.isHapticFeedbackEnabled(context)) {
|
||||
VibrateUtil.vibrateTick(context)
|
||||
}
|
||||
}
|
||||
|
||||
interface OnFilterStateChanged {
|
||||
fun newState(state: FilterPullState)
|
||||
}
|
||||
|
|
|
@ -175,8 +175,8 @@ class FilterCircleView @JvmOverloads constructor(
|
|||
|
||||
private fun evaluateBottomOffset(progress: Float, state: FilterPullState): Float {
|
||||
return when (state) {
|
||||
FilterPullState.OPEN_APEX, FilterPullState.OPENING, FilterPullState.OPEN, FilterPullState.CLOSE_APEX -> CIRCLE_Y_EVALUATOR.evaluate(progress, (-46).dp, 55.dp)
|
||||
FilterPullState.CLOSED, FilterPullState.CLOSING -> CIRCLE_Y_EVALUATOR.evaluate(progress, 0.dp, 55.dp)
|
||||
FilterPullState.OPEN_APEX, FilterPullState.OPENING, FilterPullState.OPEN, FilterPullState.CLOSE_APEX -> FilterLerp.getOpenCircleBottomPadLerp(progress)
|
||||
FilterPullState.CLOSED, FilterPullState.CLOSING -> FilterLerp.getClosedCircleBottomPadLerp(progress)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,107 @@
|
|||
package org.thoughtcrime.securesms.conversationlist.chatfilter
|
||||
|
||||
import android.animation.FloatEvaluator
|
||||
import org.signal.core.util.dp
|
||||
|
||||
/**
|
||||
* Centralized location for filter view linear interpolations.
|
||||
*/
|
||||
object FilterLerp {
|
||||
|
||||
/**
|
||||
* The minimum height of the filter pull when the filter is open.
|
||||
*/
|
||||
const val FILTER_OPEN_HEIGHT = 52f
|
||||
|
||||
/**
|
||||
* The maximum height of the filter pull. Note that this should match
|
||||
* whatever value is set to in XML.
|
||||
*/
|
||||
private const val FILTER_APEX = 130f
|
||||
|
||||
private val EVAL = FloatEvaluator()
|
||||
|
||||
private val PILL_LERP = getFn(
|
||||
Point(FILTER_OPEN_HEIGHT / FILTER_APEX, 0f),
|
||||
Point(1f, ((FILTER_OPEN_HEIGHT - FILTER_APEX) / 2))
|
||||
)
|
||||
|
||||
private val OPEN_CIRCLE_BOTTOM_PAD_LERP = getFn(
|
||||
Point(FILTER_OPEN_HEIGHT / FILTER_APEX, 8f),
|
||||
Point(1f, FILTER_APEX * 0.55f)
|
||||
)
|
||||
|
||||
private val CLOSED_CIRCLE_BOTTOM_PAD_LERP = getFn(
|
||||
Point(0f, 0f),
|
||||
Point(1f, FILTER_APEX * 0.55f)
|
||||
)
|
||||
|
||||
/**
|
||||
* Get the LERP for the "Filter enabled" pill.
|
||||
*/
|
||||
fun getPillLerp(fraction: Float): Float {
|
||||
return getLerp(fraction, PILL_LERP)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the LERP for the padding below the filter circle when the filter is open
|
||||
*/
|
||||
fun getOpenCircleBottomPadLerp(fraction: Float): Float {
|
||||
return getLerp(fraction, OPEN_CIRCLE_BOTTOM_PAD_LERP)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the LERP for the padding below the filter circle when the filter is closed
|
||||
*/
|
||||
fun getClosedCircleBottomPadLerp(fraction: Float): Float {
|
||||
return getLerp(fraction, CLOSED_CIRCLE_BOTTOM_PAD_LERP)
|
||||
}
|
||||
|
||||
private fun getLerp(fraction: Float, fn: Fn): Float {
|
||||
return EVAL.evaluate(fraction, fn(0f), fn(1f)).dp
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the linear slope between two points using:
|
||||
*
|
||||
* m = (y2 - y1) / (x2 - x1)
|
||||
*/
|
||||
private fun getSlope(
|
||||
a: Point,
|
||||
b: Point
|
||||
): Float = (b.y - a.y) / (b.x - a.x)
|
||||
|
||||
/**
|
||||
* Gets the y-intercept between two points using:
|
||||
*
|
||||
* b = y - mx
|
||||
*/
|
||||
private fun getYIntercept(
|
||||
a: Point,
|
||||
b: Point
|
||||
): Float = a.y - getSlope(a, b) * a.x
|
||||
|
||||
/**
|
||||
* For a given set of points, generates a function in the form
|
||||
*
|
||||
* y = mx + b
|
||||
*/
|
||||
private fun getFn(
|
||||
a: Point,
|
||||
b: Point
|
||||
): Fn = Fn(getSlope(a, b), getYIntercept(a, b))
|
||||
|
||||
/**
|
||||
* 2D cartesian coordinate.
|
||||
*/
|
||||
data class Point(val x: Float, val y: Float)
|
||||
|
||||
/**
|
||||
* LERP function defined as y = mx + b
|
||||
*/
|
||||
data class Fn(val m: Float, val b: Float) {
|
||||
operator fun invoke(x: Float): Float {
|
||||
return m * x + b
|
||||
}
|
||||
}
|
||||
}
|
|
@ -4,6 +4,7 @@ import android.content.Context;
|
|||
import android.os.Build;
|
||||
import android.os.VibrationEffect;
|
||||
import android.os.Vibrator;
|
||||
import android.provider.Settings;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
|
@ -27,4 +28,10 @@ public final class VibrateUtil {
|
|||
vibrator.vibrate(duration);
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isHapticFeedbackEnabled(@NonNull Context context) {
|
||||
int enabled = Settings.System.getInt(context.getContentResolver(),
|
||||
android.provider.Settings.System.HAPTIC_FEEDBACK_ENABLED, 0);
|
||||
return enabled != 0;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -63,7 +63,7 @@
|
|||
<org.thoughtcrime.securesms.conversationlist.chatfilter.ConversationListFilterPullView
|
||||
android:id="@+id/pull_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="100dp"
|
||||
android:layout_height="130dp"
|
||||
android:background="@color/signal_colorBackground"
|
||||
app:layout_scrollInterpolator="@android:anim/linear_interpolator" />
|
||||
|
||||
|
|
Ładowanie…
Reference in New Issue