diff --git a/flexera/release.ps1 b/flexera/release.ps1 index b0b3cd338a..2561297303 100644 --- a/flexera/release.ps1 +++ b/flexera/release.ps1 @@ -26,6 +26,10 @@ .PARAMETER NotesFile A text file with the notes, as an alternative to -Notes. +.PARAMETER BuildOnly + Stop after building and checking the APK, without publishing. Try the APK on a phone, then + run the script again with -SkipBuild to publish that same file. + .PARAMETER SkipBuild Publish the APK that is already in build\outputs instead of building again. @@ -37,11 +41,16 @@ .EXAMPLE .\flexera\release.ps1 -Version 0.2.0 -Notes "The alphabet follows the finger more closely." + +.EXAMPLE + .\flexera\release.ps1 -Version 0.2.0 -BuildOnly + .\flexera\release.ps1 -Version 0.2.0 -SkipBuild -Notes "The alphabet follows the finger more closely." #> param( [Parameter(Mandatory = $true)][string]$Version, [string]$Notes = "", [string]$NotesFile, + [switch]$BuildOnly, [switch]$SkipBuild, [switch]$AllowDirty, [switch]$PushSource @@ -163,6 +172,12 @@ if ($signer -match 'Android Debug') { throw "The APK is signed with the debug ke Write-Host $signer Write-Host ("APK: {0} ({1:N1} MB)" -f (Split-Path $apk -Leaf), ((Get-Item $apk).Length / 1MB)) +if ($BuildOnly) { + Write-Host "`nBuilt and checked, not published: $apk" -ForegroundColor Green + Write-Host "Publish this file with: .\flexera\release.ps1 -Version $Version -SkipBuild -Notes `"...`"" + return +} + # --- 4. Release ---------------------------------------------------------------------------------- Step "4/6 Creating release v$Version in $ReleaseRepository" diff --git a/flexera/res/values/flexera_strings.xml b/flexera/res/values/flexera_strings.xml index d5c98be4d6..157a9aa446 100644 --- a/flexera/res/values/flexera_strings.xml +++ b/flexera/res/values/flexera_strings.xml @@ -43,6 +43,8 @@ Alphabet Alphabet on the left edge For left-handed use. The letters normally sit on the right edge. + Scrub from the home screen + Slide up or down along the alphabet\'s edge of the home screen to open the app list at a letter. Swipe up and hold does the same from anywhere. Updates diff --git a/flexera/src/app/flexera/drawer/FlexeraDrawer.kt b/flexera/src/app/flexera/drawer/FlexeraDrawer.kt index 8c7a1e27e1..74ece1ccc1 100644 --- a/flexera/src/app/flexera/drawer/FlexeraDrawer.kt +++ b/flexera/src/app/flexera/drawer/FlexeraDrawer.kt @@ -2,6 +2,7 @@ package app.flexera.drawer import android.content.Context import android.graphics.Color +import android.graphics.Point import android.graphics.Rect import android.os.Build import android.util.TypedValue @@ -198,13 +199,43 @@ object FlexeraDrawer { ev: MotionEvent, ) { val wave = waveAlphabet(appsView) ?: return - val bounds = Rect() - dragLayer.getDescendantRectRelativeToSelf(wave, bounds) - val x = ev.x - bounds.left - val y = ev.y - bounds.top + val offset = layoutOffset(wave, dragLayer) + val x = ev.x - offset.x + val y = ev.y - offset.y if (wave.isScrubbing) wave.moveScrub(x, y) else wave.beginScrub(x, y) } + /** + * True when [ev], given in the drag layer's coordinates, lands where the wave alphabet's + * strip will be once the drawer is open. Scrub from home (PRD AD-10) starts there. + */ + @JvmStatic + fun isOnHomeEdge( + appsView: ActivityAllAppsContainerView<*>, + dragLayer: BaseDragLayer<*>, + ev: MotionEvent, + ): Boolean { + val wave = waveAlphabet(appsView) ?: return false + val offset = layoutOffset(wave, dragLayer) + return wave.isOnStripBand(ev.x - offset.x, ev.y - offset.y) + } + + /** + * Where [view] sits inside [ancestor] going by layout alone. A closed drawer is parked + * off-screen with a translation; this ignores it, giving the place the view has when the + * drawer is open. + */ + private fun layoutOffset(view: View, ancestor: View): Point { + val offset = Point() + var current: View? = view + while (current != null && current !== ancestor) { + offset.x += current.left + offset.y += current.top + current = current.parent as? View + } + return offset + } + /** Ends a scrub started by [scrubFromSwipe]; the drawer stays open at the chosen letter. */ @JvmStatic fun endScrubFromSwipe(appsView: ActivityAllAppsContainerView<*>) { diff --git a/flexera/src/app/flexera/drawer/FlexeraEdgeScrubController.kt b/flexera/src/app/flexera/drawer/FlexeraEdgeScrubController.kt new file mode 100644 index 0000000000..f16b114275 --- /dev/null +++ b/flexera/src/app/flexera/drawer/FlexeraEdgeScrubController.kt @@ -0,0 +1,90 @@ +package app.flexera.drawer + +import android.view.MotionEvent +import android.view.ViewConfiguration +import app.lawnchair.preferences.PreferenceManager +import com.android.launcher3.AbstractFloatingView +import com.android.launcher3.Launcher +import com.android.launcher3.LauncherState +import com.android.launcher3.Utilities +import com.android.launcher3.util.TouchController +import kotlin.math.abs + +/** + * Scrub from home (PRD AD-10). With the setting on, a vertical slide that starts on the home + * screen's edge, where the drawer's alphabet sits, opens the drawer and scrubs it in the same + * touch. Nothing is drawn on the home screen; the letters appear with the drawer. + * + * The edge is shared with page swipes, taps and item drags, so this claims a touch only once it + * has clearly moved up or down, and never while an item is being dragged. It is off by default. + */ +class FlexeraEdgeScrubController(private val launcher: Launcher) : TouchController { + + private val touchSlop = ViewConfiguration.get(launcher).scaledTouchSlop + private val prefs = PreferenceManager.getInstance(launcher) + + /** The current touch started on the edge and may still turn into a scrub. */ + private var candidate = false + private var scrubbing = false + private var downX = 0f + private var downY = 0f + + override fun onControllerInterceptTouchEvent(ev: MotionEvent): Boolean { + when (ev.actionMasked) { + MotionEvent.ACTION_DOWN -> { + scrubbing = false + candidate = canStart(ev) + downX = ev.x + downY = ev.y + } + + MotionEvent.ACTION_MOVE -> if (candidate) { + val dx = abs(ev.x - downX) + val dy = abs(ev.y - downY) + when { + // A long press picked something up, or the finger went sideways: not ours. + launcher.dragController.isDragging || (dx > touchSlop && dx >= dy) -> candidate = false + + dy > touchSlop && dy > dx * VERTICAL_BIAS -> { + candidate = false + scrubbing = true + launcher.stateManager.goToState(LauncherState.ALL_APPS) + FlexeraDrawer.scrubFromSwipe(launcher.appsView, launcher.dragLayer, ev) + return true + } + } + } + + MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> candidate = false + } + return false + } + + override fun onControllerTouchEvent(ev: MotionEvent): Boolean { + if (!scrubbing) return false + when (ev.actionMasked) { + MotionEvent.ACTION_MOVE -> + FlexeraDrawer.scrubFromSwipe(launcher.appsView, launcher.dragLayer, ev) + + MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> { + scrubbing = false + FlexeraDrawer.endScrubFromSwipe(launcher.appsView) + } + } + return true + } + + private fun canStart(ev: MotionEvent): Boolean = + prefs.flexeraScrubFromHome.get() && + (ev.edgeFlags and Utilities.EDGE_NAV_BAR) == 0 && + launcher.isInState(LauncherState.NORMAL) && + AbstractFloatingView.getTopOpenView(launcher) == null && + FlexeraDrawer.isOnHomeEdge(launcher.appsView, launcher.dragLayer, ev) + + override fun dump(): String = "FlexeraEdgeScrubController candidate=$candidate scrubbing=$scrubbing" + + private companion object { + /** A slide counts as vertical when it has moved this many times further up or down than sideways. */ + const val VERTICAL_BIAS = 2f + } +} diff --git a/flexera/src/app/flexera/drawer/WaveAlphabetView.kt b/flexera/src/app/flexera/drawer/WaveAlphabetView.kt index 51c1a1a6a8..3bc4eebb46 100644 --- a/flexera/src/app/flexera/drawer/WaveAlphabetView.kt +++ b/flexera/src/app/flexera/drawer/WaveAlphabetView.kt @@ -71,6 +71,9 @@ class WaveAlphabetView( private val waveWidth = res.getDimension(R.dimen.flexera_wave_width) private val letterSize = res.getDimension(R.dimen.flexera_wave_letter_size) + /** The section name Launcher3 gives the work profile's info card, an "i" in a circle. */ + private val workInfoSection = res.getString(R.string.work_profile_edu_section) + // The drawer has no background, so the letters are white like the app names: slightly // dimmed at rest, full white near the finger. private val idleColor = ColorUtils.setAlphaComponent(Color.WHITE, 0xB3) @@ -129,8 +132,9 @@ class WaveAlphabetView( for (section in sections) { val label = section.sectionName?.toString().orEmpty() // Launcher3 repeats the last section once more so its own scroller can reach the - // bottom, and Private Space uses a long badge string; neither is a letter. - if (label.isEmpty() || label.length > 2) continue + // bottom, Private Space uses a long badge string, and the work profile's info card + // has a section of its own; none of these is a letter. + if (label.isEmpty() || label.length > 2 || label == workInfoSection) continue if (result.isNotEmpty() && result.last().label == label) continue result.add(Letter(label, section.position)) } @@ -218,19 +222,24 @@ class WaveAlphabetView( if (layoutDirty) computeLayout() if (letters.isEmpty()) return - // On the left edge the wave reaches out over the icons and names, so darken the list - // under it for as long as the finger is down. On the right it crosses empty space. - if (onLeft && press > 0f) { - canvas.drawColor(Color.argb((LEFT_SCRUB_DIM * press * 255).toInt(), 0, 0, 0)) - } - // Where the letters rest, and which way "away from the edge" points. val baseX = if (onLeft) edgeInset else width - edgeInset val inward = if (onLeft) 1f else -1f // The wave's peak sits a fixed gap before the finger, so it reaches further out the // further the finger is from the edge. val fingerDistance = (fingerX - baseX) * inward - val reach = (fingerDistance + fingerGap).coerceIn(minReach, width * 0.7f) * press + val maxReach = width * 0.7f + val fullReach = (fingerDistance + fingerGap).coerceIn(minReach, maxReach) + val reach = fullReach * press + + // Darken the list under the wave where its letters cross icons and names. From the + // right edge that only happens once the wave reaches well into the screen, as it does + // after swipe up and hold; from the left edge the icons sit right beside the strip. + if (press > 0f) { + val overList = if (onLeft) 1f else ((fullReach - minReach) / (maxReach - minReach)).coerceIn(0f, 1f) + val dim = (SCRUB_DIM * press * overList * 255).toInt() + if (dim > 0) canvas.drawColor(Color.argb(dim, 0, 0, 0)) + } for (i in letters.indices) { val y = letterYs[i] @@ -258,9 +267,16 @@ class WaveAlphabetView( // ---- Touch ------------------------------------------------------------------------------- /** True when a touch at this point, in this view's coordinates, starts a scrub. */ - fun isOnStrip(x: Float, y: Float): Boolean { + fun isOnStrip(x: Float, y: Float): Boolean = + appsView.isInAllApps && !appsView.isSearching && isOnStripBand(x, y) + + /** + * True when this point lies on the band along the edge that the letters occupy, whether or + * not the drawer is open. Scrub from home (PRD AD-10) asks this while the drawer is closed. + */ + fun isOnStripBand(x: Float, y: Float): Boolean { if (layoutDirty) computeLayout() - if (letters.isEmpty() || !appsView.isInAllApps || appsView.isSearching) return false + if (letters.isEmpty()) return false val onEdge = if (onLeft) x <= touchWidth else x >= width - touchWidth return onEdge && y >= stripTop - verticalMargin && y <= stripBottom + verticalMargin } @@ -449,8 +465,8 @@ class WaveAlphabetView( /** How much its neighbours grow, tapering with distance from the finger. */ const val NEAR_GROWTH = 0.5f - /** How dark the list gets under a scrub from the left edge (0 = unchanged, 1 = black). */ - const val LEFT_SCRUB_DIM = 0.55f + /** How dark the list gets under a wave that lies fully over it (0 = unchanged, 1 = black). */ + const val SCRUB_DIM = 0.55f /** The tallest a resting letter may be, as a share of the vertical room it has. */ const val MAX_LETTER_SHARE_OF_SLOT = 0.85f diff --git a/flexera/src/app/flexera/ui/FlexeraDrawerSettings.kt b/flexera/src/app/flexera/ui/FlexeraDrawerSettings.kt index f776e15669..a6d56410ee 100644 --- a/flexera/src/app/flexera/ui/FlexeraDrawerSettings.kt +++ b/flexera/src/app/flexera/ui/FlexeraDrawerSettings.kt @@ -61,6 +61,11 @@ fun FlexeraDrawerSettings(modifier: Modifier = Modifier) { description = stringResource(id = R.string.flexera_alphabet_on_left_description), adapter = prefs.flexeraAlphabetOnLeft.getAdapter(), ) + SwitchPreference( + label = stringResource(id = R.string.flexera_scrub_from_home), + description = stringResource(id = R.string.flexera_scrub_from_home_description), + adapter = prefs.flexeraScrubFromHome.getAdapter(), + ) } // No background colour or opacity here: the drawer is transparent with white text, and a // light panel behind white text would make the list unreadable. diff --git a/lawnchair/src/app/lawnchair/LawnchairLauncher.kt b/lawnchair/src/app/lawnchair/LawnchairLauncher.kt index f415675229..0895ceb430 100644 --- a/lawnchair/src/app/lawnchair/LawnchairLauncher.kt +++ b/lawnchair/src/app/lawnchair/LawnchairLauncher.kt @@ -308,7 +308,9 @@ class LawnchairLauncher : QuickstepLauncher() { override fun createTouchControllers(): Array { val verticalSwipeController = VerticalSwipeTouchController(this, gestureController) - return arrayOf(verticalSwipeController) + super.createTouchControllers() + // Flexera Launcher: scrub from home (PRD AD-10) gets the first look at touches on the alphabet's edge. + val edgeScrubController = app.flexera.drawer.FlexeraEdgeScrubController(this) + return arrayOf(edgeScrubController, verticalSwipeController) + super.createTouchControllers() } override fun handleHomeTap() { diff --git a/lawnchair/src/app/lawnchair/preferences/PreferenceManager.kt b/lawnchair/src/app/lawnchair/preferences/PreferenceManager.kt index c3bd6ea71d..226b632b59 100644 --- a/lawnchair/src/app/lawnchair/preferences/PreferenceManager.kt +++ b/lawnchair/src/app/lawnchair/preferences/PreferenceManager.kt @@ -217,6 +217,9 @@ class PreferenceManager @Inject constructor( // Flexera Launcher: which screen edge the drawer's wave alphabet sits on (PRD AD-15). val flexeraAlphabetOnLeft = BoolPref("pref_flexeraAlphabetOnLeft", false, recreate) + // Flexera Launcher: whether a slide along the home screen's edge opens the drawer at a letter (PRD AD-10). + val flexeraScrubFromHome = BoolPref("pref_flexeraScrubFromHome", false) + override fun close() { TODO("Not yet implemented") } diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java index 7b2932a615..1b1158ae78 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java +++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java @@ -117,11 +117,17 @@ public class NoButtonNavbarToOverviewTouchController extends PortraitStatesTouch protected boolean canInterceptTouch(MotionEvent ev) { mIsTrackpadSwipe = isTrackpadMotionEvent(ev); mLauncher.setCanShowAllAppsEducationView(!mIsTrackpadSwipe); - if (!mIsTrackpadSwipe && DisplayController.getNavigationMode(mLauncher) - == THREE_BUTTONS) { + boolean threeButtons = !mIsTrackpadSwipe + && DisplayController.getNavigationMode(mLauncher) == THREE_BUTTONS; + // Flexera Launcher: with three-button navigation this controller used to stand aside + // completely. It now still takes the ordinary swipe up from the home screen, so that + // swipe up and hold (PRD AD-17) works in that mode too. Everything else is left to the + // controllers after it, as before. + if (threeButtons && !mLauncher.isInState(NORMAL)) { return false; } - mDidTouchStartInNavBar = (ev.getEdgeFlags() & EDGE_NAV_BAR) != 0; + // The buttons own the navigation bar, so a touch never counts as starting on it. + mDidTouchStartInNavBar = !threeButtons && (ev.getEdgeFlags() & EDGE_NAV_BAR) != 0; boolean isOneHandedModeActive = (SystemUiProxy.INSTANCE.get(mLauncher) .getLastSystemUiStateFlags() & SYSUI_STATE_ONE_HANDED_ACTIVE) != 0; // Reset touch slop multiplier to default 1.0f if one-handed-mode is not active diff --git a/src/com/android/launcher3/workprofile/PersonalWorkSlidingTabStrip.java b/src/com/android/launcher3/workprofile/PersonalWorkSlidingTabStrip.java index 534157f75f..b5c70d7c90 100644 --- a/src/com/android/launcher3/workprofile/PersonalWorkSlidingTabStrip.java +++ b/src/com/android/launcher3/workprofile/PersonalWorkSlidingTabStrip.java @@ -121,6 +121,12 @@ public class PersonalWorkSlidingTabStrip extends LinearLayout implements PageInd */ public static int getTabWidth(Context context, int totalWidth) { DeviceProfile grid = ActivityContext.lookupContext(context).getDeviceProfile(); + // Flexera Launcher: the one-column drawer list has no left and right icon to span. The + // grid maths below would make the tabs and the work info card one icon wide, so line + // them up with the search bar instead. + if (grid.numShownAllAppsColumns == app.flexera.drawer.FlexeraDrawer.COLUMNS) { + return totalWidth - 2 * app.flexera.drawer.FlexeraDrawer.rowStartPadding(context); + } int iconPadding = totalWidth / grid.numShownAllAppsColumns - grid.getAllAppsProfile().getIconSizePx(); return totalWidth - iconPadding;