mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-04-22 18:00:02 +01:00
Implement new Material3 spec.
This commit is contained in:
committed by
Greyson Parrelli
parent
556e480b06
commit
1b471e163d
@@ -1,24 +0,0 @@
|
||||
package org.thoughtcrime.securesms.util
|
||||
|
||||
import com.dd.CircularProgressButton
|
||||
|
||||
object CircularProgressButtonUtil {
|
||||
|
||||
@JvmStatic
|
||||
fun setSpinning(button: CircularProgressButton?) {
|
||||
button?.apply {
|
||||
isClickable = false
|
||||
isIndeterminateProgressMode = true
|
||||
progress = 50
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun cancelSpinning(button: CircularProgressButton?) {
|
||||
button?.apply {
|
||||
progress = 0
|
||||
isIndeterminateProgressMode = false
|
||||
isClickable = true
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package org.thoughtcrime.securesms.util
|
||||
|
||||
import android.app.Activity
|
||||
import android.os.Build
|
||||
import android.view.View
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.util.views.Stub
|
||||
|
||||
/**
|
||||
* Sets the view's isActivated state when the content of the attached recycler can scroll up.
|
||||
* This can be used to appropriately tint toolbar backgrounds. Also can emit the state change
|
||||
* for other purposes.
|
||||
*/
|
||||
class Material3OnScrollHelper(
|
||||
private val views: List<View>,
|
||||
private val viewStubs: List<Stub<out View>> = emptyList(),
|
||||
private val onActiveStateChanged: (Boolean) -> Unit
|
||||
) : RecyclerView.OnScrollListener() {
|
||||
|
||||
constructor(activity: Activity, views: List<View>, viewStubs: List<Stub<out View>>) : this(views, viewStubs, { updateStatusBarColor(activity, it) })
|
||||
|
||||
constructor(activity: Activity, view: View) : this(listOf(view), emptyList(), { updateStatusBarColor(activity, it) })
|
||||
|
||||
private var active: Boolean? = null
|
||||
|
||||
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
|
||||
updateActiveState(recyclerView.canScrollVertically(-1))
|
||||
}
|
||||
|
||||
private fun updateActiveState(isActive: Boolean) {
|
||||
if (active == isActive) {
|
||||
return
|
||||
}
|
||||
|
||||
active = isActive
|
||||
|
||||
views.forEach { it.isActivated = isActive }
|
||||
viewStubs.filter { it.resolved() }.forEach { it.get().isActivated = isActive }
|
||||
|
||||
onActiveStateChanged(isActive)
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun updateStatusBarColor(activity: Activity, isActive: Boolean) {
|
||||
if (Build.VERSION.SDK_INT > 21) {
|
||||
if (isActive) {
|
||||
WindowUtil.setStatusBarColor(activity.window, ContextCompat.getColor(activity, R.color.signal_colorSurface2))
|
||||
} else {
|
||||
WindowUtil.setStatusBarColor(activity.window, ContextCompat.getColor(activity, R.color.signal_colorBackground))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -122,6 +122,15 @@ public final class Projection {
|
||||
return set(x, y, (int) (width * scale), (int) (height * scale), newCorners);
|
||||
}
|
||||
|
||||
public @NonNull Projection insetTop(int boundary) {
|
||||
Corners newCorners = this.corners == null ? null : new Corners(0,
|
||||
0,
|
||||
this.corners.bottomRight,
|
||||
this.corners.bottomLeft);
|
||||
|
||||
return set(x, y + boundary, width, height - boundary, newCorners);
|
||||
}
|
||||
|
||||
public static @NonNull Projection relativeToParent(@NonNull ViewGroup parent, @NonNull View view, @Nullable Corners corners) {
|
||||
Rect viewBounds = new Rect();
|
||||
|
||||
|
||||
@@ -75,8 +75,6 @@ public abstract class SnackbarAsyncTask<Params>
|
||||
|
||||
Snackbar.make(view, snackbarText, snackbarDuration)
|
||||
.setAction(snackbarActionText, this)
|
||||
.setActionTextColor(snackbarActionColor)
|
||||
.setTextColor(Color.WHITE)
|
||||
.show();
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,149 @@
|
||||
package org.thoughtcrime.securesms.util.views
|
||||
|
||||
import android.animation.Animator
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.os.Parcelable
|
||||
import android.util.AttributeSet
|
||||
import android.view.ViewAnimationUtils
|
||||
import android.widget.FrameLayout
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.core.animation.doOnEnd
|
||||
import androidx.core.content.withStyledAttributes
|
||||
import com.google.android.material.button.MaterialButton
|
||||
import com.google.android.material.theme.overlay.MaterialThemeOverlay
|
||||
import org.thoughtcrime.securesms.R
|
||||
import kotlin.math.max
|
||||
|
||||
/**
|
||||
* Drop-In replacement for CircularProgressButton that better supports material design.
|
||||
*/
|
||||
class CircularProgressMaterialButton @JvmOverloads constructor(
|
||||
context: Context,
|
||||
attrs: AttributeSet? = null
|
||||
) : FrameLayout(MaterialThemeOverlay.wrap(context, attrs, 0, 0), attrs, 0) {
|
||||
init {
|
||||
inflate(getContext(), R.layout.circular_progress_material_button, this)
|
||||
}
|
||||
|
||||
private var currentState: State = State.BUTTON
|
||||
private var requestedState: State = State.BUTTON
|
||||
|
||||
private var animator: Animator? = null
|
||||
|
||||
private val materialButton: MaterialButton = findViewById(R.id.button)
|
||||
|
||||
var text: CharSequence?
|
||||
get() = materialButton.text
|
||||
set(value) {
|
||||
materialButton.text = value
|
||||
}
|
||||
|
||||
init {
|
||||
getContext().withStyledAttributes(attrs, R.styleable.CircularProgressMaterialButton) {
|
||||
val label = getString(R.styleable.CircularProgressMaterialButton_circularProgressMaterialButton__label)
|
||||
|
||||
materialButton.text = label
|
||||
}
|
||||
}
|
||||
|
||||
fun setText(@StringRes resId: Int) {
|
||||
materialButton.setText(resId)
|
||||
}
|
||||
|
||||
override fun onSaveInstanceState(): Parcelable {
|
||||
return Bundle().apply {
|
||||
putParcelable(SUPER_STATE, super.onSaveInstanceState())
|
||||
putInt(STATE, if (requestedState != currentState) requestedState.code else currentState.code)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onRestoreInstanceState(state: Parcelable) {
|
||||
val stateBundle = state as Bundle
|
||||
val superState: Parcelable? = stateBundle.getParcelable(SUPER_STATE)
|
||||
super.onRestoreInstanceState(superState)
|
||||
|
||||
currentState = if (materialButton.visibility == INVISIBLE) State.PROGRESS else State.BUTTON
|
||||
requestedState = State.fromCode(stateBundle.getInt(STATE))
|
||||
ensureRequestedState(false)
|
||||
}
|
||||
|
||||
override fun setOnClickListener(onClickListener: OnClickListener?) {
|
||||
materialButton.setOnClickListener(onClickListener)
|
||||
}
|
||||
|
||||
fun setSpinning() {
|
||||
transformTo(State.PROGRESS, true)
|
||||
}
|
||||
|
||||
fun cancelSpinning() {
|
||||
transformTo(State.BUTTON, true)
|
||||
}
|
||||
|
||||
private fun transformTo(state: State, animate: Boolean) {
|
||||
requestedState = state
|
||||
if (animator?.isRunning == true) {
|
||||
return
|
||||
}
|
||||
|
||||
if (!animate || Build.VERSION.SDK_INT < 21) {
|
||||
materialButton.visibility = state.materialButtonVisibility
|
||||
currentState = state
|
||||
return
|
||||
}
|
||||
|
||||
currentState = state
|
||||
if (state == State.BUTTON) {
|
||||
materialButton.visibility = VISIBLE
|
||||
}
|
||||
|
||||
val buttonShrunkRadius = 0f
|
||||
val buttonExpandedRadius = max(measuredWidth, measuredHeight).toFloat()
|
||||
|
||||
animator = ViewAnimationUtils.createCircularReveal(
|
||||
materialButton,
|
||||
materialButton.measuredWidth / 2,
|
||||
materialButton.measuredHeight / 2,
|
||||
if (state == State.BUTTON) buttonShrunkRadius else buttonExpandedRadius,
|
||||
if (state == State.PROGRESS) buttonShrunkRadius else buttonExpandedRadius
|
||||
).apply {
|
||||
duration = ANIMATION_DURATION
|
||||
doOnEnd {
|
||||
materialButton.visibility = state.materialButtonVisibility
|
||||
ensureRequestedState(true)
|
||||
}
|
||||
start()
|
||||
}
|
||||
}
|
||||
|
||||
private fun ensureRequestedState(animate: Boolean) {
|
||||
if (requestedState == currentState || !isAttachedToWindow) {
|
||||
return
|
||||
}
|
||||
|
||||
transformTo(requestedState, animate)
|
||||
}
|
||||
|
||||
enum class State(val code: Int, val materialButtonVisibility: Int) {
|
||||
BUTTON(0, VISIBLE),
|
||||
PROGRESS(1, INVISIBLE);
|
||||
|
||||
companion object {
|
||||
fun fromCode(code: Int): State {
|
||||
return when (code) {
|
||||
0 -> BUTTON
|
||||
1 -> PROGRESS
|
||||
else -> error("Unexpected code $code")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val ANIMATION_DURATION = 300L
|
||||
|
||||
private val SUPER_STATE = "super_state"
|
||||
private val STATE = "state"
|
||||
}
|
||||
}
|
||||
@@ -4,14 +4,24 @@ import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.Dimension;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.Px;
|
||||
import androidx.coordinatorlayout.widget.CoordinatorLayout;
|
||||
|
||||
import com.google.android.material.snackbar.Snackbar;
|
||||
|
||||
import org.signal.core.util.DimensionUnit;
|
||||
|
||||
public class SlideUpWithSnackbarBehavior extends CoordinatorLayout.Behavior<View> {
|
||||
|
||||
@Dimension(unit = Dimension.DP)
|
||||
private static final float PAD_TOP_OF_SNACKBAR_DP = 16f;
|
||||
|
||||
@Px
|
||||
private final float padTopOfSnackbar = DimensionUnit.DP.toPixels(PAD_TOP_OF_SNACKBAR_DP);
|
||||
|
||||
public SlideUpWithSnackbarBehavior(@NonNull Context context, @Nullable AttributeSet attributeSet) {
|
||||
super(context, attributeSet);
|
||||
}
|
||||
@@ -21,7 +31,7 @@ public class SlideUpWithSnackbarBehavior extends CoordinatorLayout.Behavior<View
|
||||
@NonNull View child,
|
||||
@NonNull View dependency)
|
||||
{
|
||||
float translationY = Math.min(0, dependency.getTranslationY() - dependency.getHeight());
|
||||
float translationY = Math.min(0, dependency.getTranslationY() - (dependency.getHeight() + padTopOfSnackbar));
|
||||
child.setTranslationY(translationY);
|
||||
|
||||
return true;
|
||||
|
||||
Reference in New Issue
Block a user