Add shared element transition for camera fab.

This commit is contained in:
Alex Hart 2022-04-11 13:42:08 -03:00 committed by Greyson Parrelli
parent 33b88796e8
commit 7f2f5a182f
10 changed files with 174 additions and 19 deletions

View file

@ -0,0 +1,53 @@
package org.thoughtcrime.securesms.animation.transitions
import android.animation.Animator
import android.animation.ValueAnimator
import android.content.Context
import android.transition.Transition
import android.transition.TransitionValues
import android.util.AttributeSet
import android.view.ViewGroup
import androidx.annotation.RequiresApi
import com.google.android.material.floatingactionbutton.FloatingActionButton
@RequiresApi(21)
class FabElevationFadeTransform(context: Context, attrs: AttributeSet?) : Transition(context, attrs) {
companion object {
private const val ELEVATION = "CrossfaderTransition.ELEVATION"
}
override fun captureStartValues(transitionValues: TransitionValues) {
if (transitionValues.view is FloatingActionButton) {
transitionValues.values[ELEVATION] = transitionValues.view.elevation
}
}
override fun captureEndValues(transitionValues: TransitionValues) {
if (transitionValues.view is FloatingActionButton) {
transitionValues.values[ELEVATION] = transitionValues.view.elevation
}
}
override fun createAnimator(sceneRoot: ViewGroup?, startValues: TransitionValues?, endValues: TransitionValues?): Animator? {
if (startValues?.view !is FloatingActionButton || endValues?.view !is FloatingActionButton) {
return null
}
val startElevation = startValues.view.elevation
val endElevation = endValues.view.elevation
if (startElevation == endElevation) {
return null
}
return ValueAnimator.ofFloat(
startValues.values[ELEVATION] as Float,
endValues.values[ELEVATION] as Float
).apply {
addUpdateListener {
val elevation = it.animatedValue as Float
endValues.view.elevation = elevation
}
}
}
}

View file

@ -37,6 +37,7 @@ import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.components.registration.PulsingFloatingActionButton;
import org.thoughtcrime.securesms.database.SignalDatabase;
import org.thoughtcrime.securesms.util.ConversationUtil;
import org.thoughtcrime.securesms.util.FeatureFlags;
import org.thoughtcrime.securesms.util.task.SnackbarAsyncTask;
import org.thoughtcrime.securesms.util.views.Stub;
@ -70,10 +71,16 @@ public class ConversationListArchiveFragment extends ConversationListFragment im
coordinator = view.findViewById(R.id.coordinator);
list = view.findViewById(R.id.list);
fab = view.findViewById(R.id.fab);
cameraFab = view.findViewById(R.id.camera_fab);
emptyState = new Stub<>(view.findViewById(R.id.empty_state));
if (FeatureFlags.internalUser()) {
fab = view.findViewById(R.id.fab_new);
cameraFab = view.findViewById(R.id.camera_fab_new);
} else {
fab = view.findViewById(R.id.fab_old);
cameraFab = view.findViewById(R.id.camera_fab_old);
}
toolbar.get().setNavigationOnClickListener(v -> NavHostFragment.findNavController(this).popBackStack());
toolbar.get().setTitle(R.string.AndroidManifest_archived_conversations);

View file

@ -141,6 +141,7 @@ import org.thoughtcrime.securesms.util.AppForegroundObserver;
import org.thoughtcrime.securesms.util.AppStartup;
import org.thoughtcrime.securesms.util.BottomSheetUtil;
import org.thoughtcrime.securesms.util.ConversationUtil;
import org.thoughtcrime.securesms.util.FeatureFlags;
import org.thoughtcrime.securesms.util.PlayStoreUtil;
import org.thoughtcrime.securesms.util.ServiceUtil;
import org.thoughtcrime.securesms.util.SignalLocalMetrics;
@ -247,8 +248,6 @@ public class ConversationListFragment extends MainFragment implements ActionMode
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
coordinator = view.findViewById(R.id.coordinator);
list = view.findViewById(R.id.list);
fab = view.findViewById(R.id.fab);
cameraFab = view.findViewById(R.id.camera_fab);
searchEmptyState = view.findViewById(R.id.search_no_results);
toolbarShadow = view.findViewById(R.id.conversation_list_toolbar_shadow);
bottomActionBar = view.findViewById(R.id.conversation_list_bottom_action_bar);
@ -258,6 +257,17 @@ public class ConversationListFragment extends MainFragment implements ActionMode
paymentNotificationView = new Stub<>(view.findViewById(R.id.payments_notification));
voiceNotePlayerViewStub = new Stub<>(view.findViewById(R.id.voice_note_player));
if (FeatureFlags.internalUser()) {
fab = view.findViewById(R.id.fab_new);
cameraFab = view.findViewById(R.id.camera_fab_new);
fab.setVisibility(View.VISIBLE);
cameraFab.setVisibility(View.VISIBLE);
} else {
fab = view.findViewById(R.id.fab_old);
cameraFab = view.findViewById(R.id.camera_fab_old);
}
Toolbar toolbar = getToolbar(view);
toolbar.setVisibility(View.VISIBLE);

View file

@ -110,7 +110,10 @@ class MainActivityListHostFragment : Fragment(R.layout.main_activity_list_host_f
R.id.action_conversationListFragment_to_storiesLandingFragment,
null,
null,
FragmentNavigatorExtras(requireView().findViewById<View>(R.id.camera_fab) to "camera_fab")
FragmentNavigatorExtras(
requireView().findViewById<View>(R.id.camera_fab_new) to "camera_fab",
requireView().findViewById<View>(R.id.fab_new) to "new_convo_fab"
)
)
}
}

View file

@ -13,11 +13,15 @@ import android.view.View
import android.widget.Toast
import androidx.activity.OnBackPressedCallback
import androidx.core.app.ActivityOptionsCompat
import androidx.core.app.SharedElementCallback
import androidx.core.view.ViewCompat
import androidx.fragment.app.viewModels
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.floatingactionbutton.FloatingActionButton
import com.google.android.material.snackbar.BaseTransientBottomBar
import com.google.android.material.snackbar.Snackbar
import io.reactivex.rxjava3.core.Single
import io.reactivex.rxjava3.kotlin.subscribeBy
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.components.settings.DSLConfiguration
import org.thoughtcrime.securesms.components.settings.DSLSettingsAdapter
@ -39,6 +43,7 @@ import org.thoughtcrime.securesms.stories.tabs.ConversationListTabsViewModel
import org.thoughtcrime.securesms.stories.viewer.StoryViewerActivity
import org.thoughtcrime.securesms.util.LifecycleDisposable
import org.thoughtcrime.securesms.util.visible
import java.util.concurrent.TimeUnit
/**
* The "landing page" for Stories.
@ -46,7 +51,7 @@ import org.thoughtcrime.securesms.util.visible
class StoriesLandingFragment : DSLSettingsFragment(layoutId = R.layout.stories_landing_fragment) {
private lateinit var emptyNotice: View
private lateinit var cameraFab: View
private lateinit var cameraFab: FloatingActionButton
private val lifecycleDisposable = LifecycleDisposable()
@ -86,7 +91,16 @@ class StoriesLandingFragment : DSLSettingsFragment(layoutId = R.layout.stories_l
emptyNotice = requireView().findViewById(R.id.empty_notice)
cameraFab = requireView().findViewById(R.id.camera_fab)
sharedElementEnterTransition = TransitionInflater.from(requireContext()).inflateTransition(R.transition.change_transform)
sharedElementEnterTransition = TransitionInflater.from(requireContext()).inflateTransition(R.transition.change_transform_fabs)
setEnterSharedElementCallback(object : SharedElementCallback() {
override fun onSharedElementStart(sharedElementNames: MutableList<String>?, sharedElements: MutableList<View>?, sharedElementSnapshots: MutableList<View>?) {
if (sharedElementNames?.contains("camera_fab") == true) {
lifecycleDisposable += Single.timer(200, TimeUnit.MILLISECONDS).subscribeBy {
cameraFab.setImageResource(R.drawable.ic_camera_outline_24)
}
}
}
})
cameraFab.setOnClickListener {
Permissions.with(this)

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<pathInterpolator xmlns:android="http://schemas.android.com/apk/res/android"
android:controlX1="0.4"
android:controlX2="0.0"
android:controlY1="0.2"
android:controlY2="1" />

View file

@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8"?>
<accelerateDecelerateInterpolator xmlns:android="http://schemas.android.com/apk/res/android" />

View file

@ -110,8 +110,7 @@
app:layout_behavior="org.thoughtcrime.securesms.util.views.SlideUpWithSnackbarBehavior">
<org.thoughtcrime.securesms.components.registration.PulsingFloatingActionButton
android:transitionName="camera_fab"
android:id="@+id/camera_fab"
android:id="@+id/camera_fab_new"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
@ -119,21 +118,56 @@
android:layout_marginBottom="20dp"
android:contentDescription="@string/conversation_list_fragment__open_camera_description"
android:focusable="true"
app:tint="@color/signal_icon_tint_secondary"
app:backgroundTint="@color/conversation_list_camera_button_background"
app:srcCompat="@drawable/ic_camera_solid_white_24" />
android:theme="@style/Widget.Material3.FloatingActionButton.Secondary"
android:transitionName="camera_fab"
android:visibility="gone"
app:backgroundTint="@color/signal_colorSecondaryContainer"
app:srcCompat="@drawable/ic_camera_outline_24"
app:tint="@color/signal_colorOnSecondaryContainer"
tools:visibility="visible" />
<org.thoughtcrime.securesms.components.registration.PulsingFloatingActionButton
android:id="@+id/fab"
android:id="@+id/fab_new"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:layout_marginBottom="16dp"
android:contentDescription="@string/conversation_list_fragment__fab_content_description"
android:focusable="true"
app:tint="@color/core_white"
android:theme="@style/Widget.Material3.FloatingActionButton.Secondary"
android:visibility="gone"
android:transitionName="new_convo_fab"
app:backgroundTint="@color/signal_colorPrimaryContainer"
app:srcCompat="@drawable/ic_compose_outline_24"
app:tint="@color/signal_colorOnPrimaryContainer"
tools:visibility="visible" />
<org.thoughtcrime.securesms.components.registration.PulsingFloatingActionButton
android:id="@+id/camera_fab_old"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="20dp"
android:contentDescription="@string/conversation_list_fragment__open_camera_description"
android:focusable="true"
android:visibility="gone"
app:backgroundTint="@color/conversation_list_camera_button_background"
app:srcCompat="@drawable/ic_camera_solid_white_24"
app:tint="@color/signal_icon_tint_secondary" />
<org.thoughtcrime.securesms.components.registration.PulsingFloatingActionButton
android:id="@+id/fab_old"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:layout_marginBottom="16dp"
android:contentDescription="@string/conversation_list_fragment__fab_content_description"
android:focusable="true"
android:visibility="gone"
app:backgroundTint="@color/core_ultramarine"
app:srcCompat="@drawable/ic_compose_solid_24" />
app:srcCompat="@drawable/ic_compose_solid_24"
app:tint="@color/core_white" />
<ViewStub
android:id="@+id/megaphone_container"

View file

@ -24,21 +24,38 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<org.thoughtcrime.securesms.components.registration.PulsingFloatingActionButton
android:id="@+id/camera_fab_shared_element_target"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="16dp"
android:focusable="true"
android:theme="@style/Widget.Material3.FloatingActionButton.Secondary"
android:transitionName="camera_fab"
app:elevation="0dp"
app:backgroundTint="@color/signal_colorSecondaryContainer"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:srcCompat="@drawable/ic_camera_outline_24"
app:tint="@color/signal_colorOnSecondaryContainer" />
<org.thoughtcrime.securesms.components.registration.PulsingFloatingActionButton
android:id="@+id/camera_fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="20dp"
android:layout_marginBottom="16dp"
android:contentDescription="@string/conversation_list_fragment__open_camera_description"
android:focusable="true"
android:theme="@style/Widget.Material3.FloatingActionButton.Secondary"
android:transitionName="camera_fab"
android:theme="@style/Widget.Material3.FloatingActionButton.Primary"
android:transitionName="new_convo_fab"
app:backgroundTint="@color/signal_colorPrimaryContainer"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:srcCompat="@drawable/ic_camera_outline_24"
app:srcCompat="@drawable/ic_compose_outline_24"
app:tint="@color/signal_colorOnPrimaryContainer" />
<TextView

View file

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<transitionSet android:duration="200"
android:interpolator="@anim/camera_fab_cubic_easing_interpolator"
xmlns:android="http://schemas.android.com/apk/res/android">
<transition class="org.thoughtcrime.securesms.animation.transitions.FabElevationFadeTransform" />
<changeBounds/>
<changeTransform/>
<changeImageTransform/>
</transitionSet>