Add shared element transition for camera fab.
This commit is contained in:
parent
33b88796e8
commit
7f2f5a182f
10 changed files with 174 additions and 19 deletions
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -37,6 +37,7 @@ import org.thoughtcrime.securesms.R;
|
||||||
import org.thoughtcrime.securesms.components.registration.PulsingFloatingActionButton;
|
import org.thoughtcrime.securesms.components.registration.PulsingFloatingActionButton;
|
||||||
import org.thoughtcrime.securesms.database.SignalDatabase;
|
import org.thoughtcrime.securesms.database.SignalDatabase;
|
||||||
import org.thoughtcrime.securesms.util.ConversationUtil;
|
import org.thoughtcrime.securesms.util.ConversationUtil;
|
||||||
|
import org.thoughtcrime.securesms.util.FeatureFlags;
|
||||||
import org.thoughtcrime.securesms.util.task.SnackbarAsyncTask;
|
import org.thoughtcrime.securesms.util.task.SnackbarAsyncTask;
|
||||||
import org.thoughtcrime.securesms.util.views.Stub;
|
import org.thoughtcrime.securesms.util.views.Stub;
|
||||||
|
|
||||||
|
@ -70,10 +71,16 @@ public class ConversationListArchiveFragment extends ConversationListFragment im
|
||||||
|
|
||||||
coordinator = view.findViewById(R.id.coordinator);
|
coordinator = view.findViewById(R.id.coordinator);
|
||||||
list = view.findViewById(R.id.list);
|
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));
|
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().setNavigationOnClickListener(v -> NavHostFragment.findNavController(this).popBackStack());
|
||||||
toolbar.get().setTitle(R.string.AndroidManifest_archived_conversations);
|
toolbar.get().setTitle(R.string.AndroidManifest_archived_conversations);
|
||||||
|
|
||||||
|
|
|
@ -141,6 +141,7 @@ import org.thoughtcrime.securesms.util.AppForegroundObserver;
|
||||||
import org.thoughtcrime.securesms.util.AppStartup;
|
import org.thoughtcrime.securesms.util.AppStartup;
|
||||||
import org.thoughtcrime.securesms.util.BottomSheetUtil;
|
import org.thoughtcrime.securesms.util.BottomSheetUtil;
|
||||||
import org.thoughtcrime.securesms.util.ConversationUtil;
|
import org.thoughtcrime.securesms.util.ConversationUtil;
|
||||||
|
import org.thoughtcrime.securesms.util.FeatureFlags;
|
||||||
import org.thoughtcrime.securesms.util.PlayStoreUtil;
|
import org.thoughtcrime.securesms.util.PlayStoreUtil;
|
||||||
import org.thoughtcrime.securesms.util.ServiceUtil;
|
import org.thoughtcrime.securesms.util.ServiceUtil;
|
||||||
import org.thoughtcrime.securesms.util.SignalLocalMetrics;
|
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) {
|
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||||
coordinator = view.findViewById(R.id.coordinator);
|
coordinator = view.findViewById(R.id.coordinator);
|
||||||
list = view.findViewById(R.id.list);
|
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);
|
searchEmptyState = view.findViewById(R.id.search_no_results);
|
||||||
toolbarShadow = view.findViewById(R.id.conversation_list_toolbar_shadow);
|
toolbarShadow = view.findViewById(R.id.conversation_list_toolbar_shadow);
|
||||||
bottomActionBar = view.findViewById(R.id.conversation_list_bottom_action_bar);
|
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));
|
paymentNotificationView = new Stub<>(view.findViewById(R.id.payments_notification));
|
||||||
voiceNotePlayerViewStub = new Stub<>(view.findViewById(R.id.voice_note_player));
|
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 toolbar = getToolbar(view);
|
||||||
toolbar.setVisibility(View.VISIBLE);
|
toolbar.setVisibility(View.VISIBLE);
|
||||||
|
|
||||||
|
|
|
@ -110,7 +110,10 @@ class MainActivityListHostFragment : Fragment(R.layout.main_activity_list_host_f
|
||||||
R.id.action_conversationListFragment_to_storiesLandingFragment,
|
R.id.action_conversationListFragment_to_storiesLandingFragment,
|
||||||
null,
|
null,
|
||||||
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"
|
||||||
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,11 +13,15 @@ import android.view.View
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.activity.OnBackPressedCallback
|
import androidx.activity.OnBackPressedCallback
|
||||||
import androidx.core.app.ActivityOptionsCompat
|
import androidx.core.app.ActivityOptionsCompat
|
||||||
|
import androidx.core.app.SharedElementCallback
|
||||||
import androidx.core.view.ViewCompat
|
import androidx.core.view.ViewCompat
|
||||||
import androidx.fragment.app.viewModels
|
import androidx.fragment.app.viewModels
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
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.BaseTransientBottomBar
|
||||||
import com.google.android.material.snackbar.Snackbar
|
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.R
|
||||||
import org.thoughtcrime.securesms.components.settings.DSLConfiguration
|
import org.thoughtcrime.securesms.components.settings.DSLConfiguration
|
||||||
import org.thoughtcrime.securesms.components.settings.DSLSettingsAdapter
|
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.stories.viewer.StoryViewerActivity
|
||||||
import org.thoughtcrime.securesms.util.LifecycleDisposable
|
import org.thoughtcrime.securesms.util.LifecycleDisposable
|
||||||
import org.thoughtcrime.securesms.util.visible
|
import org.thoughtcrime.securesms.util.visible
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The "landing page" for Stories.
|
* The "landing page" for Stories.
|
||||||
|
@ -46,7 +51,7 @@ import org.thoughtcrime.securesms.util.visible
|
||||||
class StoriesLandingFragment : DSLSettingsFragment(layoutId = R.layout.stories_landing_fragment) {
|
class StoriesLandingFragment : DSLSettingsFragment(layoutId = R.layout.stories_landing_fragment) {
|
||||||
|
|
||||||
private lateinit var emptyNotice: View
|
private lateinit var emptyNotice: View
|
||||||
private lateinit var cameraFab: View
|
private lateinit var cameraFab: FloatingActionButton
|
||||||
|
|
||||||
private val lifecycleDisposable = LifecycleDisposable()
|
private val lifecycleDisposable = LifecycleDisposable()
|
||||||
|
|
||||||
|
@ -86,7 +91,16 @@ class StoriesLandingFragment : DSLSettingsFragment(layoutId = R.layout.stories_l
|
||||||
emptyNotice = requireView().findViewById(R.id.empty_notice)
|
emptyNotice = requireView().findViewById(R.id.empty_notice)
|
||||||
cameraFab = requireView().findViewById(R.id.camera_fab)
|
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 {
|
cameraFab.setOnClickListener {
|
||||||
Permissions.with(this)
|
Permissions.with(this)
|
||||||
|
|
|
@ -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" />
|
|
@ -0,0 +1,2 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<accelerateDecelerateInterpolator xmlns:android="http://schemas.android.com/apk/res/android" />
|
|
@ -110,8 +110,7 @@
|
||||||
app:layout_behavior="org.thoughtcrime.securesms.util.views.SlideUpWithSnackbarBehavior">
|
app:layout_behavior="org.thoughtcrime.securesms.util.views.SlideUpWithSnackbarBehavior">
|
||||||
|
|
||||||
<org.thoughtcrime.securesms.components.registration.PulsingFloatingActionButton
|
<org.thoughtcrime.securesms.components.registration.PulsingFloatingActionButton
|
||||||
android:transitionName="camera_fab"
|
android:id="@+id/camera_fab_new"
|
||||||
android:id="@+id/camera_fab"
|
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="8dp"
|
android:layout_marginTop="8dp"
|
||||||
|
@ -119,21 +118,56 @@
|
||||||
android:layout_marginBottom="20dp"
|
android:layout_marginBottom="20dp"
|
||||||
android:contentDescription="@string/conversation_list_fragment__open_camera_description"
|
android:contentDescription="@string/conversation_list_fragment__open_camera_description"
|
||||||
android:focusable="true"
|
android:focusable="true"
|
||||||
app:tint="@color/signal_icon_tint_secondary"
|
android:theme="@style/Widget.Material3.FloatingActionButton.Secondary"
|
||||||
app:backgroundTint="@color/conversation_list_camera_button_background"
|
android:transitionName="camera_fab"
|
||||||
app:srcCompat="@drawable/ic_camera_solid_white_24" />
|
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
|
<org.thoughtcrime.securesms.components.registration.PulsingFloatingActionButton
|
||||||
android:id="@+id/fab"
|
android:id="@+id/fab_new"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginEnd="16dp"
|
android:layout_marginEnd="16dp"
|
||||||
android:layout_marginBottom="16dp"
|
android:layout_marginBottom="16dp"
|
||||||
android:contentDescription="@string/conversation_list_fragment__fab_content_description"
|
android:contentDescription="@string/conversation_list_fragment__fab_content_description"
|
||||||
android:focusable="true"
|
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:backgroundTint="@color/core_ultramarine"
|
||||||
app:srcCompat="@drawable/ic_compose_solid_24" />
|
app:srcCompat="@drawable/ic_compose_solid_24"
|
||||||
|
app:tint="@color/core_white" />
|
||||||
|
|
||||||
<ViewStub
|
<ViewStub
|
||||||
android:id="@+id/megaphone_container"
|
android:id="@+id/megaphone_container"
|
||||||
|
|
|
@ -24,21 +24,38 @@
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="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
|
<org.thoughtcrime.securesms.components.registration.PulsingFloatingActionButton
|
||||||
android:id="@+id/camera_fab"
|
android:id="@+id/camera_fab"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="8dp"
|
android:layout_marginTop="8dp"
|
||||||
android:layout_marginEnd="16dp"
|
android:layout_marginEnd="16dp"
|
||||||
android:layout_marginBottom="20dp"
|
android:layout_marginBottom="16dp"
|
||||||
android:contentDescription="@string/conversation_list_fragment__open_camera_description"
|
android:contentDescription="@string/conversation_list_fragment__open_camera_description"
|
||||||
android:focusable="true"
|
android:focusable="true"
|
||||||
android:theme="@style/Widget.Material3.FloatingActionButton.Secondary"
|
android:theme="@style/Widget.Material3.FloatingActionButton.Primary"
|
||||||
android:transitionName="camera_fab"
|
android:transitionName="new_convo_fab"
|
||||||
app:backgroundTint="@color/signal_colorPrimaryContainer"
|
app:backgroundTint="@color/signal_colorPrimaryContainer"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintEnd_toEndOf="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" />
|
app:tint="@color/signal_colorOnPrimaryContainer" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
|
|
9
app/src/main/res/transition/change_transform_fabs.xml
Normal file
9
app/src/main/res/transition/change_transform_fabs.xml
Normal 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>
|
Loading…
Add table
Reference in a new issue