Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit e8976188 authored by Fabian Kozynski's avatar Fabian Kozynski Committed by Android (Google) Code Review
Browse files

Merge changes from topic "397989775" into main

* changes:
  [Flexiglass] Replace media with new implementation
  Move things from ContainerViewModel to OverlayViewModel
parents 34463ce1 df4dbc58
Loading
Loading
Loading
Loading
+45 −13
Original line number Diff line number Diff line
@@ -22,12 +22,18 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.boundsInWindow
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.layout.onLayoutRectChanged
import androidx.compose.ui.res.dimensionResource
import com.android.compose.animation.scene.ContentScope
import com.android.systemui.Flags.mediaControlsInCompose
import com.android.systemui.keyguard.ui.viewmodel.MediaCarouselElementViewModel
import com.android.systemui.lifecycle.rememberViewModel
import com.android.systemui.media.controls.ui.composable.MediaCarousel
import com.android.systemui.media.controls.ui.controller.MediaCarouselController
import com.android.systemui.media.controls.ui.view.MediaHost
import com.android.systemui.media.dagger.MediaModule
import com.android.systemui.media.remedia.ui.compose.Media
import com.android.systemui.media.remedia.ui.compose.MediaPresentationStyle
import com.android.systemui.res.R
import javax.inject.Inject
import javax.inject.Named
@@ -37,6 +43,7 @@ class MediaCarouselElement
constructor(
    private val mediaCarouselController: MediaCarouselController,
    @param:Named(MediaModule.KEYGUARD) private val mediaHost: MediaHost,
    private val mediaCarouselElementViewModelFactory: MediaCarouselElementViewModel.Factory,
) {

    @Composable
@@ -52,6 +59,30 @@ constructor(
                dimensionResource(id = R.dimen.notification_side_paddings) +
                    dimensionResource(id = R.dimen.notification_panel_margin_horizontal)
            }
        if (mediaControlsInCompose()) {
            val viewModel =
                rememberViewModel("MediaCarouselElement") {
                    mediaCarouselElementViewModelFactory.create()
                }

            Element(
                key = Media.Elements.mediaCarousel,
                modifier =
                    modifier
                        .fillMaxWidth()
                        .padding(horizontal = horizontalPadding)
                        .onLayoutRectChanged {
                            onBottomChanged?.invoke(it.boundsInWindow.bottom.toFloat())
                        },
            ) {
                Media(
                    viewModelFactory = viewModel.mediaViewModelFactory,
                    presentationStyle = MediaPresentationStyle.Default,
                    behavior = viewModel.mediaUiBehavior,
                    onDismissed = viewModel::onSwipeToDismiss,
                )
            }
        } else {
            MediaCarousel(
                isVisible = true,
                mediaHost = mediaHost,
@@ -67,3 +98,4 @@ constructor(
            )
        }
    }
}
+20 −28
Original line number Diff line number Diff line
@@ -22,7 +22,6 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalResources
import androidx.compose.ui.res.dimensionResource
import com.android.compose.animation.scene.ContentScope
import com.android.compose.animation.scene.ElementKey
@@ -34,13 +33,8 @@ import com.android.systemui.keyguard.ui.composable.blueprint.rememberBurnIn
import com.android.systemui.keyguard.ui.composable.element.SmallClockElement
import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel
import com.android.systemui.lifecycle.rememberViewModel
import com.android.systemui.media.controls.ui.composable.MediaCarousel
import com.android.systemui.media.controls.ui.composable.isLandscape
import com.android.systemui.media.controls.ui.controller.MediaCarouselController
import com.android.systemui.media.controls.ui.view.MediaHost
import com.android.systemui.media.controls.ui.view.MediaHostState.Companion.COLLAPSED
import com.android.systemui.media.controls.ui.view.MediaHostState.Companion.EXPANDED
import com.android.systemui.media.dagger.MediaModule.QUICK_QS_PANEL
import com.android.systemui.media.remedia.ui.compose.Media
import com.android.systemui.media.remedia.ui.compose.MediaPresentationStyle
import com.android.systemui.notifications.ui.viewmodel.NotificationsShadeOverlayActionsViewModel
import com.android.systemui.notifications.ui.viewmodel.NotificationsShadeOverlayContentViewModel
import com.android.systemui.res.R
@@ -54,7 +48,6 @@ import com.android.systemui.shade.ui.composable.isFullWidthShade
import com.android.systemui.statusbar.notification.stack.ui.view.NotificationScrollView
import dagger.Lazy
import javax.inject.Inject
import javax.inject.Named
import kotlinx.coroutines.flow.Flow

@SysUISingleton
@@ -67,8 +60,6 @@ constructor(
    private val stackScrollView: Lazy<NotificationScrollView>,
    private val smallClockElement: SmallClockElement,
    private val keyguardClockViewModel: KeyguardClockViewModel,
    private val mediaCarouselController: MediaCarouselController,
    @Named(QUICK_QS_PANEL) private val mediaHost: Lazy<MediaHost>,
    private val jankMonitor: InteractionJankMonitor,
) : Overlay {
    override val key = Overlays.NotificationsShade
@@ -98,11 +89,6 @@ constructor(
                viewModel.notificationsPlaceholderViewModelFactory.create()
            }

        val usingCollapsedLandscapeMedia =
            LocalResources.current.getBoolean(R.bool.config_quickSettingsMediaLandscapeCollapsed)
        mediaHost.get().expansion =
            if (usingCollapsedLandscapeMedia && isLandscape()) COLLAPSED else EXPANDED

        val isFullWidth = isFullWidthShade()

        OverlayShade(
@@ -139,18 +125,24 @@ constructor(
                    }
                }

                MediaCarousel(
                    isVisible = viewModel.showMedia,
                    mediaHost = mediaHost.get(),
                    carouselController = mediaCarouselController,
                    usingCollapsedLandscapeMedia = usingCollapsedLandscapeMedia,
                if (viewModel.showMedia) {
                    Element(
                        key = Media.Elements.mediaCarousel,
                        modifier =
                            Modifier.padding(
                                top = notificationStackPadding,
                                start = notificationStackPadding,
                                end = notificationStackPadding,
                            ),
                    ) {
                        Media(
                            viewModelFactory = viewModel.mediaViewModelFactory,
                            presentationStyle = MediaPresentationStyle.Default,
                            behavior = viewModel.mediaUiBehavior,
                            onDismissed = viewModel::onMediaSwipeToDismiss,
                        )
                    }
                }

                NotificationScrollingStack(
                    shadeSession = shadeSession,
+14 −38
Original line number Diff line number Diff line
@@ -58,7 +58,6 @@ import com.android.compose.animation.scene.ContentScope
import com.android.compose.animation.scene.UserAction
import com.android.compose.animation.scene.UserActionResult
import com.android.compose.animation.scene.animateContentFloatAsState
import com.android.compose.animation.scene.animateSceneDpAsState
import com.android.compose.animation.scene.content.state.TransitionState
import com.android.compose.modifiers.thenIf
import com.android.compose.windowsizeclass.LocalWindowSizeClass
@@ -71,21 +70,16 @@ import com.android.systemui.compose.modifiers.sysuiResTag
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.lifecycle.ExclusiveActivatable
import com.android.systemui.lifecycle.rememberViewModel
import com.android.systemui.media.controls.ui.composable.MediaCarousel
import com.android.systemui.media.controls.ui.composable.isLandscape
import com.android.systemui.media.controls.ui.controller.MediaCarouselController
import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager
import com.android.systemui.media.controls.ui.view.MediaHost
import com.android.systemui.media.controls.ui.view.MediaHostState.Companion.EXPANDED
import com.android.systemui.media.dagger.MediaModule
import com.android.systemui.media.remedia.ui.compose.Media
import com.android.systemui.media.remedia.ui.compose.MediaPresentationStyle
import com.android.systemui.notifications.ui.composable.HeadsUpNotificationSpace
import com.android.systemui.notifications.ui.composable.NotificationScrollingStack
import com.android.systemui.qs.composefragment.ui.GridAnchor
import com.android.systemui.qs.footer.ui.compose.FooterActionsWithAnimatedVisibility
import com.android.systemui.qs.panels.ui.compose.TileGrid
import com.android.systemui.qs.shared.ui.ElementKeys
import com.android.systemui.qs.ui.composable.QuickSettings.SharedValues.MediaLandscapeTopOffset
import com.android.systemui.qs.ui.composable.QuickSettings.SharedValues.MediaOffset.InQS
import com.android.systemui.qs.ui.viewmodel.QuickSettingsContainerViewModel
import com.android.systemui.qs.ui.viewmodel.QuickSettingsSceneContentViewModel
import com.android.systemui.qs.ui.viewmodel.QuickSettingsUserActionsViewModel
import com.android.systemui.res.R
@@ -100,7 +94,6 @@ import com.android.systemui.statusbar.notification.stack.ui.view.NotificationScr
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel
import dagger.Lazy
import javax.inject.Inject
import javax.inject.Named
import kotlin.math.roundToInt
import kotlinx.coroutines.flow.Flow

@@ -114,8 +107,6 @@ constructor(
    private val notificationsPlaceholderViewModelFactory: NotificationsPlaceholderViewModel.Factory,
    private val actionsViewModelFactory: QuickSettingsUserActionsViewModel.Factory,
    private val contentViewModelFactory: QuickSettingsSceneContentViewModel.Factory,
    private val mediaCarouselController: MediaCarouselController,
    @Named(MediaModule.QS_PANEL) private val mediaHost: MediaHost,
    private val jankMonitor: InteractionJankMonitor,
) : ExclusiveActivatable(), Scene {
    override val key = Scenes.QuickSettings
@@ -145,19 +136,11 @@ constructor(
            viewModel = viewModel,
            headerViewModel = viewModel.qsContainerViewModel.shadeHeaderViewModel,
            notificationsPlaceholderViewModel = notificationsPlaceholderViewModel,
            mediaCarouselController = mediaCarouselController,
            mediaHost = mediaHost,
            modifier = modifier,
            shadeSession = shadeSession,
            jankMonitor = jankMonitor,
        )
    }

    init {
        mediaHost.expansion = EXPANDED
        mediaHost.showsOnlyActiveMedia = false
        mediaHost.init(MediaHierarchyManager.LOCATION_QS)
    }
}

@Composable
@@ -166,8 +149,6 @@ private fun ContentScope.QuickSettingsScene(
    viewModel: QuickSettingsSceneContentViewModel,
    headerViewModel: ShadeHeaderViewModel,
    notificationsPlaceholderViewModel: NotificationsPlaceholderViewModel,
    mediaCarouselController: MediaCarouselController,
    mediaHost: MediaHost,
    modifier: Modifier = Modifier,
    shadeSession: SaveableSession,
    jankMonitor: InteractionJankMonitor,
@@ -233,10 +214,8 @@ private fun ContentScope.QuickSettingsScene(
            WindowInsets.navigationBars.asPaddingValues().calculateBottomPadding()

        // ############# Media ###############
        val isMediaVisible = viewModel.isMediaVisible
        val isMediaVisible = viewModel.qsContainerViewModel.showMedia
        val mediaInRow = isMediaVisible && isLandscape()
        val mediaOffset by
            animateSceneDpAsState(value = InQS, key = MediaLandscapeTopOffset, canOverflow = false)

        // This is the background for the whole scene, as the elements don't necessarily provide
        // a background that extends to the edges.
@@ -301,19 +280,16 @@ private fun ContentScope.QuickSettingsScene(
                            },
                        media =
                            @Composable {
                                MediaCarousel(
                                    isVisible = isMediaVisible,
                                    mediaHost = mediaHost,
                                    modifier =
                                        Modifier.fillMaxWidth()
                                            .padding(
                                                horizontal =
                                                    dimensionResource(
                                                        id = R.dimen.qs_horizontal_margin
                                                    )
                                            ),
                                    carouselController = mediaCarouselController,
                                Element(key = Media.Elements.mediaCarousel, modifier = Modifier) {
                                    Media(
                                        viewModelFactory =
                                            viewModel.qsContainerViewModel.mediaViewModelFactory,
                                        presentationStyle = MediaPresentationStyle.Default,
                                        behavior = QuickSettingsContainerViewModel.mediaUiBehavior,
                                        onDismissed =
                                            viewModel.qsContainerViewModel::onMediaSwipeToDismiss,
                                    )
                                }
                            },
                        mediaInRow = mediaInRow,
                        modifier =
+52 −36
Original line number Diff line number Diff line
@@ -65,8 +65,8 @@ import com.android.systemui.brightness.ui.compose.ContainerColors
import com.android.systemui.compose.modifiers.sysuiResTag
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.lifecycle.rememberViewModel
import com.android.systemui.media.controls.ui.composable.MediaCarousel
import com.android.systemui.media.controls.ui.view.MediaHostState.Companion.COLLAPSED
import com.android.systemui.media.remedia.ui.compose.Media
import com.android.systemui.media.remedia.ui.compose.MediaPresentationStyle
import com.android.systemui.notifications.ui.composable.SnoozeableHeadsUpNotificationSpace
import com.android.systemui.qs.composefragment.ui.GridAnchor
import com.android.systemui.qs.flags.QsDetailedView
@@ -74,6 +74,8 @@ import com.android.systemui.qs.panels.ui.compose.EditMode
import com.android.systemui.qs.panels.ui.compose.TileDetails
import com.android.systemui.qs.panels.ui.compose.TileGrid
import com.android.systemui.qs.panels.ui.compose.toolbar.Toolbar
import com.android.systemui.qs.panels.ui.viewmodel.toolbar.ToolbarViewModel
import com.android.systemui.qs.tiles.dialog.AudioDetailsViewModel
import com.android.systemui.qs.ui.composable.QuickSettingsShade.systemGestureExclusionInShade
import com.android.systemui.qs.ui.viewmodel.QuickSettingsContainerViewModel
import com.android.systemui.qs.ui.viewmodel.QuickSettingsShadeOverlayActionsViewModel
@@ -90,6 +92,7 @@ import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrim
import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimShape
import com.android.systemui.statusbar.notification.stack.ui.view.NotificationScrollView
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel
import com.android.systemui.volume.panel.component.volume.slider.ui.viewmodel.AudioStreamSliderViewModel
import com.android.systemui.volume.panel.component.volume.ui.composable.VolumeSlider
import dagger.Lazy
import javax.inject.Inject
@@ -125,16 +128,12 @@ constructor(
    override fun ContentScope.Content(modifier: Modifier) {
        val coroutineScope = rememberCoroutineScope()
        val contentViewModel =
            rememberViewModel("QuickSettingsShadeOverlayContent") {
                contentViewModelFactory.create()
            rememberViewModel("QuickSettingsShadeOverlayContent", key = coroutineScope) {
                contentViewModelFactory.create(coroutineScope)
            }
        val quickSettingsContainerViewModel =
            rememberViewModel("QuickSettingsShadeOverlayContainer") {
                quickSettingsContainerViewModelFactory.create(
                    supportsBrightnessMirroring = true,
                    expansion = COLLAPSED,
                    volumeSliderCoroutineScope = coroutineScope,
                )
                quickSettingsContainerViewModelFactory.create(supportsBrightnessMirroring = true)
            }
        val hunPlaceholderViewModel =
            rememberViewModel("QuickSettingsShadeOverlayPlaceholder") {
@@ -153,7 +152,7 @@ constructor(
            OverlayShade(
                panelElement = QuickSettingsShade.Elements.Panel,
                alignmentOnWideScreens = Alignment.TopEnd,
                enableTransparency = quickSettingsContainerViewModel.isTransparencyEnabled,
                enableTransparency = contentViewModel.isTransparencyEnabled,
                onScrimClicked = contentViewModel::onScrimClicked,
                onBackgroundPlaced = { bounds, topCornerRadius, bottomCornerRadius ->
                    contentViewModel.onPanelShapeInWindowChanged(
@@ -176,7 +175,10 @@ constructor(
                    }
                },
            ) {
                QuickSettingsContainer(viewModel = quickSettingsContainerViewModel)
                QuickSettingsContainer(
                    contentViewModel = contentViewModel,
                    containerViewModel = quickSettingsContainerViewModel,
                )
            }
            SnoozeableHeadsUpNotificationSpace(
                stackScrollView = notificationStackScrollView.get(),
@@ -196,13 +198,15 @@ private sealed interface ShadeBodyState {
}

@Composable
fun ContentScope.QuickSettingsContainer(
    viewModel: QuickSettingsContainerViewModel,
private fun ContentScope.QuickSettingsContainer(
    contentViewModel: QuickSettingsShadeOverlayContentViewModel,
    containerViewModel: QuickSettingsContainerViewModel,
    modifier: Modifier = Modifier,
) {
    val isEditing by viewModel.editModeViewModel.isEditing.collectAsStateWithLifecycle()
    val isEditing by containerViewModel.editModeViewModel.isEditing.collectAsStateWithLifecycle()
    val tileDetails =
        if (QsDetailedView.isEnabled) viewModel.detailsViewModel.activeTileDetails else null
        if (QsDetailedView.isEnabled) containerViewModel.detailsViewModel.activeTileDetails
        else null

    AnimatedContent(
        modifier = Modifier.sysuiResTag("quick_settings_container"),
@@ -217,19 +221,23 @@ fun ContentScope.QuickSettingsContainer(
        when (state) {
            ShadeBodyState.Editing -> {
                EditMode(
                    viewModel = viewModel.editModeViewModel,
                    viewModel = containerViewModel.editModeViewModel,
                    modifier =
                        modifier.fillMaxWidth().padding(QuickSettingsShade.Dimensions.Padding),
                )
            }

            ShadeBodyState.TileDetails -> {
                TileDetails(modifier = modifier, viewModel.detailsViewModel)
                TileDetails(modifier = modifier, containerViewModel.detailsViewModel)
            }

            ShadeBodyState.Default -> {
                QuickSettingsLayout(
                    viewModel = viewModel,
                    qsContainerViewModel = containerViewModel,
                    toolbarViewModelFactory = contentViewModel.toolbarViewModelFactory,
                    isTransparencyEnabled = contentViewModel.isTransparencyEnabled,
                    volumeSliderViewModel = contentViewModel.volumeSliderViewModel,
                    audioDetailsViewModelFactory = contentViewModel.audioDetailsViewModelFactory,
                    modifier = modifier.sysuiResTag("quick_settings_panel"),
                )
            }
@@ -239,8 +247,12 @@ fun ContentScope.QuickSettingsContainer(

/** Column containing Brightness and QS tiles. */
@Composable
fun ContentScope.QuickSettingsLayout(
    viewModel: QuickSettingsContainerViewModel,
private fun ContentScope.QuickSettingsLayout(
    qsContainerViewModel: QuickSettingsContainerViewModel,
    toolbarViewModelFactory: ToolbarViewModel.Factory,
    isTransparencyEnabled: Boolean,
    volumeSliderViewModel: AudioStreamSliderViewModel?,
    audioDetailsViewModelFactory: AudioDetailsViewModel.Factory,
    modifier: Modifier = Modifier,
) {
    Column(
@@ -254,32 +266,35 @@ fun ContentScope.QuickSettingsLayout(
        if (isFullWidthShade()) {
            VerticalSeparator()
            QuickSettingsOverlayHeader(
                viewModel = viewModel.shadeHeaderViewModel,
                viewModel = qsContainerViewModel.shadeHeaderViewModel,
                modifier = Modifier.element(QuickSettingsShade.Elements.Header),
            )

            VerticalSeparator()
        }

        val toolbarViewModel =
            rememberViewModel("QuickSettingsLayout") { toolbarViewModelFactory.create() }

        Toolbar(
            modifier =
                Modifier.fillMaxWidth().requiredHeight(QuickSettingsShade.Dimensions.ToolbarHeight),
            viewModel = viewModel.toolbarViewModel,
            viewModel = toolbarViewModel,
        )

        // TODO(b/428805936): Double check this padding.
        VerticalSeparator()

        Column(modifier = Modifier.fillMaxWidth().verticalScroll(rememberScrollState())) {
            MediaCarousel(
                isVisible = viewModel.showMedia,
                mediaHost = viewModel.mediaHost,
                carouselController = viewModel.mediaCarouselController,
                usingCollapsedLandscapeMedia = true,
                modifier = Modifier.padding(horizontal = QuickSettingsShade.Dimensions.Padding),
            Media(
                viewModelFactory = qsContainerViewModel.mediaViewModelFactory,
                presentationStyle = MediaPresentationStyle.Compact,
                behavior = QuickSettingsContainerViewModel.mediaUiBehavior,
                onDismissed = qsContainerViewModel::onMediaSwipeToDismiss,
                modifier = Modifier,
            )

            if (viewModel.showMedia) {
            if (qsContainerViewModel.showMedia) {
                VerticalSeparator()
            }

@@ -289,12 +304,11 @@ fun ContentScope.QuickSettingsLayout(
                )
            ) {
                BrightnessSliderContainer(
                    viewModel = viewModel.brightnessSliderViewModel,
                    viewModel = qsContainerViewModel.brightnessSliderViewModel,
                    containerColors =
                        ContainerColors(
                            idleColor = Color.Transparent,
                            mirrorColor =
                                OverlayShade.Colors.panelBackground(viewModel.isTransparencyEnabled),
                            mirrorColor = OverlayShade.Colors.panelBackground(isTransparencyEnabled),
                        ),
                    modifier = Modifier.fillMaxWidth(),
                )
@@ -302,7 +316,6 @@ fun ContentScope.QuickSettingsLayout(

            VerticalSeparator()

            val volumeSliderViewModel = viewModel.volumeSliderViewModel
            if (volumeSliderViewModel != null) {
                val volumeSliderState by volumeSliderViewModel.slider.collectAsStateWithLifecycle()

@@ -337,8 +350,8 @@ fun ContentScope.QuickSettingsLayout(
                                    contentColor = MaterialTheme.colorScheme.onPrimary,
                                ),
                            onClick = {
                                viewModel.detailsViewModel.onVolumeSettingsButtonClicked(
                                    viewModel.audioDetailsViewModelFactory.create()
                                qsContainerViewModel.detailsViewModel.onVolumeSettingsButtonClicked(
                                    audioDetailsViewModelFactory.create()
                                )
                            },
                        ) {
@@ -359,7 +372,10 @@ fun ContentScope.QuickSettingsLayout(
            // TODO(b/428805936): Double check this padding.
            VerticalSeparator(QuickSettingsShade.Dimensions.Padding)

            TileGrid(viewModel = viewModel.tileGridViewModel, modifier = Modifier.fillMaxWidth())
            TileGrid(
                viewModel = qsContainerViewModel.tileGridViewModel,
                modifier = Modifier.fillMaxWidth(),
            )

            // TODO(b/428805936): Double check this padding.
            VerticalSeparator(QuickSettingsShade.Dimensions.Padding * 2)
+2 −2
Original line number Diff line number Diff line
@@ -20,7 +20,7 @@ import androidx.compose.animation.core.tween
import com.android.compose.animation.scene.Edge
import com.android.compose.animation.scene.TransitionBuilder
import com.android.compose.animation.scene.UserActionDistance
import com.android.systemui.media.controls.ui.composable.MediaCarousel
import com.android.systemui.media.remedia.ui.compose.Media
import com.android.systemui.notifications.ui.composable.Notifications
import com.android.systemui.qs.ui.composable.QuickSettings
import com.android.systemui.scene.shared.model.Scenes
@@ -47,7 +47,7 @@ fun TransitionBuilder.toShadeTransition(durationScale: Double = 1.0) {

    val qsTranslation = -ShadeHeader.Dimensions.CollapsedHeightForTransitions * 0.66f
    translate(QuickSettings.Elements.QuickQuickSettings, y = qsTranslation)
    translate(MediaCarousel.Elements.Content, y = qsTranslation)
    translate(Media.Elements.mediaCarousel, y = qsTranslation)
    translate(Notifications.Elements.NotificationScrim, Edge.Top, false)
}

Loading