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

Commit a74cd1b4 authored by Fabián Kozynski's avatar Fabián Kozynski Committed by Fabian Kozynski
Browse files

Add optional arrows around the PagerDots

The arrows are controlled by a parameter in the composable.

Implemented in QS PaginatedGridLayout to only show when there's a
pointer device connected (e.g. mouse).

Test: manual, RTL
Test: atest PagerDotsScreenshotTest
Fixes: 397156072
Flag: com.android.systemui.paginated_qs_pager_dots_arrows
Change-Id: I11297bc6f23949490c05d692bd8978f2fdef6721
parent 84c6f07c
Loading
Loading
Loading
Loading
+10 −0
Original line number Diff line number Diff line
@@ -2049,3 +2049,13 @@ flag {
      purpose: PURPOSE_BUGFIX
    }
}

flag {
    name: "paginated_qs_pager_dots_arrows"
    namespace: "systemui"
    description: "Show arrows in the pager dots in paginated QS"
    bug: "397156072"
    metadata {
      purpose: PURPOSE_BUGFIX
    }
}
+139 −36
Original line number Diff line number Diff line
@@ -19,18 +19,29 @@ package com.android.systemui.common.ui.compose
import androidx.compose.animation.Animatable
import androidx.compose.animation.core.spring
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement.spacedBy
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.pager.PagerState
import androidx.compose.material3.Icon
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.scale
import androidx.compose.ui.geometry.CornerRadius
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.graphics.drawscope.scale
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.graphics.vector.path
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.pageLeft
import androidx.compose.ui.semantics.pageRight
import androidx.compose.ui.semantics.semantics
@@ -39,19 +50,28 @@ import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp
import com.android.compose.modifiers.width
import com.android.systemui.common.ui.compose.Icons.ChevronRight
import com.android.systemui.common.ui.compose.PagerDotsDefaults.SPRING_STIFFNESS
import com.android.systemui.res.R
import kotlin.math.abs
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch

/** Pager dots to be used with a Pager (displays horizontally, but can be rotated for vertical). */
@Composable
fun PagerDots(
    pagerState: PagerState,
    /** Color for the dot corresponding to the current page. */
    activeColor: Color,
    /** Color for the dots corresponding to the not current pages. */
    nonActiveColor: Color,
    modifier: Modifier = Modifier,
    /** Size of each inactive dot. The active dot width is double this. */
    dotSize: Dp = 6.dp,
    /** Space between each dot. */
    spaceSize: Dp = 4.dp,
    /** Whether to show arrows for moving to previous/next page on click */
    showArrows: Boolean = false,
) {
    if (pagerState.pageCount < 2) return

@@ -88,10 +108,30 @@ fun PagerDots(
            }
        }
    }

    Row(
        modifier = modifier,
        horizontalArrangement = spacedBy(8.dp),
        verticalAlignment = Alignment.CenterVertically,
    ) {
        if (showArrows) {
            val enabled = pagerState.canScrollBackward
            Icon(
                imageVector = ChevronRight,
                contentDescription =
                    stringResource(R.string.pager_dots_previous_content_description),
                tint = activeColor.copy(enabled.alpha),
                modifier =
                    Modifier.size(24.dp).scale(scaleX = -1f, scaleY = 1f).clickable(
                        enabled = enabled
                    ) {
                        coroutineScope.launch {
                            pagerState.animateScrollToPage(pagerState.currentPage - 1)
                        }
                    },
            )
        }
        Canvas(
        modifier
            .width { totalWidth.roundToPx() }
            Modifier.width { totalWidth.roundToPx() }
                .height(dotSize)
                .pagerDotsSemantics(pagerState, coroutineScope)
        ) {
@@ -130,8 +170,26 @@ fun PagerDots(
                }
            }
        }
        if (showArrows) {
            val enabled = pagerState.currentPage != pagerState.pageCount - 1
            Icon(
                imageVector = ChevronRight,
                contentDescription = stringResource(R.string.pager_dots_next_content_description),
                tint = activeColor.copy(alpha = enabled.alpha),
                modifier =
                    Modifier.size(24.dp).clickable(enabled = enabled) {
                        coroutineScope.launch {
                            pagerState.animateScrollToPage(pagerState.currentPage + 1)
                        }
                    },
            )
        }
    }
}

private val Boolean.alpha
    get() = if (this) 1f else 0.38f

private fun Modifier.pagerDotsSemantics(
    pagerState: PagerState,
    coroutineScope: CoroutineScope,
@@ -166,3 +224,48 @@ private fun Modifier.pagerDotsSemantics(
private object PagerDotsDefaults {
    const val SPRING_STIFFNESS = 1600f
}

private object Icons {
    val ChevronRight: ImageVector
        get() {
            if (_ChevronRight != null) {
                return _ChevronRight!!
            }
            _ChevronRight =
                ImageVector.Builder(
                        name = "ChevronRight",
                        defaultWidth = 24.dp,
                        defaultHeight = 24.dp,
                        viewportWidth = 960f,
                        viewportHeight = 960f,
                        autoMirror = true,
                    )
                    .apply {
                        path(fill = SolidColor(Color.Black)) {
                            moveTo(472f, 480f)
                            lineTo(332f, 340f)
                            quadToRelative(-18f, -18f, -18f, -44f)
                            reflectiveQuadToRelative(18f, -44f)
                            quadToRelative(18f, -18f, 44f, -18f)
                            reflectiveQuadToRelative(44f, 18f)
                            lineToRelative(183f, 183f)
                            quadToRelative(9f, 9f, 14f, 21f)
                            reflectiveQuadToRelative(5f, 24f)
                            quadToRelative(0f, 12f, -5f, 24f)
                            reflectiveQuadToRelative(-14f, 21f)
                            lineTo(420f, 708f)
                            quadToRelative(-18f, 18f, -44f, 18f)
                            reflectiveQuadToRelative(-44f, -18f)
                            quadToRelative(-18f, -18f, -18f, -44f)
                            reflectiveQuadToRelative(18f, -44f)
                            lineToRelative(140f, -140f)
                            close()
                        }
                    }
                    .build()

            return _ChevronRight!!
        }

    @Suppress("ObjectPropertyName") private var _ChevronRight: ImageVector? = null
}
+6 −0
Original line number Diff line number Diff line
@@ -4456,4 +4456,10 @@

    <!-- Content of button shown on the bouncer screen to navigate back to keyguard. [CHAR LIMIT=NONE] -->
    <string name="back_button_on_bouncer">Back</string>

    <!-- Content description for arrow in Pager Dots to navigate to previous page [CHAR LIMIT=NONE] -->
    <string name="pager_dots_previous_content_description">Previous</string>

    <!-- Content description for arrow in Pager Dots to navigate to next page [CHAR LIMIT=NONE] -->
    <string name="pager_dots_next_content_description">Next</string>
</resources>
+29 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2025 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.inputdevice.domain.interactor

import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.inputdevice.data.repository.PointerDeviceRepository
import javax.inject.Inject

@SysUISingleton
class PointerDeviceInteractor
@Inject
constructor(private val pointerDeviceRepository: PointerDeviceRepository) {
    val isAnyPointerDeviceConnected
        get() = pointerDeviceRepository.isAnyPointerDeviceConnected
}
+3 −1
Original line number Diff line number Diff line
@@ -30,7 +30,6 @@ import androidx.compose.foundation.pager.rememberPagerState
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.Alignment
@@ -148,6 +147,7 @@ constructor(
            FooterBar(
                buildNumberViewModelFactory = viewModel.buildNumberViewModelFactory,
                pagerState = pagerState,
                showArrowsInPager = viewModel.showArrowsInPagerDots,
                editButtonViewModelFactory = viewModel.editModeButtonViewModelFactory,
                isVisible = {
                    with(layoutState.transitionState) {
@@ -168,6 +168,7 @@ private object Dimensions {
private fun FooterBar(
    buildNumberViewModelFactory: BuildNumberViewModel.Factory,
    pagerState: PagerState,
    showArrowsInPager: Boolean,
    editButtonViewModelFactory: EditModeButtonViewModel.Factory,
    isVisible: () -> Boolean = { true },
) {
@@ -198,6 +199,7 @@ private fun FooterBar(
            activeColor = MaterialTheme.colorScheme.onSurfaceVariant,
            nonActiveColor = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = .5f),
            modifier = Modifier.wrapContentWidth(),
            showArrows = showArrowsInPager,
        )
        Row(Modifier.weight(1f)) {
            Spacer(modifier = Modifier.weight(1f))
Loading