Merge changes from topics "people-space-benchmark", "people-space-jank-benchmark", "sysui-compose-initializer" into tm-qpr-dev
* changes: Show the Compose implementation of the footer actions when available Introduce ComposeInitializer (1/2) Add a Microbenchmark for the conversations list (2/2) Add a Microbenchmark for the PeopleSpace activity (2/2)
This commit is contained in:
committed by
Android (Google) Code Review
commit
220397f120
@@ -271,6 +271,7 @@ android_library {
|
||||
"LowLightDreamLib",
|
||||
"motion_tool_lib",
|
||||
"androidx.core_core-animation-testing-nodeps",
|
||||
"androidx.compose.ui_ui",
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
@@ -17,13 +17,21 @@
|
||||
|
||||
package com.android.systemui.compose
|
||||
|
||||
import android.content.Context
|
||||
import android.view.View
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import com.android.systemui.people.ui.viewmodel.PeopleViewModel
|
||||
import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
|
||||
|
||||
/** The Compose facade, when Compose is *not* available. */
|
||||
object ComposeFacade : BaseComposeFacade {
|
||||
override fun isComposeAvailable(): Boolean = false
|
||||
|
||||
override fun composeInitializer(): ComposeInitializer {
|
||||
throwComposeUnavailableError()
|
||||
}
|
||||
|
||||
override fun setPeopleSpaceActivityContent(
|
||||
activity: ComponentActivity,
|
||||
viewModel: PeopleViewModel,
|
||||
@@ -32,7 +40,15 @@ object ComposeFacade : BaseComposeFacade {
|
||||
throwComposeUnavailableError()
|
||||
}
|
||||
|
||||
private fun throwComposeUnavailableError() {
|
||||
override fun createFooterActionsView(
|
||||
context: Context,
|
||||
viewModel: FooterActionsViewModel,
|
||||
qsVisibilityLifecycleOwner: LifecycleOwner
|
||||
): View {
|
||||
throwComposeUnavailableError()
|
||||
}
|
||||
|
||||
private fun throwComposeUnavailableError(): Nothing {
|
||||
error(
|
||||
"Compose is not available. Make sure to check isComposeAvailable() before calling any" +
|
||||
" other function on ComposeFacade."
|
||||
|
||||
@@ -16,16 +16,24 @@
|
||||
|
||||
package com.android.systemui.compose
|
||||
|
||||
import android.content.Context
|
||||
import android.view.View
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.compose.ui.platform.ComposeView
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import com.android.compose.theme.PlatformTheme
|
||||
import com.android.systemui.people.ui.compose.PeopleScreen
|
||||
import com.android.systemui.people.ui.viewmodel.PeopleViewModel
|
||||
import com.android.systemui.qs.footer.ui.compose.FooterActions
|
||||
import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
|
||||
|
||||
/** The Compose facade, when Compose is available. */
|
||||
object ComposeFacade : BaseComposeFacade {
|
||||
override fun isComposeAvailable(): Boolean = true
|
||||
|
||||
override fun composeInitializer(): ComposeInitializer = ComposeInitializerImpl
|
||||
|
||||
override fun setPeopleSpaceActivityContent(
|
||||
activity: ComponentActivity,
|
||||
viewModel: PeopleViewModel,
|
||||
@@ -33,4 +41,14 @@ object ComposeFacade : BaseComposeFacade {
|
||||
) {
|
||||
activity.setContent { PlatformTheme { PeopleScreen(viewModel, onResult) } }
|
||||
}
|
||||
|
||||
override fun createFooterActionsView(
|
||||
context: Context,
|
||||
viewModel: FooterActionsViewModel,
|
||||
qsVisibilityLifecycleOwner: LifecycleOwner,
|
||||
): View {
|
||||
return ComposeView(context).apply {
|
||||
setContent { PlatformTheme { FooterActions(viewModel, qsVisibilityLifecycleOwner) } }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* Copyright (C) 2023 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.systemui.compose
|
||||
|
||||
import android.view.View
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.ViewTreeLifecycleOwner
|
||||
import androidx.savedstate.SavedStateRegistry
|
||||
import androidx.savedstate.SavedStateRegistryController
|
||||
import androidx.savedstate.SavedStateRegistryOwner
|
||||
import com.android.compose.animation.ViewTreeSavedStateRegistryOwner
|
||||
import com.android.systemui.lifecycle.ViewLifecycleOwner
|
||||
|
||||
internal object ComposeInitializerImpl : ComposeInitializer {
|
||||
override fun onAttachedToWindow(root: View) {
|
||||
if (ViewTreeLifecycleOwner.get(root) != null) {
|
||||
error("root $root already has a LifecycleOwner")
|
||||
}
|
||||
|
||||
val parent = root.parent
|
||||
if (parent is View && parent.id != android.R.id.content) {
|
||||
error(
|
||||
"ComposeInitializer.onAttachedToWindow(View) must be called on the content child." +
|
||||
"Outside of activities and dialogs, this is usually the top-most View of a " +
|
||||
"window."
|
||||
)
|
||||
}
|
||||
|
||||
// The lifecycle owner, which is STARTED when [root] is visible and RESUMED when [root] is
|
||||
// both visible and focused.
|
||||
val lifecycleOwner = ViewLifecycleOwner(root)
|
||||
|
||||
// We create a trivial implementation of [SavedStateRegistryOwner] that does not do any save
|
||||
// or restore because SystemUI process is always running and top-level windows using this
|
||||
// initializer are created once, when the process is started.
|
||||
val savedStateRegistryOwner =
|
||||
object : SavedStateRegistryOwner {
|
||||
private val savedStateRegistry =
|
||||
SavedStateRegistryController.create(this).apply { performRestore(null) }
|
||||
|
||||
override fun getLifecycle(): Lifecycle = lifecycleOwner.lifecycle
|
||||
|
||||
override fun getSavedStateRegistry(): SavedStateRegistry {
|
||||
return savedStateRegistry.savedStateRegistry
|
||||
}
|
||||
}
|
||||
|
||||
// We must call [ViewLifecycleOwner.onCreate] after creating the [SavedStateRegistryOwner]
|
||||
// because `onCreate` might move the lifecycle state to STARTED which will make
|
||||
// [SavedStateRegistryController.performRestore] throw.
|
||||
lifecycleOwner.onCreate()
|
||||
|
||||
// Set the owners on the root. They will be reused by any ComposeView inside the root
|
||||
// hierarchy.
|
||||
ViewTreeLifecycleOwner.set(root, lifecycleOwner)
|
||||
ViewTreeSavedStateRegistryOwner.set(root, savedStateRegistryOwner)
|
||||
}
|
||||
|
||||
override fun onDetachedFromWindow(root: View) {
|
||||
(ViewTreeLifecycleOwner.get(root) as ViewLifecycleOwner).onDestroy()
|
||||
ViewTreeLifecycleOwner.set(root, null)
|
||||
ViewTreeSavedStateRegistryOwner.set(root, null)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright (C) 2023 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.systemui.compose.modifiers
|
||||
|
||||
import androidx.compose.ui.ExperimentalComposeUiApi
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.testTag
|
||||
import androidx.compose.ui.semantics.semantics
|
||||
import androidx.compose.ui.semantics.testTagsAsResourceId
|
||||
|
||||
/**
|
||||
* Set a test tag on this node so that it is associated with [resId]. This node will then be
|
||||
* accessible by integration tests using `sysuiResSelector(resId)`.
|
||||
*/
|
||||
@OptIn(ExperimentalComposeUiApi::class)
|
||||
fun Modifier.sysuiResTag(resId: String): Modifier {
|
||||
return this.semantics { testTagsAsResourceId = true }.testTag("com.android.systemui:id/$resId")
|
||||
}
|
||||
@@ -51,6 +51,7 @@ import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import com.android.compose.theme.LocalAndroidColorScheme
|
||||
import com.android.systemui.R
|
||||
import com.android.systemui.compose.modifiers.sysuiResTag
|
||||
import com.android.systemui.people.ui.viewmodel.PeopleTileViewModel
|
||||
import com.android.systemui.people.ui.viewmodel.PeopleViewModel
|
||||
|
||||
@@ -110,7 +111,9 @@ private fun PeopleScreenWithConversations(
|
||||
recentTiles: List<PeopleTileViewModel>,
|
||||
onTileClicked: (PeopleTileViewModel) -> Unit,
|
||||
) {
|
||||
Column {
|
||||
Column(
|
||||
Modifier.sysuiResTag("top_level_with_conversations"),
|
||||
) {
|
||||
Column(
|
||||
Modifier.fillMaxWidth().padding(PeopleSpacePadding),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
@@ -132,7 +135,7 @@ private fun PeopleScreenWithConversations(
|
||||
}
|
||||
|
||||
LazyColumn(
|
||||
Modifier.fillMaxWidth(),
|
||||
Modifier.fillMaxWidth().sysuiResTag("scroll_view"),
|
||||
contentPadding =
|
||||
PaddingValues(
|
||||
top = 16.dp,
|
||||
|
||||
@@ -73,6 +73,7 @@ import com.android.systemui.R
|
||||
import com.android.systemui.animation.Expandable
|
||||
import com.android.systemui.common.shared.model.Icon
|
||||
import com.android.systemui.common.ui.compose.Icon
|
||||
import com.android.systemui.compose.modifiers.sysuiResTag
|
||||
import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsButtonViewModel
|
||||
import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsForegroundServicesButtonViewModel
|
||||
import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsSecurityButtonViewModel
|
||||
@@ -180,9 +181,9 @@ fun FooterActions(
|
||||
|
||||
security?.let { SecurityButton(it, Modifier.weight(1f)) }
|
||||
foregroundServices?.let { ForegroundServicesButton(it) }
|
||||
userSwitcher?.let { IconButton(it) }
|
||||
IconButton(viewModel.settings)
|
||||
viewModel.power?.let { IconButton(it) }
|
||||
userSwitcher?.let { IconButton(it, Modifier.sysuiResTag("multi_user_switch")) }
|
||||
IconButton(viewModel.settings, Modifier.sysuiResTag("settings_button_container"))
|
||||
viewModel.power?.let { IconButton(it, Modifier.sysuiResTag("pm_lite")) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,8 +17,12 @@
|
||||
|
||||
package com.android.systemui.compose
|
||||
|
||||
import android.content.Context
|
||||
import android.view.View
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import com.android.systemui.people.ui.viewmodel.PeopleViewModel
|
||||
import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
|
||||
|
||||
/**
|
||||
* A facade to interact with Compose, when it is available.
|
||||
@@ -35,10 +39,22 @@ interface BaseComposeFacade {
|
||||
*/
|
||||
fun isComposeAvailable(): Boolean
|
||||
|
||||
/**
|
||||
* Return the [ComposeInitializer] to make Compose usable in windows outside normal activities.
|
||||
*/
|
||||
fun composeInitializer(): ComposeInitializer
|
||||
|
||||
/** Bind the content of [activity] to [viewModel]. */
|
||||
fun setPeopleSpaceActivityContent(
|
||||
activity: ComponentActivity,
|
||||
viewModel: PeopleViewModel,
|
||||
onResult: (PeopleViewModel.Result) -> Unit,
|
||||
)
|
||||
|
||||
/** Create a [View] to represent [viewModel] on screen. */
|
||||
fun createFooterActionsView(
|
||||
context: Context,
|
||||
viewModel: FooterActionsViewModel,
|
||||
qsVisibilityLifecycleOwner: LifecycleOwner,
|
||||
): View
|
||||
}
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright (C) 2022 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.systemui.compose
|
||||
|
||||
import android.view.View
|
||||
|
||||
/**
|
||||
* An initializer to use Compose outside of an Activity, e.g. inside a window added directly using
|
||||
* [android.view.WindowManager.addView] (like the shade or status bar) or inside a dialog.
|
||||
*
|
||||
* Example:
|
||||
* ```
|
||||
* windowManager.addView(MyWindowRootView(context), /* layoutParams */)
|
||||
*
|
||||
* class MyWindowRootView(context: Context) : FrameLayout(context) {
|
||||
* override fun onAttachedToWindow() {
|
||||
* super.onAttachedToWindow()
|
||||
* ComposeInitializer.onAttachedToWindow(this)
|
||||
* }
|
||||
*
|
||||
* override fun onDetachedFromWindow() {
|
||||
* super.onDetachedFromWindow()
|
||||
* ComposeInitializer.onDetachedFromWindow(this)
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
interface ComposeInitializer {
|
||||
/** Function to be called on your window root view's [View.onAttachedToWindow] function. */
|
||||
fun onAttachedToWindow(root: View)
|
||||
|
||||
/** Function to be called on your window root view's [View.onDetachedFromWindow] function. */
|
||||
fun onDetachedFromWindow(root: View)
|
||||
}
|
||||
@@ -145,7 +145,7 @@ private fun createLifecycleOwnerAndRun(
|
||||
* └───────────────┴───────────────────┴──────────────┴─────────────────┘
|
||||
* ```
|
||||
*/
|
||||
private class ViewLifecycleOwner(
|
||||
class ViewLifecycleOwner(
|
||||
private val view: View,
|
||||
) : LifecycleOwner {
|
||||
|
||||
|
||||
@@ -68,8 +68,10 @@ public class PeopleSpaceActivity extends ComponentActivity {
|
||||
};
|
||||
|
||||
if (ComposeFacade.INSTANCE.isComposeAvailable()) {
|
||||
Log.d(TAG, "Using the Compose implementation of the PeopleSpaceActivity");
|
||||
ComposeFacade.INSTANCE.setPeopleSpaceActivityContent(this, viewModel, onResult);
|
||||
} else {
|
||||
Log.d(TAG, "Using the View implementation of the PeopleSpaceActivity");
|
||||
ViewGroup view = PeopleViewBinder.create(this);
|
||||
PeopleViewBinder.bind(view, viewModel, /* lifecycleOwner= */ this, onResult);
|
||||
setContentView(view);
|
||||
|
||||
@@ -49,6 +49,7 @@ import com.android.systemui.Dumpable;
|
||||
import com.android.systemui.R;
|
||||
import com.android.systemui.animation.Interpolators;
|
||||
import com.android.systemui.animation.ShadeInterpolation;
|
||||
import com.android.systemui.compose.ComposeFacade;
|
||||
import com.android.systemui.dump.DumpManager;
|
||||
import com.android.systemui.flags.FeatureFlags;
|
||||
import com.android.systemui.media.controls.ui.MediaHost;
|
||||
@@ -228,9 +229,7 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca
|
||||
|
||||
mQSFooterActionsViewModel = mFooterActionsViewModelFactory.create(/* lifecycleOwner */
|
||||
this);
|
||||
LinearLayout footerActionsView = view.findViewById(R.id.qs_footer_actions);
|
||||
FooterActionsViewBinder.bind(footerActionsView, mQSFooterActionsViewModel,
|
||||
mListeningAndVisibilityLifecycleOwner);
|
||||
bindFooterActionsView(view);
|
||||
mFooterActionsController.init();
|
||||
|
||||
mQSPanelScrollView = view.findViewById(R.id.expanded_qs_scroll_view);
|
||||
@@ -291,6 +290,33 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca
|
||||
});
|
||||
}
|
||||
|
||||
private void bindFooterActionsView(View root) {
|
||||
LinearLayout footerActionsView = root.findViewById(R.id.qs_footer_actions);
|
||||
|
||||
if (!ComposeFacade.INSTANCE.isComposeAvailable()) {
|
||||
Log.d(TAG, "Binding the View implementation of the QS footer actions");
|
||||
FooterActionsViewBinder.bind(footerActionsView, mQSFooterActionsViewModel,
|
||||
mListeningAndVisibilityLifecycleOwner);
|
||||
return;
|
||||
}
|
||||
|
||||
// Compose is available, so let's use the Compose implementation of the footer actions.
|
||||
Log.d(TAG, "Binding the Compose implementation of the QS footer actions");
|
||||
View composeView = ComposeFacade.INSTANCE.createFooterActionsView(root.getContext(),
|
||||
mQSFooterActionsViewModel, mListeningAndVisibilityLifecycleOwner);
|
||||
|
||||
// The id R.id.qs_footer_actions is used by QSContainerImpl to set the horizontal margin
|
||||
// to all views except for qs_footer_actions, so we set it to the Compose view.
|
||||
composeView.setId(R.id.qs_footer_actions);
|
||||
|
||||
// Replace the View by the Compose provided one.
|
||||
ViewGroup parent = (ViewGroup) footerActionsView.getParent();
|
||||
ViewGroup.LayoutParams layoutParams = footerActionsView.getLayoutParams();
|
||||
int index = parent.indexOfChild(footerActionsView);
|
||||
parent.removeViewAt(index);
|
||||
parent.addView(composeView, index, layoutParams);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setScrollListener(ScrollListener listener) {
|
||||
mScrollListener = listener;
|
||||
|
||||
@@ -58,6 +58,7 @@ import android.widget.FrameLayout;
|
||||
import com.android.internal.view.FloatingActionMode;
|
||||
import com.android.internal.widget.floatingtoolbar.FloatingToolbar;
|
||||
import com.android.systemui.R;
|
||||
import com.android.systemui.compose.ComposeFacade;
|
||||
|
||||
/**
|
||||
* Combined keyguard and notification panel view. Also holding backdrop and scrims.
|
||||
@@ -149,6 +150,18 @@ public class NotificationShadeWindowView extends FrameLayout {
|
||||
protected void onAttachedToWindow() {
|
||||
super.onAttachedToWindow();
|
||||
setWillNotDraw(!DEBUG);
|
||||
|
||||
if (ComposeFacade.INSTANCE.isComposeAvailable()) {
|
||||
ComposeFacade.INSTANCE.composeInitializer().onAttachedToWindow(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDetachedFromWindow() {
|
||||
super.onDetachedFromWindow();
|
||||
if (ComposeFacade.INSTANCE.isComposeAvailable()) {
|
||||
ComposeFacade.INSTANCE.composeInitializer().onDetachedFromWindow(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* Copyright (C) 2022 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.systemui.compose
|
||||
|
||||
import android.content.Context
|
||||
import android.testing.AndroidTestingRunner
|
||||
import android.testing.ViewUtils
|
||||
import android.widget.FrameLayout
|
||||
import androidx.compose.ui.platform.ComposeView
|
||||
import androidx.test.filters.SmallTest
|
||||
import com.android.systemui.SysuiTestCase
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
@SmallTest
|
||||
@RunWith(AndroidTestingRunner::class)
|
||||
class ComposeInitializerTest : SysuiTestCase() {
|
||||
@Test
|
||||
fun testCanAddComposeViewInInitializedWindow() {
|
||||
if (!ComposeFacade.isComposeAvailable()) {
|
||||
return
|
||||
}
|
||||
|
||||
val root = TestWindowRoot(context)
|
||||
try {
|
||||
runOnMainThreadAndWaitForIdleSync { ViewUtils.attachView(root) }
|
||||
assertThat(root.isAttachedToWindow).isTrue()
|
||||
|
||||
runOnMainThreadAndWaitForIdleSync { root.addView(ComposeView(context)) }
|
||||
} finally {
|
||||
runOnMainThreadAndWaitForIdleSync { ViewUtils.detachView(root) }
|
||||
}
|
||||
}
|
||||
|
||||
private fun runOnMainThreadAndWaitForIdleSync(f: () -> Unit) {
|
||||
mContext.mainExecutor.execute(f)
|
||||
waitForIdleSync()
|
||||
}
|
||||
|
||||
class TestWindowRoot(context: Context) : FrameLayout(context) {
|
||||
override fun onAttachedToWindow() {
|
||||
super.onAttachedToWindow()
|
||||
ComposeFacade.composeInitializer().onAttachedToWindow(this)
|
||||
}
|
||||
|
||||
override fun onDetachedFromWindow() {
|
||||
super.onDetachedFromWindow()
|
||||
ComposeFacade.composeInitializer().onDetachedFromWindow(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user