feat: Improve Lawnchair grid specifications (#6583)

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: TheSuperDragon <70206496+superdragonxd@users.noreply.github.com>
Signed-off-by: Pun Butrach <pun.butrach@gmail.com>
This commit is contained in:
authored and GitHub committed 2026-06-28 08:37:50 +00:00
1 parent dd04bbafe7
commit 266c07c808
20 files changed
+601 -110

No files matched your search

@@ -370,7 +370,7 @@ public final class FeatureFlagsImpl implements FeatureFlags {
public boolean enableScalabilityForDesktopExperience() { public boolean enableScalabilityForDesktopExperience() {
return false; return true;
} }
@Override @Override
+8
View File
@@ -765,6 +765,14 @@
<string name="open_in_store_drop_target_label">Open in store</string> <string name="open_in_store_drop_target_label">Open in store</string>
<string name="dock_icons">Dock icons</string> <string name="dock_icons">Dock icons</string>
<string name="state_folded">%1$s (folded)</string>
<string name="state_unfolded">%1$s (unfolded)</string>
<string name="folded_label">Folded</string>
<string name="unfolded_label">Unfolded</string>
<string name="when_folded_label">When folded</string>
<string name="when_unfolded_label">When unfolded</string>
<string name="unfolded_grid_description">Lawnchair pairs two pages together to fill the larger display</string>
<string name="foldable_columns_error">The folded value must be less than or equal to the unfolded value. It will be automatically adjusted if needed.</string>
<string name="hotseat_bottom_space_label">Bottom padding</string> <string name="hotseat_bottom_space_label">Bottom padding</string>
<string name="page_indicator_height">Page indicator height</string> <string name="page_indicator_height">Page indicator height</string>
+1 -1
View File
@@ -151,7 +151,7 @@
launcher:packageName="com.google.android.deskclock" launcher:packageName="com.google.android.deskclock"
launcher:screen="0" launcher:screen="0"
launcher:spanX="2" launcher:spanX="2"
launcher:spanY="3" launcher:spanY="2"
launcher:x="2" launcher:x="2"
launcher:y="1"/> launcher:y="1"/>
@@ -33,25 +33,25 @@
launcher:packageName="com.android.vending" launcher:packageName="com.android.vending"
launcher:screen="0" launcher:screen="0"
launcher:x="0" launcher:x="0"
launcher:y="4"/> launcher:y="5"/>
<favorite <favorite
launcher:className="com.google.android.gm.ConversationListActivityGmail" launcher:className="com.google.android.gm.ConversationListActivityGmail"
launcher:packageName="com.google.android.gm" launcher:packageName="com.google.android.gm"
launcher:screen="0" launcher:screen="0"
launcher:x="1" launcher:x="1"
launcher:y="4"/> launcher:y="5"/>
<favorite <favorite
launcher:className="com.google.android.apps.photos.home.HomeActivity" launcher:className="com.google.android.apps.photos.home.HomeActivity"
launcher:packageName="com.google.android.apps.photos" launcher:packageName="com.google.android.apps.photos"
launcher:screen="0" launcher:screen="0"
launcher:x="2" launcher:x="2"
launcher:y="4"/> launcher:y="5"/>
<favorite <favorite
launcher:className="com.google.android.youtube.app.honeycomb.Shell$HomeActivity" launcher:className="com.google.android.youtube.app.honeycomb.Shell$HomeActivity"
launcher:packageName="com.google.android.youtube" launcher:packageName="com.google.android.youtube"
launcher:screen="0" launcher:screen="0"
launcher:x="3" launcher:x="3"
launcher:y="4"/> launcher:y="5"/>
<appwidget <appwidget
launcher:className="com.google.android.apps.search.assistant.verticals.snapshot.widgets.weather.WeatherWidget_Receiver" launcher:className="com.google.android.apps.search.assistant.verticals.snapshot.widgets.weather.WeatherWidget_Receiver"
launcher:packageName="com.google.android.googlequicksearchbox" launcher:packageName="com.google.android.googlequicksearchbox"
@@ -69,7 +69,7 @@
launcher:packageName="com.google.android.calendar" launcher:packageName="com.google.android.calendar"
launcher:screen="1" launcher:screen="1"
launcher:spanX="4" launcher:spanX="4"
launcher:spanY="2" launcher:spanY="3"
launcher:x="0" launcher:x="0"
launcher:y="3"/> launcher:y="3"/>
</favorites> </favorites>
+5 -5
View File
@@ -51,8 +51,8 @@
</grid-option> </grid-option>
<grid-option <grid-option
launcher:name="4_by_7" launcher:name="4_by_6"
launcher:numRows="7" launcher:numRows="6"
launcher:numColumns="4" launcher:numColumns="4"
launcher:numFolderRows="3" launcher:numFolderRows="3"
launcher:numFolderColumns="3" launcher:numFolderColumns="3"
@@ -130,7 +130,7 @@
launcher:dbFile="launcher_6_by_5.db" launcher:dbFile="launcher_6_by_5.db"
launcher:defaultLayoutId="@xml/default_workspace_6x5" launcher:defaultLayoutId="@xml/default_workspace_6x5"
launcher:inlineQsb="landscape" launcher:inlineQsb="landscape"
launcher:deviceCategory="tablet" > launcher:deviceCategory="tablet|desktop" >
<display-option <display-option
launcher:name="Tablet" launcher:name="Tablet"
@@ -170,7 +170,7 @@
launcher:allAppsSpecsTwoPanelId="@xml/spec_foldable_all_apps_4x5_unfolded" launcher:allAppsSpecsTwoPanelId="@xml/spec_foldable_all_apps_4x5_unfolded"
launcher:allAppsStyle="@style/AllAppsStyle4x5Fold" launcher:allAppsStyle="@style/AllAppsStyle4x5Fold"
launcher:cellStyle="@style/CellStyle4x5Fold" launcher:cellStyle="@style/CellStyle4x5Fold"
launcher:defaultLayoutId="@xml/default_workspace_splitdisplay_4x5" launcher:defaultLayoutId="@xml/default_workspace_splitdisplay_4x6"
launcher:deviceCategory="multi_display" launcher:deviceCategory="multi_display"
launcher:devicePaddingId="@xml/paddings_split_4x5" launcher:devicePaddingId="@xml/paddings_split_4x5"
launcher:folderSpecsId="@xml/spec_handheld_folder_4x5" launcher:folderSpecsId="@xml/spec_handheld_folder_4x5"
@@ -189,7 +189,7 @@
launcher:numFolderColumns="3" launcher:numFolderColumns="3"
launcher:numFolderRows="3" launcher:numFolderRows="3"
launcher:numHotseatIcons="4" launcher:numHotseatIcons="4"
launcher:numRows="5" launcher:numRows="6"
launcher:workspaceCellSpecsId="@xml/spec_handheld_workspace_cell_4x5" launcher:workspaceCellSpecsId="@xml/spec_handheld_workspace_cell_4x5"
launcher:workspaceCellSpecsTwoPanelId="@xml/spec_foldable_workspace_cell_4x5_unfolded" launcher:workspaceCellSpecsTwoPanelId="@xml/spec_foldable_workspace_cell_4x5_unfolded"
launcher:workspaceSpecsId="@xml/spec_handheld_workspace_4x5" launcher:workspaceSpecsId="@xml/spec_handheld_workspace_4x5"
@@ -55,10 +55,16 @@ class DeviceProfileOverrides @Inject constructor(
prefs.hotseatColumns.set(gridInfo.numHotseatColumns) prefs.hotseatColumns.set(gridInfo.numHotseatColumns)
} }
fun getOverrides(defaultGrid: InvariantDeviceProfile.GridOption) = Options( fun getOverrides(
defaultGrid: InvariantDeviceProfile.GridOption,
deviceType: Int,
previewOverrides: PreviewOverrides? = null,
) = Options(
prefs = prefs, prefs = prefs,
prefs2 = preferenceManager2, prefs2 = preferenceManager2,
defaultGrid = defaultGrid, defaultGrid = defaultGrid,
deviceType = deviceType,
previewOverrides = previewOverrides ?: PreviewOverrides(),
) )
fun getTextFactors() = TextFactors(preferenceManager2) fun getTextFactors() = TextFactors(preferenceManager2)
@@ -80,6 +86,11 @@ class DeviceProfileOverrides @Inject constructor(
) )
} }
/** Override for any other value that's not a DBGridInfo, extends this value when needed */
data class PreviewOverrides(
val foldableDatabaseHotseatIcons: Int? = null,
)
data class Options( data class Options(
val numAllAppsColumns: Int, val numAllAppsColumns: Int,
val numFolderRows: Int, val numFolderRows: Int,
@@ -90,11 +101,18 @@ class DeviceProfileOverrides @Inject constructor(
val allAppsIconTextSizeFactor: Float, val allAppsIconTextSizeFactor: Float,
val enableTaskbarOnPhone: Boolean, val enableTaskbarOnPhone: Boolean,
// Foldable overrides (-1 means don't override)
val foldableShownHotseatIcons: Int = -1,
val foldableDatabaseHotseatIcons: Int = -1,
val foldableDatabaseAllAppsColumns: Int = -1,
) { ) {
constructor( constructor(
prefs: PreferenceManager, prefs: PreferenceManager,
prefs2: PreferenceManager2, prefs2: PreferenceManager2,
defaultGrid: InvariantDeviceProfile.GridOption, defaultGrid: InvariantDeviceProfile.GridOption,
deviceType: Int,
previewOverrides: PreviewOverrides,
) : this( ) : this(
numAllAppsColumns = prefs2.drawerColumns.firstCached(gridOption = defaultGrid), numAllAppsColumns = prefs2.drawerColumns.firstCached(gridOption = defaultGrid),
numFolderRows = prefs.folderRows.get(defaultGrid), numFolderRows = prefs.folderRows.get(defaultGrid),
@@ -110,6 +128,26 @@ class DeviceProfileOverrides @Inject constructor(
}, },
enableTaskbarOnPhone = prefs2.enableTaskbarOnPhone.firstCached(), enableTaskbarOnPhone = prefs2.enableTaskbarOnPhone.firstCached(),
foldableShownHotseatIcons = if (deviceType == InvariantDeviceProfile.TYPE_MULTI_DISPLAY) {
val folded = prefs.hotseatColumns.get()
val unfolded = previewOverrides.foldableDatabaseHotseatIcons ?: prefs.hotseatColumnsUnfolded.get()
folded.coerceAtMost(unfolded)
} else {
-1
},
foldableDatabaseHotseatIcons = if (deviceType == InvariantDeviceProfile.TYPE_MULTI_DISPLAY) {
previewOverrides.foldableDatabaseHotseatIcons ?: prefs.hotseatColumnsUnfolded.get()
} else {
-1
},
foldableDatabaseAllAppsColumns = if (deviceType == InvariantDeviceProfile.TYPE_MULTI_DISPLAY) {
val folded = prefs2.drawerColumns.firstCached(gridOption = defaultGrid)
val unfolded = prefs2.drawerColumnsUnfolded.firstCached(gridOption = defaultGrid)
folded.coerceAtLeast(unfolded)
} else {
-1
},
) )
fun applyUi(idp: InvariantDeviceProfile) { fun applyUi(idp: InvariantDeviceProfile) {
@@ -119,6 +157,17 @@ class DeviceProfileOverrides @Inject constructor(
idp.numFolderRows[INDEX_DEFAULT] = numFolderRows idp.numFolderRows[INDEX_DEFAULT] = numFolderRows
idp.numFolderColumns[INDEX_DEFAULT] = numFolderColumns idp.numFolderColumns[INDEX_DEFAULT] = numFolderColumns
// Foldable overrides for hotseat and allapps columns
if (foldableShownHotseatIcons > 0) {
idp.numShownHotseatIcons = foldableShownHotseatIcons
}
if (foldableDatabaseHotseatIcons > 0) {
idp.numDatabaseHotseatIcons = foldableDatabaseHotseatIcons
}
if (foldableDatabaseAllAppsColumns > 0) {
idp.numDatabaseAllAppsColumns = foldableDatabaseAllAppsColumns
}
// apply icon and text size // apply icon and text size
idp.iconSize[INDEX_DEFAULT] *= iconSizeFactor idp.iconSize[INDEX_DEFAULT] *= iconSizeFactor
idp.iconSize[INDEX_LANDSCAPE] *= iconSizeFactor idp.iconSize[INDEX_LANDSCAPE] *= iconSizeFactor
@@ -34,6 +34,7 @@ import com.android.launcher3.graphics.ThemeManager
import com.android.launcher3.model.DeviceGridState import com.android.launcher3.model.DeviceGridState
import com.android.launcher3.util.ComponentKey import com.android.launcher3.util.ComponentKey
import com.android.launcher3.util.DaggerSingletonObject import com.android.launcher3.util.DaggerSingletonObject
import com.android.launcher3.util.DisplayController
import com.android.launcher3.util.Executors import com.android.launcher3.util.Executors
import com.android.launcher3.util.SafeCloseable import com.android.launcher3.util.SafeCloseable
import com.android.quickstep.RecentsModel import com.android.quickstep.RecentsModel
@@ -45,6 +46,7 @@ class PreferenceManager @Inject constructor(
) : BasePreferenceManager(context), ) : BasePreferenceManager(context),
SafeCloseable { SafeCloseable {
private val idp get() = InvariantDeviceProfile.INSTANCE.get(context) private val idp get() = InvariantDeviceProfile.INSTANCE.get(context)
private val dc get() = DisplayController.INSTANCE.get(context)
private val mRecentsModel get() = RecentsModel.INSTANCE.get(context) private val mRecentsModel get() = RecentsModel.INSTANCE.get(context)
private val reloadIcons: () -> Unit = { private val reloadIcons: () -> Unit = {
mRecentsModel.onThemeChanged() mRecentsModel.onThemeChanged()
@@ -55,6 +57,8 @@ class PreferenceManager @Inject constructor(
} }
private val reloadGrid: () -> Unit = { idp.onPreferencesChanged(context) } private val reloadGrid: () -> Unit = { idp.onPreferencesChanged(context) }
private val deviceType = dc.info.deviceType
private val recreate = { private val recreate = {
LawnchairLauncher.instance?.recreateIfNotScheduled() LawnchairLauncher.instance?.recreateIfNotScheduled()
Unit Unit
@@ -67,9 +71,37 @@ class PreferenceManager @Inject constructor(
val transparentIconBackground = BoolPref("prefs_transparentIconBackground", false) val transparentIconBackground = BoolPref("prefs_transparentIconBackground", false)
val shadowBGIcons = BoolPref("pref_shadowBGIcons", true) val shadowBGIcons = BoolPref("pref_shadowBGIcons", true)
val addIconToHome = BoolPref("pref_add_icon_to_home", true) val addIconToHome = BoolPref("pref_add_icon_to_home", true)
val hotseatColumns = IntPref("pref_hotseatColumns", 4, reloadGrid)
val workspaceColumns = IntPref("pref_workspaceColumns", 4) private val isPhone: Boolean get() = deviceType == InvariantDeviceProfile.TYPE_PHONE
val workspaceRows = IntPref("pref_workspaceRows", 7) private val isTablet: Boolean get() = deviceType == InvariantDeviceProfile.TYPE_TABLET
private val isFoldable: Boolean get() = deviceType == InvariantDeviceProfile.TYPE_MULTI_DISPLAY
private val isDesktop: Boolean get() = deviceType == InvariantDeviceProfile.TYPE_DESKTOP
val calculatedGridSpec = when {
// This grid configuration is perfect for Phone, tested against Pixel 7,
// alternative dense configuration can be 5x5x7
isPhone -> LayoutConfig(4, 4, 6)
// This grid configuration is perfect for Tablet, tested against Pixel Tablet
isTablet -> LayoutConfig(6, 6, 5)
// This grid configuration is perfect for Foldable, tested against Pixel 10 Pro Fold
// Note: Hotseat column is 4 when folded, unfolded uses hotseatColumns + 2 or higher number
// defined in numExtendedHotseatIcons from device profile
isFoldable -> LayoutConfig(4, 4, 6, 6)
// This grid configuration is not tested against actual desktop devices,
// but tablet configuration works perfectly when displayed via emulator
isDesktop -> LayoutConfig(6, 6, 5)
// This grid configuration is the fallback for all devices type, this shouldn't be possible
else -> LayoutConfig(4, 4, 7)
}
val hotseatColumns = IntPref("pref_hotseatColumns", calculatedGridSpec.hotseatColumns, reloadGrid)
val hotseatColumnsUnfolded = IntPref("pref_hotseatColumnsUnfolded", calculatedGridSpec.hotseatColumnsUnfolded, reloadGrid)
val workspaceColumns = IntPref("pref_workspaceColumns", calculatedGridSpec.workspaceColumns)
val workspaceRows = IntPref("pref_workspaceRows", calculatedGridSpec.workspaceRows)
val workspaceIncreaseMaxGridSize = BoolPref("pref_workspace_increase_max_grid_size", false) val workspaceIncreaseMaxGridSize = BoolPref("pref_workspace_increase_max_grid_size", false)
val folderRows = IdpIntPref("pref_folderRows", { numFolderRows[INDEX_DEFAULT] }, reloadGrid) val folderRows = IdpIntPref("pref_folderRows", { numFolderRows[INDEX_DEFAULT] }, reloadGrid)
@@ -192,3 +224,35 @@ class PreferenceManager @Inject constructor(
@Composable @Composable
fun preferenceManager() = PreferenceManager.getInstance(LocalContext.current) fun preferenceManager() = PreferenceManager.getInstance(LocalContext.current)
/**
* Grid layout configuration for a device's workspace.
*
* @param hotseatColumns The amount of column the dock can contain
* @param workspaceColumns The amount of column the home screen can contain
* @param workspaceRows The amount of row the home screen can contain
* @param hotseatColumnsUnfolded The amount of column the dock can contain when unfolded (foldables only)
*/
data class LayoutConfig(
/**
* Hotseat columns refer to the amount of column the dock can contain.
* For foldables, this is the folded (closed) state.
*/
val hotseatColumns: Int,
/**
* Workspace columns refer to the amount of column the home screen can contain.
*/
val workspaceColumns: Int,
/**
* Workspace rows refer to the amount of row the home screen can contain.
*/
val workspaceRows: Int,
/**
* Hotseat columns when the foldable is in unfolded (opened) state.
* For non-foldable devices, this must be equals to [hotseatColumns].
*/
val hotseatColumnsUnfolded: Int = hotseatColumns,
)
@@ -680,6 +680,12 @@ class PreferenceManager2 @Inject constructor(
onSet = { reloadHelper.reloadGrid() }, onSet = { reloadHelper.reloadGrid() },
) )
val drawerColumnsUnfolded = idpPreference(
key = intPreferencesKey(name = "drawer_columns_unfolded"),
defaultSelector = { numAllAppsColumns + 2 },
onSet = { reloadHelper.reloadGrid() },
)
val folderColumns = idpPreference( val folderColumns = idpPreference(
key = intPreferencesKey(name = "folder_columns"), key = intPreferencesKey(name = "folder_columns"),
defaultSelector = { numFolderColumns[INDEX_DEFAULT] }, defaultSelector = { numFolderColumns[INDEX_DEFAULT] },
@@ -145,10 +145,16 @@ class LawnQsbLayout(context: Context, attrs: AttributeSet?) : FrameLayout(contex
} }
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
val dp = activity.deviceProfile
// Unlike Phone, for Foldable/Tablet we let the original onMeasure do that instead since it
// matched what we need. It perfectly fit the QSB with the grid.
if (!dp.deviceProperties.isPhone) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
return
}
val requestedWidth = MeasureSpec.getSize(widthMeasureSpec) val requestedWidth = MeasureSpec.getSize(widthMeasureSpec)
val height = MeasureSpec.getSize(heightMeasureSpec) val height = MeasureSpec.getSize(heightMeasureSpec)
val dp = activity.deviceProfile
val cellWidth = DeviceProfile.calculateCellWidth( val cellWidth = DeviceProfile.calculateCellWidth(
requestedWidth, requestedWidth,
dp.cellLayoutBorderSpacePx.x, dp.cellLayoutBorderSpacePx.x,
@@ -1,7 +1,11 @@
package app.lawnchair.ui.preferences.components package app.lawnchair.ui.preferences.components
import android.content.res.Configuration
import androidx.compose.foundation.layout.BoxWithConstraints
import androidx.compose.foundation.layout.ColumnScope import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.requiredSize
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.derivedStateOf
@@ -10,7 +14,9 @@ import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import app.lawnchair.DeviceProfileOverrides import app.lawnchair.DeviceProfileOverrides
import app.lawnchair.preferences.preferenceManager import app.lawnchair.preferences.preferenceManager
import com.android.launcher3.InvariantDeviceProfile import com.android.launcher3.InvariantDeviceProfile
@@ -18,36 +24,88 @@ import com.android.launcher3.InvariantDeviceProfile
@Composable @Composable
fun ColumnScope.GridOverridesPreview( fun ColumnScope.GridOverridesPreview(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
expandToAvailableSpace: Boolean = true,
previewOverrides: DeviceProfileOverrides.PreviewOverrides = DeviceProfileOverrides.PreviewOverrides(),
updateGridOptions: DeviceProfileOverrides.DBGridInfo.() -> DeviceProfileOverrides.DBGridInfo, updateGridOptions: DeviceProfileOverrides.DBGridInfo.() -> DeviceProfileOverrides.DBGridInfo,
) { ) {
val previewIdp = createPreviewIdp(updateGridOptions, previewOverrides)
val isPortrait = LocalConfiguration.current.orientation == Configuration.ORIENTATION_PORTRAIT
val previewAspectRatio = remember(previewIdp, isPortrait) {
val matchingProfile = previewIdp.supportedProfiles.firstOrNull { profile ->
profile.deviceProperties.isLandscape != isPortrait
} ?: previewIdp.supportedProfiles.firstOrNull { profile ->
!profile.deviceProperties.isMultiDisplay
} ?: previewIdp.supportedProfiles.firstOrNull()
val width = matchingProfile?.deviceProperties?.widthPx?.toFloat()
val height = matchingProfile?.deviceProperties?.heightPx?.toFloat()
if (width == null || height == null || height <= 0f) {
1f
} else {
width / height
}
}
WithWallpaper { wallpaper -> WithWallpaper { wallpaper ->
DummyLauncherBox( BoxWithConstraints(
modifier = modifier modifier = modifier
.weight(1f) .then(if (expandToAvailableSpace) Modifier.weight(1f) else Modifier)
.fillMaxWidth()
.align(Alignment.CenterHorizontally) .align(Alignment.CenterHorizontally)
.clip(MaterialTheme.shapes.large), .clip(MaterialTheme.shapes.large),
contentAlignment = Alignment.Center,
) { ) {
WallpaperPreview( val fitByWidth = maxHeight == 0.dp || maxWidth == 0.dp || (maxWidth / maxHeight) <= previewAspectRatio
wallpaper = wallpaper, val previewModifier = if (fitByWidth) {
modifier = Modifier.fillMaxSize(), Modifier.requiredSize(
) width = maxWidth,
DummyLauncherLayout( height = maxWidth / previewAspectRatio,
idp = createPreviewIdp(updateGridOptions), )
modifier = Modifier.fillMaxSize(), } else {
) Modifier.requiredSize(
width = maxHeight * previewAspectRatio,
height = maxHeight,
)
}
DummyLauncherBox(
modifier = previewModifier.clip(MaterialTheme.shapes.large),
aspectRatio = previewAspectRatio,
) {
WallpaperPreview(
wallpaper = wallpaper,
modifier = Modifier.fillMaxSize(),
)
DummyLauncherLayout(
idp = previewIdp,
modifier = Modifier.fillMaxSize(),
)
}
} }
} }
} }
@Composable @Composable
fun createPreviewIdp(updateGridOptions: DeviceProfileOverrides.DBGridInfo.() -> DeviceProfileOverrides.DBGridInfo): InvariantDeviceProfile { fun createPreviewIdp(updateGridOptions: DeviceProfileOverrides.DBGridInfo.() -> DeviceProfileOverrides.DBGridInfo): InvariantDeviceProfile {
return createPreviewIdp(
updateGridOptions = updateGridOptions,
previewOverrides = DeviceProfileOverrides.PreviewOverrides(),
)
}
@Composable
fun createPreviewIdp(
updateGridOptions: DeviceProfileOverrides.DBGridInfo.() -> DeviceProfileOverrides.DBGridInfo,
previewOverrides: DeviceProfileOverrides.PreviewOverrides = DeviceProfileOverrides.PreviewOverrides(),
): InvariantDeviceProfile {
val context = LocalContext.current val context = LocalContext.current
val prefs = preferenceManager() val prefs = preferenceManager()
val newIdp by remember { val newIdp by remember(previewOverrides) {
derivedStateOf { derivedStateOf {
val options = DeviceProfileOverrides.DBGridInfo(prefs) val options = DeviceProfileOverrides.DBGridInfo(prefs)
InvariantDeviceProfile(context, updateGridOptions(options)) InvariantDeviceProfile(context, updateGridOptions(options), previewOverrides)
} }
} }
return newIdp return newIdp
@@ -29,12 +29,13 @@ import com.android.launcher3.LauncherAppState
fun DummyLauncherBox( fun DummyLauncherBox(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
darkText: Boolean = wallpaperSupportsDarkText(), darkText: Boolean = wallpaperSupportsDarkText(),
aspectRatio: Float? = null,
content: @Composable BoxScope.() -> Unit, content: @Composable BoxScope.() -> Unit,
) { ) {
val context = LocalContext.current val context = LocalContext.current
val idp = remember { InvariantDeviceProfile.INSTANCE.get(context) } val idp = remember { InvariantDeviceProfile.INSTANCE.get(context) }
val dp = idp.getDeviceProfile(context) val dp = idp.getDeviceProfile(context)
val ratio = dp.deviceProperties.widthPx.toFloat() / dp.deviceProperties.heightPx.toFloat() val ratio = aspectRatio ?: (dp.deviceProperties.widthPx.toFloat() / dp.deviceProperties.heightPx.toFloat())
Box(modifier = modifier.aspectRatio(ratio, matchHeightConstraintsFirst = true)) { Box(modifier = modifier.aspectRatio(ratio, matchHeightConstraintsFirst = true)) {
LawnchairTheme(darkTheme = !darkText) { LawnchairTheme(darkTheme = !darkText) {
@@ -59,6 +59,7 @@ fun SliderPreference(
step: Int, step: Int,
showAsPercentage: Boolean = false, showAsPercentage: Boolean = false,
showUnit: String = "", showUnit: String = "",
enabled: Boolean = true,
) { ) {
val transformedAdapter = rememberTransformAdapter( val transformedAdapter = rememberTransformAdapter(
adapter = adapter, adapter = adapter,
@@ -74,6 +75,7 @@ fun SliderPreference(
step = step.toFloat(), step = step.toFloat(),
showAsPercentage = showAsPercentage, showAsPercentage = showAsPercentage,
showUnit = showUnit, showUnit = showUnit,
enabled = enabled,
) )
} }
@@ -86,6 +88,7 @@ fun SliderPreference(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
showAsPercentage: Boolean = false, showAsPercentage: Boolean = false,
showUnit: String = "", showUnit: String = "",
enabled: Boolean = true,
) { ) {
var adapterValue by adapter var adapterValue by adapter
@@ -100,6 +103,7 @@ fun SliderPreference(
modifier = modifier, modifier = modifier,
showAsPercentage = showAsPercentage, showAsPercentage = showAsPercentage,
showUnit = showUnit, showUnit = showUnit,
enabled = enabled,
) )
} }
@@ -113,6 +117,7 @@ private fun SliderPreference(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
showAsPercentage: Boolean = false, showAsPercentage: Boolean = false,
showUnit: String = "", showUnit: String = "",
enabled: Boolean = true,
) { ) {
var sliderValue by remember { mutableFloatStateOf(value) } var sliderValue by remember { mutableFloatStateOf(value) }
val mMSDLPlayerWrapper = MSDLPlayerWrapper.INSTANCE.get(LocalContext.current) val mMSDLPlayerWrapper = MSDLPlayerWrapper.INSTANCE.get(LocalContext.current)
@@ -176,6 +181,7 @@ private fun SliderPreference(
.padding(top = 2.dp, bottom = 12.dp) .padding(top = 2.dp, bottom = 12.dp)
.padding(horizontal = 14.dp) .padding(horizontal = 14.dp)
.height(24.dp), .height(24.dp),
enabled = enabled,
) )
}, },
modifier = modifier, modifier = modifier,
@@ -47,10 +47,12 @@ import app.lawnchair.ui.preferences.components.colorpreference.ColorPreference
import app.lawnchair.ui.preferences.components.controls.SliderPreference import app.lawnchair.ui.preferences.components.controls.SliderPreference
import app.lawnchair.ui.preferences.components.controls.SwitchPreference import app.lawnchair.ui.preferences.components.controls.SwitchPreference
import app.lawnchair.ui.preferences.components.controls.SwitchPreferenceWithPreview import app.lawnchair.ui.preferences.components.controls.SwitchPreferenceWithPreview
import app.lawnchair.ui.preferences.components.controls.WarningPreference
import app.lawnchair.ui.preferences.components.layout.ExpandAndShrink import app.lawnchair.ui.preferences.components.layout.ExpandAndShrink
import app.lawnchair.ui.preferences.components.layout.PreferenceGroup import app.lawnchair.ui.preferences.components.layout.PreferenceGroup
import app.lawnchair.ui.preferences.components.layout.PreferenceLayout import app.lawnchair.ui.preferences.components.layout.PreferenceLayout
import app.lawnchair.ui.preferences.navigation.AppDrawerHiddenApps import app.lawnchair.ui.preferences.navigation.AppDrawerHiddenApps
import com.android.launcher3.InvariantDeviceProfile
import com.android.launcher3.R import com.android.launcher3.R
object AppDrawerRoutes { object AppDrawerRoutes {
@@ -65,6 +67,7 @@ fun AppDrawerPreferences(
val prefs2 = preferenceManager2() val prefs2 = preferenceManager2()
val context = LocalContext.current val context = LocalContext.current
val resources = context.resources val resources = context.resources
val isFoldable = InvariantDeviceProfile.deviceType == InvariantDeviceProfile.TYPE_MULTI_DISPLAY
PreferenceLayout( PreferenceLayout(
label = stringResource(id = R.string.app_drawer_label), label = stringResource(id = R.string.app_drawer_label),
@@ -117,13 +120,41 @@ fun AppDrawerPreferences(
} }
} }
PreferenceGroup(heading = stringResource(id = R.string.grid)) { PreferenceGroup(heading = stringResource(id = R.string.grid)) {
Item { val drawerColumnsAdapter = prefs2.drawerColumns.getAdapter()
SliderPreference( val drawerColumnsUnfoldedAdapter = prefs2.drawerColumnsUnfolded.getAdapter()
label = stringResource(id = R.string.app_drawer_columns), if (isFoldable) {
adapter = prefs2.drawerColumns.getAdapter(), Item {
step = 1, SliderPreference(
valueRange = 3..10, label = stringResource(id = R.string.state_folded, stringResource(id = R.string.app_drawer_columns)),
) adapter = drawerColumnsAdapter,
step = 1,
valueRange = 3..10,
)
}
Item {
SliderPreference(
label = stringResource(id = R.string.state_unfolded, stringResource(id = R.string.app_drawer_columns)),
adapter = drawerColumnsUnfoldedAdapter,
step = 1,
valueRange = 3..10,
)
}
Item(
visible = drawerColumnsAdapter.state.value > drawerColumnsUnfoldedAdapter.state.value,
) {
WarningPreference(
text = stringResource(id = R.string.foldable_columns_error),
)
}
} else {
Item {
SliderPreference(
label = stringResource(id = R.string.app_drawer_columns),
adapter = drawerColumnsAdapter,
step = 1,
valueRange = 3..10,
)
}
} }
Item { Item {
SliderPreference( SliderPreference(
@@ -44,11 +44,13 @@ import app.lawnchair.ui.preferences.components.colorpreference.ColorPreference
import app.lawnchair.ui.preferences.components.controls.MainSwitchPreference import app.lawnchair.ui.preferences.components.controls.MainSwitchPreference
import app.lawnchair.ui.preferences.components.controls.SliderPreference import app.lawnchair.ui.preferences.components.controls.SliderPreference
import app.lawnchair.ui.preferences.components.controls.SwitchPreference import app.lawnchair.ui.preferences.components.controls.SwitchPreference
import app.lawnchair.ui.preferences.components.controls.WarningPreference
import app.lawnchair.ui.preferences.components.createPreviewIdp import app.lawnchair.ui.preferences.components.createPreviewIdp
import app.lawnchair.ui.preferences.components.layout.DividerColumn import app.lawnchair.ui.preferences.components.layout.DividerColumn
import app.lawnchair.ui.preferences.components.layout.PreferenceGroup import app.lawnchair.ui.preferences.components.layout.PreferenceGroup
import app.lawnchair.ui.preferences.components.layout.PreferenceGroupHeading import app.lawnchair.ui.preferences.components.layout.PreferenceGroupHeading
import app.lawnchair.ui.preferences.components.layout.PreferenceLayout import app.lawnchair.ui.preferences.components.layout.PreferenceLayout
import com.android.launcher3.InvariantDeviceProfile
import com.android.launcher3.R import com.android.launcher3.R
@Composable @Composable
@@ -137,14 +139,44 @@ fun HotseatBackgroundSettings(prefs: PreferenceManager, prefs2: PreferenceManage
@Composable @Composable
fun GridSettings(prefs: PreferenceManager, prefs2: PreferenceManager2) { fun GridSettings(prefs: PreferenceManager, prefs2: PreferenceManager2) {
val isFoldable = InvariantDeviceProfile.deviceType == InvariantDeviceProfile.TYPE_MULTI_DISPLAY
val hotseatColumnsAdapter = prefs.hotseatColumns.getAdapter()
val hotseatColumnsUnfoldedAdapter = prefs.hotseatColumnsUnfolded.getAdapter()
PreferenceGroup(heading = stringResource(id = R.string.grid)) { PreferenceGroup(heading = stringResource(id = R.string.grid)) {
Item { if (isFoldable) {
SliderPreference( Item {
label = stringResource(id = R.string.dock_icons), SliderPreference(
adapter = prefs.hotseatColumns.getAdapter(), label = stringResource(id = R.string.state_folded, stringResource(id = R.string.dock_icons)),
step = 1, adapter = hotseatColumnsAdapter,
valueRange = 3..10, step = 1,
) valueRange = 3..10,
)
}
Item {
SliderPreference(
label = stringResource(id = R.string.state_unfolded, stringResource(id = R.string.dock_icons)),
adapter = hotseatColumnsUnfoldedAdapter,
step = 1,
valueRange = 3..10,
)
}
Item(
visible = hotseatColumnsAdapter.state.value > hotseatColumnsUnfoldedAdapter.state.value,
) {
WarningPreference(
text = stringResource(id = R.string.foldable_columns_error),
)
}
} else {
Item {
SliderPreference(
label = stringResource(id = R.string.dock_icons),
adapter = hotseatColumnsAdapter,
step = 1,
valueRange = 3..10,
)
}
} }
Item { Item {
SliderPreference( SliderPreference(
@@ -176,6 +208,7 @@ fun ColumnScope.DockPreferencesPreview(modifier: Modifier = Modifier) {
val adapters = listOf( val adapters = listOf(
prefs2.hotseatMode.getAdapter(), prefs2.hotseatMode.getAdapter(),
prefs.hotseatColumns.getAdapter(), prefs.hotseatColumns.getAdapter(),
prefs.hotseatColumnsUnfolded.getAdapter(),
prefs2.themedHotseatQsb.getAdapter(), prefs2.themedHotseatQsb.getAdapter(),
prefs.hotseatQsbCornerRadius.getAdapter(), prefs.hotseatQsbCornerRadius.getAdapter(),
prefs.hotseatQsbAlpha.getAdapter(), prefs.hotseatQsbAlpha.getAdapter(),
@@ -1,15 +1,28 @@
package app.lawnchair.ui.preferences.destinations package app.lawnchair.ui.preferences.destinations
import android.content.res.Configuration import android.content.res.Configuration
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxWithConstraints
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.FlowRow
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Button import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.LocalTextStyle
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.saveable.rememberSaveable
@@ -19,15 +32,18 @@ import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import app.lawnchair.DeviceProfileOverrides
import app.lawnchair.preferences.asPreferenceAdapter import app.lawnchair.preferences.asPreferenceAdapter
import app.lawnchair.preferences.getAdapter import app.lawnchair.preferences.getAdapter
import app.lawnchair.preferences.preferenceManager import app.lawnchair.preferences.preferenceManager
import app.lawnchair.ui.preferences.LocalIsExpandedScreen
import app.lawnchair.ui.preferences.LocalNavController import app.lawnchair.ui.preferences.LocalNavController
import app.lawnchair.ui.preferences.components.GridOverridesPreview import app.lawnchair.ui.preferences.components.GridOverridesPreview
import app.lawnchair.ui.preferences.components.controls.SliderPreference import app.lawnchair.ui.preferences.components.controls.SliderPreference
import app.lawnchair.ui.preferences.components.layout.PreferenceGroup import app.lawnchair.ui.preferences.components.layout.PreferenceGroup
import app.lawnchair.ui.preferences.components.layout.PreferenceLayout import app.lawnchair.ui.preferences.components.layout.PreferenceLayout
import com.android.launcher3.LauncherAppState import app.lawnchair.ui.preferences.components.layout.PreferenceTemplate
import com.android.launcher3.InvariantDeviceProfile
import com.android.launcher3.R import com.android.launcher3.R
@OptIn(ExperimentalMaterial3ExpressiveApi::class) @OptIn(ExperimentalMaterial3ExpressiveApi::class)
@@ -35,78 +51,263 @@ import com.android.launcher3.R
fun HomeScreenGridPreferences( fun HomeScreenGridPreferences(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
) { ) {
val isExpandedScreen = LocalIsExpandedScreen.current
val isPortrait = LocalConfiguration.current.orientation == Configuration.ORIENTATION_PORTRAIT val isPortrait = LocalConfiguration.current.orientation == Configuration.ORIENTATION_PORTRAIT
val scrollState = rememberScrollState()
PreferenceLayout( PreferenceLayout(
label = stringResource(id = R.string.home_screen_grid), label = stringResource(id = R.string.home_screen_grid),
modifier = modifier, modifier = modifier,
isExpandedScreen = true, isExpandedScreen = true,
scrollState = if (isPortrait) null else scrollState, scrollState = null,
) { ) {
val controlsScrollState = rememberScrollState()
val prefs = preferenceManager() val prefs = preferenceManager()
val columnsAdapter = prefs.workspaceColumns.getAdapter() val columnsAdapter = prefs.workspaceColumns.getAdapter()
val rowsAdapter = prefs.workspaceRows.getAdapter() val rowsAdapter = prefs.workspaceRows.getAdapter()
val hotseatColumnsAdapter = prefs.hotseatColumns.getAdapter()
val hotseatColumnsUnfoldedAdapter = prefs.hotseatColumnsUnfolded.getAdapter()
val increaseMaxGridSize = prefs.workspaceIncreaseMaxGridSize.getAdapter() val increaseMaxGridSize = prefs.workspaceIncreaseMaxGridSize.getAdapter()
val isFoldable = InvariantDeviceProfile.deviceType == InvariantDeviceProfile.TYPE_MULTI_DISPLAY
val originalColumns = remember { columnsAdapter.state.value } val originalColumns = remember { columnsAdapter.state.value }
val originalRows = remember { rowsAdapter.state.value } val originalRows = remember { rowsAdapter.state.value }
val originalHotseatColumns = remember { hotseatColumnsAdapter.state.value }
val originalHotseatColumnsUnfolded = remember { hotseatColumnsUnfoldedAdapter.state.value }
val columns = rememberSaveable { mutableIntStateOf(originalColumns) } val columns = rememberSaveable { mutableIntStateOf(originalColumns) }
val rows = rememberSaveable { mutableIntStateOf(originalRows) } val rows = rememberSaveable { mutableIntStateOf(originalRows) }
val hotseatColumns = rememberSaveable { mutableIntStateOf(originalHotseatColumns) }
val hotseatColumnsUnfolded = rememberSaveable {
mutableIntStateOf(originalHotseatColumnsUnfolded.coerceAtLeast(originalHotseatColumns))
}
if (isPortrait) { LaunchedEffect(hotseatColumns.intValue) {
GridOverridesPreview { if (hotseatColumnsUnfolded.intValue < hotseatColumns.intValue) {
copy(numColumns = columns.intValue, numRows = rows.intValue) hotseatColumnsUnfolded.intValue = hotseatColumns.intValue
} }
} }
val maxGridSize = if (increaseMaxGridSize.state.value) 20 else 10 val maxGridSize = if (increaseMaxGridSize.state.value) 20 else 10
PreferenceGroup { BoxWithConstraints(
Item {
SliderPreference(
label = stringResource(id = R.string.columns),
adapter = columns.asPreferenceAdapter(),
step = 1,
valueRange = 3..maxGridSize,
)
}
Item {
SliderPreference(
label = stringResource(id = R.string.rows),
adapter = rows.asPreferenceAdapter(),
step = 1,
valueRange = 3..maxGridSize,
)
}
}
val navController = LocalNavController.current
val context = LocalContext.current
val applyOverrides = {
prefs.batchEdit {
columnsAdapter.onChange(columns.intValue)
rowsAdapter.onChange(rows.intValue)
}
LauncherAppState.getIDP(context).onPreferencesChanged(context)
navController.popBackStack()
}
Box(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.padding(top = 8.dp) .weight(1f),
.padding(horizontal = 16.dp),
) { ) {
Button( // Don't stress over the coerce value,
onClick = { applyOverrides() }, // we eyeballing it till there's a much better solutions(tm) or permanent workarounds.
modifier = Modifier val settingsMinHeight = when {
.align(Alignment.CenterEnd) // This should allow user to see the unfolded label,
.fillMaxWidth(), // which will be their indicator that they can scroll the settings entries.
enabled = columns.intValue != originalColumns || rows.intValue != originalRows, isFoldable -> (maxHeight * 0.58f).coerceAtLeast(360.dp)
shapes = ButtonDefaults.shapes(),
// This should be enough for 3 preferences, they can't scroll beyond this.
// Should you introduce another prefs, raise the value by a little so that they
// have an indication that you can scroll the entries.
isPortrait -> (maxHeight * 0.40f).coerceAtLeast(315.dp)
// Landscape mode
else -> (maxHeight * 0.52f).coerceAtLeast(280.dp)
}
val previewMaxHeight = (maxHeight - settingsMinHeight)
.coerceAtLeast(if (isPortrait) 180.dp else 140.dp)
Column(
modifier = Modifier.fillMaxHeight(),
) { ) {
Text(text = stringResource(id = R.string.action_apply)) GridOverridesPreview(
modifier = Modifier
.fillMaxWidth()
.heightIn(max = previewMaxHeight)
.padding(horizontal = 16.dp, vertical = if (isPortrait) 16.dp else 12.dp),
expandToAvailableSpace = false,
updateGridOptions = {
copy(
numColumns = columns.intValue,
numRows = rows.intValue,
numHotseatColumns = hotseatColumns.intValue,
)
},
previewOverrides = if (isFoldable) {
DeviceProfileOverrides.PreviewOverrides(
foldableDatabaseHotseatIcons = hotseatColumnsUnfolded.intValue,
)
} else {
DeviceProfileOverrides.PreviewOverrides()
},
)
Column(
modifier = Modifier
.weight(1f)
.heightIn(min = settingsMinHeight),
) {
Column(
modifier = Modifier
.weight(1f)
.verticalScroll(controlsScrollState),
) {
if (isFoldable) {
FlowRow(
horizontalArrangement = Arrangement.spacedBy(8.dp),
maxItemsInEachRow = if (isExpandedScreen) Int.MAX_VALUE else 1,
) {
PreferenceGroup(heading = stringResource(id = R.string.when_folded_label)) {
Item {
SliderPreference(
label = stringResource(id = R.string.columns),
adapter = columns.asPreferenceAdapter(),
step = 1,
valueRange = 3..maxGridSize,
)
}
Item {
SliderPreference(
label = stringResource(id = R.string.rows),
adapter = rows.asPreferenceAdapter(),
step = 1,
valueRange = 3..maxGridSize,
)
}
Item {
SliderPreference(
label = stringResource(id = R.string.dock_icons),
adapter = hotseatColumns.asPreferenceAdapter(),
step = 1,
valueRange = 3..maxGridSize,
)
}
}
PreferenceGroup(
heading = stringResource(id = R.string.when_unfolded_label),
) {
Item {
SliderPreference(
label = stringResource(id = R.string.dock_icons),
adapter = hotseatColumnsUnfolded.asPreferenceAdapter(),
step = 1,
valueRange = hotseatColumns.intValue..maxGridSize,
)
}
Item {
FakeExpandedGridPreference(
columns = columns.intValue * 2,
rows = rows.intValue,
description = stringResource(id = R.string.unfolded_grid_description),
)
}
}
}
} else {
PreferenceGroup {
Item {
SliderPreference(
label = stringResource(id = R.string.columns),
adapter = columns.asPreferenceAdapter(),
step = 1,
valueRange = 3..maxGridSize,
)
}
Item {
SliderPreference(
label = stringResource(id = R.string.rows),
adapter = rows.asPreferenceAdapter(),
step = 1,
valueRange = 3..maxGridSize,
)
}
Item {
SliderPreference(
label = stringResource(id = R.string.dock_icons),
adapter = hotseatColumns.asPreferenceAdapter(),
step = 1,
valueRange = 3..maxGridSize,
)
}
}
}
}
val navController = LocalNavController.current
val context = LocalContext.current
val applyOverrides = {
prefs.batchEdit {
columnsAdapter.onChange(columns.intValue)
rowsAdapter.onChange(rows.intValue)
hotseatColumnsAdapter.onChange(hotseatColumns.intValue)
hotseatColumnsUnfoldedAdapter.onChange(hotseatColumnsUnfolded.intValue)
}
InvariantDeviceProfile.INSTANCE.get(context).onPreferencesChanged(context)
navController.popBackStack()
}
val isChanged = columns.intValue != originalColumns ||
rows.intValue != originalRows ||
hotseatColumns.intValue != originalHotseatColumns ||
hotseatColumnsUnfolded.intValue != originalHotseatColumnsUnfolded
Box(
modifier = Modifier
.fillMaxWidth()
.padding(top = 8.dp)
.padding(horizontal = 16.dp),
) {
Button(
onClick = { applyOverrides() },
modifier = Modifier
.align(Alignment.CenterEnd)
.fillMaxWidth(),
enabled = isChanged,
shapes = ButtonDefaults.shapes(),
) {
Text(text = stringResource(id = R.string.action_apply))
}
}
}
} }
} }
} }
} }
@Composable
private fun FakeExpandedGridPreference(
columns: Int,
rows: Int,
description: String,
modifier: Modifier = Modifier,
) {
PreferenceTemplate(
title = {
Row(
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.fillMaxWidth()
.padding(top = 16.dp)
.padding(horizontal = 16.dp),
) {
Text(
text = stringResource(id = R.string.grid),
modifier = Modifier
.weight(1f)
.padding(end = 8.dp),
)
CompositionLocalProvider(
LocalContentColor provides MaterialTheme.colorScheme.onSurface,
LocalTextStyle provides MaterialTheme.typography.bodyLarge,
) {
Text(
text = stringResource(id = R.string.x_by_y, columns, rows),
color = MaterialTheme.colorScheme.onSurfaceVariant,
)
}
}
},
description = {
Text(description)
},
modifier = modifier,
applyPaddings = false,
enabled = false,
)
}
@@ -517,7 +517,9 @@ public class QuickstepLauncher extends Launcher implements RecentsViewContainer,
List<SystemShortcut.Factory> shortcuts = new ArrayList(Arrays.asList( List<SystemShortcut.Factory> shortcuts = new ArrayList(Arrays.asList(
APP_INFO, WellbeingModel.SHORTCUT_FACTORY, mHotseatPredictionController)); APP_INFO, WellbeingModel.SHORTCUT_FACTORY, mHotseatPredictionController));
shortcuts.addAll(getSplitShortcuts()); if (LawnchairApp.isRecentsEnabled()) {
shortcuts.addAll(getSplitShortcuts());
}
shortcuts.add(WIDGETS); shortcuts.add(WIDGETS);
shortcuts.add(INSTALL); shortcuts.add(INSTALL);
if (Flags.enableLongPressRemoveShortcut() if (Flags.enableLongPressRemoveShortcut()
+2 -2
View File
@@ -174,7 +174,7 @@
launcher:devicePaddingId="@xml/paddings_6x5" launcher:devicePaddingId="@xml/paddings_6x5"
launcher:dbFile="launcher_6_by_5.db" launcher:dbFile="launcher_6_by_5.db"
launcher:defaultLayoutId="@xml/default_workspace_6x5" launcher:defaultLayoutId="@xml/default_workspace_6x5"
launcher:deviceCategory="tablet" > launcher:deviceCategory="tablet|desktop" >
<display-option <display-option
launcher:name="Tablet" launcher:name="Tablet"
@@ -248,4 +248,4 @@
</grid-option> </grid-option>
</profiles> </profiles>
+16 -7
View File
@@ -897,8 +897,12 @@ public class DeviceProfile {
+ space; + space;
} else if (isQsbInline) { } else if (isQsbInline) {
hotseatBarSizePx = Math.max(hotseatIconSizePx, getHotseatProfile().getQsbVisualHeight()) hotseatBarSizePx = Math.max(hotseatIconSizePx, getHotseatProfile().getQsbVisualHeight())
+ hotseatBarBottomSpacePx + hotseatBarBottomSpacePx;
+ space; } else if (isQsbOnTop()) { // LC-Note: isQsbOnTop, this usually is a foldable device, not a tablet
hotseatBarSizePx = hotseatIconSizePx
+ hotseatQsbSpace
+ getHotseatProfile().getQsbVisualHeight()
+ hotseatBarBottomSpacePx;
} else { } else {
hotseatBarSizePx = hotseatIconSizePx hotseatBarSizePx = hotseatIconSizePx
+ hotseatQsbSpace + hotseatQsbSpace
@@ -1417,7 +1421,7 @@ public class DeviceProfile {
} }
var allAppLeftRightMarginMultiplier = PreferenceCacheExtensionsKt var allAppLeftRightMarginMultiplier = PreferenceCacheExtensionsKt
.firstCached(preferenceManager2.getDrawerLeftRightMarginFactor()); .firstCached(preferenceManager2.getDrawerLeftRightMarginFactor());
var marginMultiplier = allAppLeftRightMarginMultiplier * (!getDeviceProperties().isTablet() ? 100 : 2); var marginMultiplier = allAppLeftRightMarginMultiplier * (!getDeviceProperties().isTablet() ? 100 : 10);
allAppsLeftRightMargin = (int) (allAppsLeftRightMargin * marginMultiplier); allAppsLeftRightMargin = (int) (allAppsLeftRightMargin * marginMultiplier);
// todo fix how drawer padding values are calculated in responsive grid type // todo fix how drawer padding values are calculated in responsive grid type
@@ -1881,8 +1885,8 @@ public class DeviceProfile {
} else { } else {
hotseatBarPadding.left += qsbWidth; hotseatBarPadding.left += qsbWidth;
} }
} else if (isTaskbarPresent || isQsbInline) { } else if (isQsbOnTop() || isQsbInline) {
// Center the QSB vertically with hotseat // Keep the hotseat icons in the lower section so the QSB can sit above them.
int hotseatBarBottomPadding = getHotseatBarBottomPadding(); int hotseatBarBottomPadding = getHotseatBarBottomPadding();
int hotseatBarTopPadding = int hotseatBarTopPadding =
hotseatBarSizePx - hotseatBarBottomPadding - hotseatCellHeightPx; hotseatBarSizePx - hotseatBarBottomPadding - hotseatCellHeightPx;
@@ -1986,6 +1990,11 @@ public class DeviceProfile {
+ additionalQsbSpace; + additionalQsbSpace;
} }
/** LC-Note: For ID'ing tablet/foldable mode */
private boolean isQsbOnTop() {
return isTaskbarPresent || mDeviceProperties.isTwoPanels();
}
/** /**
* Returns the number of pixels the QSB is translated from the bottom of the screen. * Returns the number of pixels the QSB is translated from the bottom of the screen.
*/ */
@@ -1993,7 +2002,7 @@ public class DeviceProfile {
if (mDeviceProperties.isPhone() && isQsbInline) { if (mDeviceProperties.isPhone() && isQsbInline) {
return getHotseatBarBottomPadding() return getHotseatBarBottomPadding()
- ((getHotseatProfile().getQsbHeight() - hotseatCellHeightPx) / 2); - ((getHotseatProfile().getQsbHeight() - hotseatCellHeightPx) / 2);
} else if (isTaskbarPresent || (mDeviceProperties.isLandscape() && isQsbInline)) { // QSB on top } else if (isQsbOnTop() || (mDeviceProperties.isLandscape() && isQsbInline)) { // LC-Note: isQsbOnTop
return hotseatBarSizePx - getHotseatProfile().getQsbHeight() return hotseatBarSizePx - getHotseatProfile().getQsbHeight()
+ getHotseatProfile().getQsbShadowHeight(); + getHotseatProfile().getQsbShadowHeight();
} else { } else {
@@ -2005,7 +2014,7 @@ public class DeviceProfile {
* Returns the number of pixels the hotseat is translated from the bottom of the screen. * Returns the number of pixels the hotseat is translated from the bottom of the screen.
*/ */
private int getHotseatBarBottomPadding() { private int getHotseatBarBottomPadding() {
if (isTaskbarPresent || isQsbInline) { // QSB on top or inline if (isQsbOnTop() || isQsbInline) { // LC-Note: isQsbOnTop
return hotseatBarBottomSpacePx - (Math.abs(hotseatCellHeightPx - iconSizePx) / 2); return hotseatBarBottomSpacePx - (Math.abs(hotseatCellHeightPx - iconSizePx) / 2);
} else { } else {
return hotseatBarSizePx - hotseatCellHeightPx; return hotseatBarSizePx - hotseatCellHeightPx;
@@ -347,7 +347,16 @@ public class InvariantDeviceProfile {
this.mThemeManager = ThemeManager.INSTANCE.get(context.getApplicationContext()); this.mThemeManager = ThemeManager.INSTANCE.get(context.getApplicationContext());
this.mDisplayController = DisplayController.INSTANCE.get(context.getApplicationContext()); this.mDisplayController = DisplayController.INSTANCE.get(context.getApplicationContext());
String gridName = DeviceProfileOverrides.INSTANCE.get(context).getGridName(dbGridInfo); String gridName = DeviceProfileOverrides.INSTANCE.get(context).getGridName(dbGridInfo);
initGrid(context, gridName, dbGridInfo); initGrid(context, gridName, dbGridInfo, null);
}
public InvariantDeviceProfile(Context context, DeviceProfileOverrides.DBGridInfo dbGridInfo,
DeviceProfileOverrides.PreviewOverrides previewOverrides) {
this.mPrefs = LauncherPrefs.get(context.getApplicationContext());
this.mThemeManager = ThemeManager.INSTANCE.get(context.getApplicationContext());
this.mDisplayController = DisplayController.INSTANCE.get(context.getApplicationContext());
String gridName = DeviceProfileOverrides.INSTANCE.get(context).getGridName(dbGridInfo);
initGrid(context, gridName, dbGridInfo, previewOverrides);
} }
private String initGrid(Context context, String gridName) { private String initGrid(Context context, String gridName) {
@@ -376,7 +385,7 @@ public class InvariantDeviceProfile {
} }
DeviceProfileOverrides.DBGridInfo dbGridInfo = DeviceProfileOverrides.INSTANCE.get(context) DeviceProfileOverrides.DBGridInfo dbGridInfo = DeviceProfileOverrides.INSTANCE.get(context)
.getGridInfo(); .getGridInfo();
initGrid(context, displayInfo, displayOption, dbGridInfo); initGrid(context, displayInfo, displayOption, dbGridInfo, null);
FileLog.d(TAG, "After initGrid:" FileLog.d(TAG, "After initGrid:"
+ "gridName:" + gridName + "gridName:" + gridName
+ ", dbFile:" + dbFile + ", dbFile:" + dbFile
@@ -386,6 +395,11 @@ public class InvariantDeviceProfile {
} }
private String initGrid(Context context, String gridName, DeviceProfileOverrides.DBGridInfo dbGridInfo) { private String initGrid(Context context, String gridName, DeviceProfileOverrides.DBGridInfo dbGridInfo) {
return initGrid(context, gridName, dbGridInfo, null);
}
private String initGrid(Context context, String gridName, DeviceProfileOverrides.DBGridInfo dbGridInfo,
DeviceProfileOverrides.PreviewOverrides previewOverrides) {
Info displayInfo = mDisplayController.getInfo(); Info displayInfo = mDisplayController.getInfo();
List<DisplayOption> allOptions = getPredefinedDeviceProfiles( List<DisplayOption> allOptions = getPredefinedDeviceProfiles(
context, context,
@@ -407,7 +421,7 @@ public class InvariantDeviceProfile {
if (!displayOption.grid.name.equals(gridName)) { if (!displayOption.grid.name.equals(gridName)) {
mPrefs.put(GRID_NAME, displayOption.grid.name); mPrefs.put(GRID_NAME, displayOption.grid.name);
} }
initGrid(context, displayInfo, displayOption, dbGridInfo); initGrid(context, displayInfo, displayOption, dbGridInfo, previewOverrides);
return displayOption.grid.name; return displayOption.grid.name;
} }
@@ -427,7 +441,9 @@ public class InvariantDeviceProfile {
initGrid(context, getCurrentGridName(context)); initGrid(context, getCurrentGridName(context));
} }
private void initGrid(Context context, Info displayInfo, DisplayOption displayOption, DeviceProfileOverrides.DBGridInfo dbGridInfo) { private void initGrid(Context context, Info displayInfo, DisplayOption displayOption,
DeviceProfileOverrides.DBGridInfo dbGridInfo,
DeviceProfileOverrides.PreviewOverrides previewOverrides) {
this.closestProfile = displayOption.grid; this.closestProfile = displayOption.grid;
enableTwoLinesInAllApps = Flags.enableTwolineToggle() enableTwoLinesInAllApps = Flags.enableTwolineToggle()
@@ -435,8 +451,8 @@ public class InvariantDeviceProfile {
&& mPrefs.get(ENABLE_TWOLINE_ALLAPPS_TOGGLE); && mPrefs.get(ENABLE_TWOLINE_ALLAPPS_TOGGLE);
mLocale = context.getResources().getConfiguration().locale.toString(); mLocale = context.getResources().getConfiguration().locale.toString();
DeviceProfileOverrides.Options overrideOptions = DeviceProfileOverrides.INSTANCE.get(context) DeviceProfileOverrides.Options overrideOptions = DeviceProfileOverrides.INSTANCE.get(context)
.getOverrides(displayOption.grid); .getOverrides(displayOption.grid, displayInfo.getDeviceType(), previewOverrides);
DisplayMetrics metrics = context.getResources().getDisplayMetrics(); DisplayMetrics metrics = context.getResources().getDisplayMetrics();
GridOption closestProfile = displayOption.grid; GridOption closestProfile = displayOption.grid;
numRows = dbGridInfo.getNumRows(); numRows = dbGridInfo.getNumRows();
@@ -489,7 +505,7 @@ public class InvariantDeviceProfile {
horizontalMargin = displayOption.horizontalMargin; horizontalMargin = displayOption.horizontalMargin;
numShownHotseatIcons = deviceType == TYPE_MULTI_DISPLAY numShownHotseatIcons = deviceType == TYPE_MULTI_DISPLAY
? closestProfile.numDatabaseHotseatIcons : dbGridInfo.getNumHotseatColumns(); ? closestProfile.numHotseatIcons : dbGridInfo.getNumHotseatColumns();
numDatabaseHotseatIcons = deviceType == TYPE_MULTI_DISPLAY numDatabaseHotseatIcons = deviceType == TYPE_MULTI_DISPLAY
? closestProfile.numDatabaseHotseatIcons : numShownHotseatIcons; ? closestProfile.numDatabaseHotseatIcons : numShownHotseatIcons;
hotseatBarBottomSpace = displayOption.hotseatBarBottomSpace; hotseatBarBottomSpace = displayOption.hotseatBarBottomSpace;
@@ -729,6 +729,7 @@ public class DisplayController implements DesktopVisibilityListener {
public @DeviceType int getDeviceType() { public @DeviceType int getDeviceType() {
if (mIsDesktopFormFactor) { if (mIsDesktopFormFactor) {
Log.d("LC-DisplayController", "Device has desktop profile (Desktop)");
return TYPE_DESKTOP; return TYPE_DESKTOP;
} }