diff --git a/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt b/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt index 994237ebab15..a3d9959a20cc 100644 --- a/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt @@ -13,7 +13,6 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import android.view.ViewTreeObserver import androidx.annotation.StringRes import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AppCompatActivity @@ -113,21 +112,6 @@ class HomeFragment : Fragment(), AccountObserver { } } - private val preDrawListener = object : ViewTreeObserver.OnPreDrawListener { - override fun onPreDraw(): Boolean { - if (view != null) { - viewLifecycleOwner.lifecycleScope.launch { - delay(ANIM_SCROLL_DELAY) - restoreLayoutState() - startPostponedEnterTransition() - }.invokeOnCompletion { - sessionControlComponent.view.viewTreeObserver.removeOnPreDrawListener(this) - } - } - return true - } - } - private var homeMenu: HomeMenu? = null private val sessionManager: SessionManager @@ -201,20 +185,22 @@ class HomeFragment : Fragment(), AccountObserver { activity.themeManager.applyStatusBarTheme(activity) postponeEnterTransition() - sessionControlComponent.view.viewTreeObserver.addOnPreDrawListener(preDrawListener) + TransitionPreDrawListener( + fragment = this, + viewTreeObserver = sessionControlComponent.view.viewTreeObserver, + restoreLayoutState = { + val homeViewModel: HomeScreenViewModel by activityViewModels() + homeViewModel.layoutManagerState?.also { parcelable -> + sessionControlComponent.view.layoutManager?.onRestoreInstanceState(parcelable) + } + homeLayout?.progress = homeViewModel.motionLayoutProgress + homeViewModel.layoutManagerState = null + } + ) return view } - private fun restoreLayoutState() { - val homeViewModel: HomeScreenViewModel by activityViewModels() - homeViewModel.layoutManagerState?.also { parcelable -> - sessionControlComponent.view.layoutManager?.onRestoreInstanceState(parcelable) - } - homeLayout?.progress = homeViewModel.motionLayoutProgress - homeViewModel.layoutManagerState = null - } - @SuppressWarnings("LongMethod") override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) @@ -278,7 +264,6 @@ class HomeFragment : Fragment(), AccountObserver { override fun onDestroyView() { homeMenu = null - sessionControlComponent.view.viewTreeObserver.removeOnPreDrawListener(preDrawListener) super.onDestroyView() } diff --git a/app/src/main/java/org/mozilla/fenix/home/TransitionPreDrawListener.kt b/app/src/main/java/org/mozilla/fenix/home/TransitionPreDrawListener.kt new file mode 100644 index 000000000000..f7abb64f2437 --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/home/TransitionPreDrawListener.kt @@ -0,0 +1,52 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package org.mozilla.fenix.home + +import android.view.ViewTreeObserver +import androidx.fragment.app.Fragment +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.LifecycleObserver +import androidx.lifecycle.OnLifecycleEvent +import androidx.lifecycle.lifecycleScope +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch + +class TransitionPreDrawListener( + private val fragment: Fragment, + private val viewTreeObserver: ViewTreeObserver, + private val restoreLayoutState: () -> Unit +) : ViewTreeObserver.OnPreDrawListener, LifecycleObserver { + + init { + fragment.viewLifecycleOwner.lifecycle.addObserver(this) + } + + @OnLifecycleEvent(Lifecycle.Event.ON_CREATE) + fun onCreateView() { + viewTreeObserver.addOnPreDrawListener(this) + } + + @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY) + fun onDestroyView() { + viewTreeObserver.removeOnPreDrawListener(this) + } + + override fun onPreDraw(): Boolean { + if (fragment.view != null) { + fragment.viewLifecycleOwner.lifecycleScope.launch { + delay(ANIM_SCROLL_DELAY) + restoreLayoutState() + fragment.startPostponedEnterTransition() + }.invokeOnCompletion { + viewTreeObserver.removeOnPreDrawListener(this) + } + } + return true + } + + companion object { + private const val ANIM_SCROLL_DELAY = 100L + } +} diff --git a/app/src/test/java/org/mozilla/fenix/home/TransitionPreDrawListenerTest.kt b/app/src/test/java/org/mozilla/fenix/home/TransitionPreDrawListenerTest.kt new file mode 100644 index 000000000000..6b24a5cafecf --- /dev/null +++ b/app/src/test/java/org/mozilla/fenix/home/TransitionPreDrawListenerTest.kt @@ -0,0 +1,41 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package org.mozilla.fenix.home + +import android.view.ViewTreeObserver +import androidx.fragment.app.Fragment +import io.mockk.mockk +import io.mockk.verify +import org.junit.Before +import org.junit.Test + +class TransitionPreDrawListenerTest { + + private lateinit var fragment: Fragment + private lateinit var viewTreeObserver: ViewTreeObserver + + @Before + fun setup() { + fragment = mockk(relaxed = true) + viewTreeObserver = mockk(relaxed = true) + } + + @Test + fun `adds observer when constructed`() { + val listener = TransitionPreDrawListener(fragment, viewTreeObserver) {} + verify { fragment.viewLifecycleOwner.lifecycle.addObserver(listener) } + } + + @Test + fun `adds listener on create and removes on destroy`() { + val listener = TransitionPreDrawListener(fragment, viewTreeObserver) {} + + listener.onCreateView() + verify { viewTreeObserver.addOnPreDrawListener(listener) } + + listener.onDestroyView() + verify { viewTreeObserver.removeOnPreDrawListener(listener) } + } +}