Turn the app drawer into a one-column list with letter headers
PRD M2 (AD-1 to AD-3, AD-7, AD-8, HS-7). The drawer keeps Launcher3's adapter, sorting, live updates, click, long-press menu and drag; only what differs from the grid is new. - FlexeraDrawer (flexera/src): column count, row styling, and a letter-header adapter item - flexera/res/layout/all_apps_icon.xml overrides Launcher3's grid cell with a horizontal row (46dp icon, name beside it, 64dp tall) - AlphabeticalAppsList adds a header item at the start of each letter group and points the fast-scroll section at it; Private Space keeps its own framing - BaseAllAppsAdapter creates and binds the header view type and styles rows - DeviceProfileOverrides forces one drawer column - DeviceProfile.getAllAppsIconStartMargin returns the row padding for a one-column drawer, which keeps the search bar full width - Suggested apps row above the list is off by default Tested on the Android 17 emulator: scrolling, tap to launch, long-press menu, drag to the home screen, and live removal and return of an app all work. The right-edge fast scroller did not respond to scripted drags and still needs a hand test. Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
This commit is contained in:
1 parent
9376781e8a
commit
76c3603693
9 files changed
+165
-2
No files matched your search
@@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Flexera Launcher: an app row in the drawer list (PRD AD-2).
|
||||
Overrides Launcher3's res/layout/all_apps_icon.xml, which is a grid cell with the icon above the
|
||||
label. Here the icon sits at the start and the name beside it. It is still a BubbleTextView, so
|
||||
click, long-press menu, drag and notification dots keep working unchanged.
|
||||
Height, icon gap and text size are set in code by FlexeraDrawer.styleAppRow.
|
||||
-->
|
||||
<com.android.launcher3.BubbleTextView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:launcher="http://schemas.android.com/apk/res-auto"
|
||||
style="@style/BaseIcon.AllApps"
|
||||
android:id="@+id/icon"
|
||||
android:ellipsize="end"
|
||||
android:gravity="start|center_vertical"
|
||||
android:paddingStart="@dimen/flexera_drawer_row_padding_start"
|
||||
android:paddingEnd="@dimen/flexera_drawer_row_padding_end"
|
||||
android:textAlignment="viewStart"
|
||||
launcher:centerVertically="false"
|
||||
launcher:iconDisplay="all_apps"
|
||||
launcher:iconSizeOverride="@dimen/flexera_drawer_row_icon_size"
|
||||
launcher:layoutHorizontal="true" />
|
||||
@@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Flexera Launcher: a letter header that starts each group in the drawer list (PRD AD-3). -->
|
||||
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/flexera_drawer_header_height"
|
||||
android:gravity="start|bottom"
|
||||
android:importantForAccessibility="yes"
|
||||
android:accessibilityHeading="true"
|
||||
android:paddingStart="@dimen/flexera_drawer_row_padding_start"
|
||||
android:paddingEnd="@dimen/flexera_drawer_row_padding_end"
|
||||
android:paddingBottom="@dimen/flexera_drawer_header_padding_bottom"
|
||||
android:textAlignment="viewStart"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:textSize="@dimen/flexera_drawer_header_text_size"
|
||||
android:textStyle="bold" />
|
||||
@@ -14,4 +14,7 @@
|
||||
|
||||
<!-- Lawnchair's own nightly updater does not apply to this app. -->
|
||||
<bool name="config_default_enable_nightly_auto_updater">false</bool>
|
||||
|
||||
<!-- The drawer is a plain alphabetical list (PRD AD-2), so no row of suggested apps above it. -->
|
||||
<bool name="config_default_show_suggested_apps_at_drawer_top">false</bool>
|
||||
</resources>
|
||||
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!-- Drawer list (PRD AD-2, AD-3). Rows are sized for thumb taps; 48dp is Android's minimum target. -->
|
||||
<dimen name="flexera_drawer_row_height">64dp</dimen>
|
||||
<!-- Smaller than a home-screen icon, so rows have air between them. -->
|
||||
<dimen name="flexera_drawer_row_icon_size">46dp</dimen>
|
||||
<dimen name="flexera_drawer_row_icon_gap">20dp</dimen>
|
||||
<dimen name="flexera_drawer_row_text_size">17sp</dimen>
|
||||
<dimen name="flexera_drawer_row_padding_start">24dp</dimen>
|
||||
<!-- Leaves the screen edge free for the scroller now and the wave alphabet later (M3). -->
|
||||
<dimen name="flexera_drawer_row_padding_end">56dp</dimen>
|
||||
|
||||
<dimen name="flexera_drawer_header_height">44dp</dimen>
|
||||
<dimen name="flexera_drawer_header_padding_bottom">6dp</dimen>
|
||||
<dimen name="flexera_drawer_header_text_size">14sp</dimen>
|
||||
</resources>
|
||||
@@ -0,0 +1,77 @@
|
||||
package app.flexera.drawer
|
||||
|
||||
import android.content.Context
|
||||
import android.util.TypedValue
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.TextView
|
||||
import com.android.launcher3.BubbleTextView
|
||||
import com.android.launcher3.R
|
||||
import com.android.launcher3.allapps.BaseAllAppsAdapter.AdapterItem
|
||||
|
||||
/**
|
||||
* Flexera Launcher's app drawer: a single-column alphabetical list with letter headers
|
||||
* (PRD AD-2 and AD-3), built on Launcher3's all-apps adapter rather than replacing it.
|
||||
*
|
||||
* Launcher3 keeps doing the work it already does well: loading apps, sorting them, live updates,
|
||||
* click, long-press menu and drag. This object supplies only what differs from the stock grid:
|
||||
* - the column count ([COLUMNS]),
|
||||
* - the look of a row ([styleAppRow]; the row layout itself is flexera/res/layout/all_apps_icon.xml,
|
||||
* which overrides Launcher3's layout of the same name), and
|
||||
* - a letter header row ([VIEW_TYPE_SECTION_HEADER] and the functions that create and bind it).
|
||||
*/
|
||||
object FlexeraDrawer {
|
||||
|
||||
/** The drawer is one column wide. */
|
||||
const val COLUMNS = 1
|
||||
|
||||
/**
|
||||
* Adapter view type of a letter header row. Launcher3 uses bits 1 to 10 and Lawnchair's search
|
||||
* results use bits 10 to 22, so this stays clear of both.
|
||||
*/
|
||||
const val VIEW_TYPE_SECTION_HEADER = 1 shl 24
|
||||
|
||||
/** Creates the adapter item for the header that starts the section named [sectionName]. */
|
||||
@JvmStatic
|
||||
fun sectionHeader(sectionName: String): AdapterItem = SectionHeaderItem(sectionName)
|
||||
|
||||
@JvmStatic
|
||||
fun createSectionHeader(inflater: LayoutInflater, parent: ViewGroup): View =
|
||||
inflater.inflate(R.layout.flexera_all_apps_section_header, parent, false)
|
||||
|
||||
@JvmStatic
|
||||
fun bindSectionHeader(view: View, item: AdapterItem) {
|
||||
(view as TextView).text = (item as? SectionHeaderItem)?.sectionName.orEmpty()
|
||||
}
|
||||
|
||||
/**
|
||||
* Finishes turning a freshly inflated all-apps icon into a list row. Launcher3 sizes the view
|
||||
* as a grid cell (icon above a small label); a row needs its own height, a wider gap between
|
||||
* icon and name, and a label large enough to read in a list.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun styleAppRow(icon: BubbleTextView) {
|
||||
val res = icon.resources
|
||||
icon.layoutParams.height = res.getDimensionPixelSize(R.dimen.flexera_drawer_row_height)
|
||||
icon.compoundDrawablePadding = res.getDimensionPixelSize(R.dimen.flexera_drawer_row_icon_gap)
|
||||
icon.setTextSize(TypedValue.COMPLEX_UNIT_PX, res.getDimension(R.dimen.flexera_drawer_row_text_size))
|
||||
}
|
||||
|
||||
/**
|
||||
* How far a row's icon sits from the edge of the list. Launcher3 uses its grid version of
|
||||
* this number to line the search bar up with the first icon, so the list supplies its own.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun rowStartPadding(context: Context): Int =
|
||||
context.resources.getDimensionPixelSize(R.dimen.flexera_drawer_row_padding_start)
|
||||
|
||||
/**
|
||||
* A letter header. It carries its letter so that the adapter's diffing notices when the
|
||||
* header at a position changes, for example after an app is installed or removed.
|
||||
*/
|
||||
class SectionHeaderItem(val sectionName: String) : AdapterItem(VIEW_TYPE_SECTION_HEADER) {
|
||||
override fun isContentSame(other: AdapterItem): Boolean =
|
||||
other is SectionHeaderItem && other.sectionName == sectionName
|
||||
}
|
||||
}
|
||||
@@ -158,8 +158,9 @@ class DeviceProfileOverrides @Inject constructor(
|
||||
|
||||
fun applyUi(idp: InvariantDeviceProfile) {
|
||||
// apply grid size
|
||||
idp.numAllAppsColumns = numAllAppsColumns
|
||||
idp.numDatabaseAllAppsColumns = numAllAppsColumns
|
||||
// Flexera Launcher: the drawer is a one-column list, whatever the column preference says.
|
||||
idp.numAllAppsColumns = app.flexera.drawer.FlexeraDrawer.COLUMNS
|
||||
idp.numDatabaseAllAppsColumns = app.flexera.drawer.FlexeraDrawer.COLUMNS
|
||||
idp.numFolderRows[INDEX_DEFAULT] = numFolderRows
|
||||
idp.numFolderColumns[INDEX_DEFAULT] = numFolderColumns
|
||||
|
||||
|
||||
@@ -1979,6 +1979,14 @@ public class DeviceProfile {
|
||||
|
||||
/** The margin between the edge of all apps and the edge of the first icon. */
|
||||
public int getAllAppsIconStartMargin(Context context) {
|
||||
// Flexera Launcher: in the one-column drawer list the first icon sits at the row's start
|
||||
// padding. The grid maths below would instead centre it in a full-width cell, which
|
||||
// squeezed the search bar down to a small pill.
|
||||
if (numShownAllAppsColumns == app.flexera.drawer.FlexeraDrawer.COLUMNS) {
|
||||
return (Utilities.isRtl(context.getResources()) ? allAppsPadding.right
|
||||
: allAppsPadding.left)
|
||||
+ app.flexera.drawer.FlexeraDrawer.rowStartPadding(context);
|
||||
}
|
||||
int allAppsSpacing;
|
||||
if (isVerticalBarLayout()) {
|
||||
// On phones, the landscape layout uses a different setup.
|
||||
|
||||
@@ -491,6 +491,17 @@ public class AlphabeticalAppsList<T extends Context & ActivityContext> implement
|
||||
Log.d(TAG, "Adding apps with sections. HasPrivateApps: " + hasPrivateApps);
|
||||
for (int i = 0; i < appList.size(); i++) {
|
||||
AppInfo info = appList.get(i);
|
||||
// Flexera Launcher: start each letter group with a header row (PRD AD-3). The header
|
||||
// takes this position, so the fast-scroll section created below lands on the header.
|
||||
// Private Space keeps Launcher3's own framing and gets no headers.
|
||||
if (!hasPrivateApps && !info.sectionName.equals(lastSectionName)) {
|
||||
mAdapterItems.add(app.flexera.drawer.FlexeraDrawer.sectionHeader(info.sectionName));
|
||||
Log.d(TAG, "addAppsWithSections: adding sectionName: " + info.sectionName
|
||||
+ " with header at position: " + position);
|
||||
lastSectionName = info.sectionName;
|
||||
mFastScrollerSections.add(new FastScrollSectionInfo(info.sectionName, position));
|
||||
position++;
|
||||
}
|
||||
// Apply decorator to private apps.
|
||||
if (hasPrivateApps) {
|
||||
mAdapterItems.add(AdapterItem.asAppWithDecorationInfo(info,
|
||||
|
||||
@@ -243,7 +243,13 @@ public abstract class BaseAllAppsAdapter<T extends Context & ActivityContext> ex
|
||||
// Ensure the all apps icon height matches the workspace icons in portrait mode.
|
||||
icon.getLayoutParams().height =
|
||||
mActivityContext.getDeviceProfile().getAllAppsProfile().getCellHeightPx();
|
||||
// Flexera Launcher: the drawer is a list, so size and style this as a row.
|
||||
app.flexera.drawer.FlexeraDrawer.styleAppRow(icon);
|
||||
return new ViewHolder(icon);
|
||||
case app.flexera.drawer.FlexeraDrawer.VIEW_TYPE_SECTION_HEADER:
|
||||
// Flexera Launcher: letter header that starts each group in the drawer list.
|
||||
return new ViewHolder(app.flexera.drawer.FlexeraDrawer.createSectionHeader(
|
||||
mLayoutInflater, parent));
|
||||
case VIEW_TYPE_EMPTY_SEARCH:
|
||||
return new ViewHolder(mLayoutInflater.inflate(R.layout.all_apps_empty_search,
|
||||
parent, false));
|
||||
@@ -325,6 +331,11 @@ public abstract class BaseAllAppsAdapter<T extends Context & ActivityContext> ex
|
||||
}
|
||||
break;
|
||||
}
|
||||
case app.flexera.drawer.FlexeraDrawer.VIEW_TYPE_SECTION_HEADER:
|
||||
// Flexera Launcher: letter header in the drawer list.
|
||||
app.flexera.drawer.FlexeraDrawer.bindSectionHeader(
|
||||
holder.itemView, mApps.getAdapterItems().get(position));
|
||||
break;
|
||||
case VIEW_TYPE_EMPTY_SEARCH: {
|
||||
AppInfo info = mApps.getAdapterItems().get(position).itemInfo;
|
||||
if (info != null) {
|
||||
|
||||
Reference in new issue
Block a user