Implement chat filter design feedback.

main
Alex Hart 2023-01-02 12:19:04 -04:00 zatwierdzone przez GitHub
rodzic 495c91ba86
commit 9e8350e8c2
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 4AEE18F83AFDEB23
6 zmienionych plików z 131 dodań i 9 usunięć

Wyświetl plik

@ -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);

Wyświetl plik

@ -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)
}

Wyświetl plik

@ -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)
}
}

Wyświetl plik

@ -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
}
}
}

Wyświetl plik

@ -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;
}
}

Wyświetl plik

@ -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" />