Implement new Material3 spec.

This commit is contained in:
Alex Hart
2022-05-26 17:32:52 -03:00
committed by Greyson Parrelli
parent 556e480b06
commit 1b471e163d
374 changed files with 3219 additions and 3049 deletions

View File

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

View File

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

View File

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

View File

@@ -75,8 +75,6 @@ public abstract class SnackbarAsyncTask<Params>
Snackbar.make(view, snackbarText, snackbarDuration)
.setAction(snackbarActionText, this)
.setActionTextColor(snackbarActionColor)
.setTextColor(Color.WHITE)
.show();
}

View File

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

View File

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