Compare commits

...

3 commits

Author SHA1 Message Date
47ae0bf35f
update README.md
Some checks are pending
Android CI / build (push) Waiting to run
2025-02-16 16:51:18 +01:00
5669279c64
add <,>,V,Λ gestures 2025-02-16 15:50:13 +01:00
0c0d90a357
improve gesture detection 2025-02-15 03:08:18 +01:00
8 changed files with 420 additions and 155 deletions

View file

@ -14,12 +14,6 @@
µLauncher is an Android home screen that lets you launch apps using swipe gestures and button presses. µLauncher is an Android home screen that lets you launch apps using swipe gestures and button presses.
It is *minimal, efficient and free of distraction*. It is *minimal, efficient and free of distraction*.
Your home screen only displays the date, time and a wallpaper.
Pressing back or swiping up (this can be configured) opens a list
of all installed apps, which can be searched efficiently.
This is a fork of [finnmglas's app Launcher][original-repo].
<a href="https://f-droid.org/packages/de.jrpie.android.launcher/"><img src="https://fdroid.gitlab.io/artwork/badge/get-it-on.png" alt="Get it on F-Droid" height="80"></a> <a href="https://f-droid.org/packages/de.jrpie.android.launcher/"><img src="https://fdroid.gitlab.io/artwork/badge/get-it-on.png" alt="Get it on F-Droid" height="80"></a>
<a href="https://accrescent.app/app/de.jrpie.android.launcher.accrescent"><img alt="Get it on Accrescent" src="https://accrescent.app/badges/get-it-on.png" height="80"></a> <a href="https://accrescent.app/app/de.jrpie.android.launcher.accrescent"><img alt="Get it on Accrescent" src="https://accrescent.app/badges/get-it-on.png" height="80"></a>
@ -51,6 +45,45 @@ You can also [get it on Google Play](https://play.google.com/store/apps/details?
height="400"> height="400">
µLauncher is a fork of [finnmglas's app Launcher][original-repo].
An incomplete list of changes can be found [here](docs/launcher.md).
## Features
µLauncher only displays the date, time and a wallpaper.
Pressing back or swiping up (this can be configured) opens a list
of all installed apps, which can be searched efficiently.
The following gestures are available:
- volume up / down,
- swipe up / down / left / right,
- swipe with two fingers,
- swipe on the left / right resp. top / bottom edge,
- draw < / > / V / Λ
- click on date / time,
- double click,
- long click,
- back button.
To every gesture you can bind one of the following actions:
- launch an app,
- open a list of all / favorite / private apps,
- open µLauncher settings,
- toggle private space lock,
- lock the screen,
- toggle the torch,
- volume up / down,
- go to previous / next audio track.
µLauncher is compatible with [work profile](https://www.android.com/enterprise/work-profile/),
so apps like [Shelter](https://gitea.angry.im/PeterCxy/Shelter) can be used.
By default the font is set to [Hack][hack-font], but other fonts can be selected.
## Contributing ## Contributing
There are several ways to contribute to this app: There are several ways to contribute to this app:
@ -63,34 +96,10 @@ There are several ways to contribute to this app:
- Open a new pull request. - Open a new pull request.
See [BUILD.md](BUILD.md) for instructions how to build this project. See [build.md](docs/build.md) for instructions how to build this project.
The [CI pipeline](https://github.com/jrpie/Launcher/actions) automatically creates debug builds. The [CI pipeline](https://github.com/jrpie/Launcher/actions) automatically creates debug builds.
Note that those are not signed. Note that those are not signed.
## Notable changes compared to [Finn's Launcher][original-repo]:
* Edge gestures: There is a setting to allow distinguishing swiping at the edges of the screen from swiping in the center.
* Compatible with [work profile](https://www.android.com/enterprise/work-profile/), so apps like [Shelter](https://gitea.angry.im/PeterCxy/Shelter) can be used.
* The home button now works as expected.
### Visual
* This app uses the system wallpaper instead of a custom solution.
* The font has been changed to [Hack][hack-font].
* Font Awesome Icons were replaced by Material icons.
* The gear button on the home screen was removed. Instead pressing back opens the list of applications and the app settings are accessible from there.
### Search
* The search algorithm was modified to prefer matches at the beginning of the app name, i.e. when searching for `"te"`, `"termux"` is sorted before `"notes"`.
* The search bar was moved to the bottom of the screen.
### Technical
* Small improvements to the gesture detection.
* Different apps set as default.
* Package name was changed to `de.jrpie.android.launcher` to avoid clashing with the original app.
* Dropped support for API < 21 (i.e. pre Lollypop)
* Some refactoring
---
--- ---
[hack-font]: https://sourcefoundry.org/hack/ [hack-font]: https://sourcefoundry.org/hack/
[original-repo]: https://github.com/finnmglas/Launcher [original-repo]: https://github.com/finnmglas/Launcher

View file

@ -1,6 +1,7 @@
package de.jrpie.android.launcher.actions package de.jrpie.android.launcher.actions
import android.content.Context import android.content.Context
import android.util.Log
import de.jrpie.android.launcher.R import de.jrpie.android.launcher.R
import de.jrpie.android.launcher.preferences.LauncherPreferences import de.jrpie.android.launcher.preferences.LauncherPreferences
@ -169,6 +170,54 @@ enum class Gesture(
R.array.default_double_right, R.array.default_double_right,
R.anim.left_right R.anim.left_right
), ),
SWIPE_LARGER(
"action.larger",
R.string.settings_gesture_swipe_larger,
R.string.settings_gesture_description_swipe_larger,
R.array.no_default
),
SWIPE_LARGER_REVERSE(
"action.larger_reverse",
R.string.settings_gesture_swipe_larger_reverse,
R.string.settings_gesture_description_swipe_larger_reverse,
R.array.no_default
),
SWIPE_SMALLER(
"action.smaller",
R.string.settings_gesture_swipe_smaller,
R.string.settings_gesture_description_swipe_smaller,
R.array.no_default
),
SWIPE_SMALLER_REVERSE(
"action.smaller_reverse",
R.string.settings_gesture_swipe_smaller_reverse,
R.string.settings_gesture_description_swipe_smaller_reverse,
R.array.no_default
),
SWIPE_LAMBDA(
"action.lambda",
R.string.settings_gesture_swipe_lambda,
R.string.settings_gesture_description_swipe_lambda,
R.array.no_default
),
SWIPE_LAMBDA_REVERSE(
"action.lambda_reverse",
R.string.settings_gesture_swipe_lambda_reverse,
R.string.settings_gesture_description_swipe_lambda_reverse,
R.array.no_default
),
SWIPE_V(
"action.v",
R.string.settings_gesture_swipe_v,
R.string.settings_gesture_description_swipe_v,
R.array.no_default
),
SWIPE_V_REVERSE(
"action.v_reverse",
R.string.settings_gesture_swipe_v_reverse,
R.string.settings_gesture_description_swipe_v_reverse,
R.array.no_default
),
BACK( BACK(
"action.back", "action.back",
R.string.settings_gesture_back, R.string.settings_gesture_back,
@ -267,6 +316,7 @@ enum class Gesture(
} }
operator fun invoke(context: Context) { operator fun invoke(context: Context) {
Log.i("Launcher", "Detected gesture: $this")
val action = Action.forGesture(this) val action = Action.forGesture(this)
Action.launch(action, context, this.animationIn, this.animationOut) Action.launch(action, context, this.animationIn, this.animationOut)
} }

View file

@ -6,14 +6,11 @@ import android.content.res.Resources
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.util.DisplayMetrics import android.util.DisplayMetrics
import android.view.GestureDetector
import android.view.KeyEvent import android.view.KeyEvent
import android.view.MotionEvent import android.view.MotionEvent
import android.view.View import android.view.View
import android.view.ViewConfiguration
import android.window.OnBackInvokedDispatcher import android.window.OnBackInvokedDispatcher
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.GestureDetectorCompat
import androidx.core.view.isVisible import androidx.core.view.isVisible
import de.jrpie.android.launcher.R import de.jrpie.android.launcher.R
import de.jrpie.android.launcher.actions.Action import de.jrpie.android.launcher.actions.Action
@ -23,13 +20,6 @@ import de.jrpie.android.launcher.databinding.HomeBinding
import de.jrpie.android.launcher.preferences.LauncherPreferences import de.jrpie.android.launcher.preferences.LauncherPreferences
import de.jrpie.android.launcher.ui.tutorial.TutorialActivity import de.jrpie.android.launcher.ui.tutorial.TutorialActivity
import java.util.Locale import java.util.Locale
import java.util.Timer
import kotlin.concurrent.fixedRateTimer
import kotlin.math.abs
import kotlin.math.max
import kotlin.math.min
import kotlin.math.tan
/** /**
* [HomeActivity] is the actual application Launcher, * [HomeActivity] is the actual application Launcher,
@ -43,10 +33,10 @@ import kotlin.math.tan
* - Setting global variables (preferences etc.) * - Setting global variables (preferences etc.)
* - Opening the [TutorialActivity] on new installations * - Opening the [TutorialActivity] on new installations
*/ */
class HomeActivity : UIObject, AppCompatActivity(), class HomeActivity : UIObject, AppCompatActivity() {
GestureDetector.OnGestureListener, GestureDetector.OnDoubleTapListener {
private lateinit var binding: HomeBinding private lateinit var binding: HomeBinding
private lateinit var touchGestureDetector: TouchGestureDetector
private var sharedPreferencesListener = private var sharedPreferencesListener =
SharedPreferences.OnSharedPreferenceChangeListener { _, prefKey -> SharedPreferences.OnSharedPreferenceChangeListener { _, prefKey ->
@ -61,22 +51,29 @@ class HomeActivity : UIObject, AppCompatActivity(),
} }
} }
private var edgeWidth = 0.15f
private var bufferedPointerCount = 1 // how many fingers on screen
private var pointerBufferTimer = Timer()
private lateinit var mDetector: GestureDetectorCompat
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super<AppCompatActivity>.onCreate(savedInstanceState) super<AppCompatActivity>.onCreate(savedInstanceState)
super<UIObject>.onCreate() super<UIObject>.onCreate()
val displayMetrics = DisplayMetrics()
windowManager.defaultDisplay.getMetrics(displayMetrics)
val width = displayMetrics.widthPixels
val height = displayMetrics.heightPixels
touchGestureDetector = TouchGestureDetector(
this,
width,
height,
LauncherPreferences.enabled_gestures().edgeSwipeEdgeWidth() / 100f
)
// Initialise layout // Initialise layout
binding = HomeBinding.inflate(layoutInflater) binding = HomeBinding.inflate(layoutInflater)
setContentView(binding.root) setContentView(binding.root)
// Handle back key / gesture on Android 13+, cf. onKeyDown() // Handle back key / gesture on Android 13+, cf. onKeyDown()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
onBackInvokedDispatcher.registerOnBackInvokedCallback( onBackInvokedDispatcher.registerOnBackInvokedCallback(
@ -95,9 +92,6 @@ class HomeActivity : UIObject, AppCompatActivity(),
override fun onStart() { override fun onStart() {
super<AppCompatActivity>.onStart() super<AppCompatActivity>.onStart()
mDetector = GestureDetectorCompat(this, this)
mDetector.setOnDoubleTapListener(this)
super<UIObject>.onStart() super<UIObject>.onStart()
LauncherPreferences.getSharedPreferences() LauncherPreferences.getSharedPreferences()
@ -172,7 +166,8 @@ class HomeActivity : UIObject, AppCompatActivity(),
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
edgeWidth = LauncherPreferences.enabled_gestures().edgeSwipeEdgeWidth() / 100f touchGestureDetector.edgeWidth =
LauncherPreferences.enabled_gestures().edgeSwipeEdgeWidth() / 100f
initClock() initClock()
updateSettingsFallbackButtonVisibility() updateSettingsFallbackButtonVisibility()
@ -211,95 +206,8 @@ class HomeActivity : UIObject, AppCompatActivity(),
return true return true
} }
override fun onFling(e1: MotionEvent?, e2: MotionEvent, dX: Float, dY: Float): Boolean {
if (e1 == null) return false
val displayMetrics = DisplayMetrics()
windowManager.defaultDisplay.getMetrics(displayMetrics)
val width = displayMetrics.widthPixels
val height = displayMetrics.heightPixels
val diffX = e1.x - e2.x
val diffY = e1.y - e2.y
val doubleActions = LauncherPreferences.enabled_gestures().doubleSwipe()
val edgeActions = LauncherPreferences.enabled_gestures().edgeSwipe()
val threshold = ViewConfiguration.get(this).scaledTouchSlop
val angularThreshold = tan(Math.PI / 6)
var gesture = if (angularThreshold * abs(diffX) > abs(diffY)) { // horizontal swipe
if (diffX > threshold)
Gesture.SWIPE_LEFT
else if (diffX < -threshold)
Gesture.SWIPE_RIGHT
else null
} else if (angularThreshold * abs(diffY) > abs(diffX)) { // vertical swipe
// Only open if the swipe was not from the phones top edge
// TODO: replace 100px by sensible dp value (e.g. twice the height of the status bar)
if (diffY < -threshold && e1.y > 100)
Gesture.SWIPE_DOWN
else if (diffY > threshold)
Gesture.SWIPE_UP
else null
} else null
if (doubleActions && bufferedPointerCount > 1) {
gesture = gesture?.let(Gesture::getDoubleVariant)
}
if (edgeActions) {
if (max(e1.x, e2.x) < edgeWidth * width) {
gesture = gesture?.getEdgeVariant(Gesture.Edge.LEFT)
} else if (min(e1.x, e2.x) > (1 - edgeWidth) * width) {
gesture = gesture?.getEdgeVariant(Gesture.Edge.RIGHT)
}
if (max(e1.y, e2.y) < edgeWidth * height) {
gesture = gesture?.getEdgeVariant(Gesture.Edge.TOP)
} else if (min(e1.y, e2.y) > (1 - edgeWidth) * height) {
gesture = gesture?.getEdgeVariant(Gesture.Edge.BOTTOM)
}
}
gesture?.invoke(this)
return true
}
override fun onLongPress(event: MotionEvent) {
Gesture.LONG_CLICK(this)
}
override fun onDoubleTap(event: MotionEvent): Boolean {
Gesture.DOUBLE_CLICK(this)
return false
}
// Tooltip
override fun onSingleTapConfirmed(event: MotionEvent): Boolean {
return false
}
override fun onTouchEvent(event: MotionEvent): Boolean { override fun onTouchEvent(event: MotionEvent): Boolean {
return touchGestureDetector.onTouchEvent(event) || super.onTouchEvent(event)
// Buffer / Debounce the pointer count
if (event.pointerCount > bufferedPointerCount) {
bufferedPointerCount = event.pointerCount
pointerBufferTimer = fixedRateTimer("pointerBufferTimer", true, 300, 1000) {
bufferedPointerCount = 1
this.cancel() // a non-recurring timer
}
}
return if (mDetector.onTouchEvent(event)) {
false
} else {
super.onTouchEvent(event)
}
} }
override fun setOnClicks() { override fun setOnClicks() {
@ -329,16 +237,4 @@ class HomeActivity : UIObject, AppCompatActivity(),
override fun isHomeScreen(): Boolean { override fun isHomeScreen(): Boolean {
return true return true
} }
/* TODO: Remove those. For now they are necessary
* because this inherits from GestureDetector.OnGestureListener */
override fun onDoubleTapEvent(event: MotionEvent): Boolean { return false }
override fun onDown(event: MotionEvent): Boolean { return false }
override fun onScroll(e1: MotionEvent?, e2: MotionEvent, dX: Float, dY: Float): Boolean { return false }
override fun onShowPress(event: MotionEvent) {}
override fun onSingleTapUp(event: MotionEvent): Boolean { return false }
} }

View file

@ -0,0 +1,241 @@
package de.jrpie.android.launcher.ui
import android.content.Context
import android.view.MotionEvent
import android.view.ViewConfiguration
import de.jrpie.android.launcher.actions.Gesture
import de.jrpie.android.launcher.preferences.LauncherPreferences
import kotlin.math.abs
import kotlin.math.max
import kotlin.math.min
import kotlin.math.tan
class TouchGestureDetector(
private val context: Context,
val width: Int,
val height: Int,
var edgeWidth: Float
) {
private val ANGULAR_THRESHOLD = tan(Math.PI / 6)
private val TOUCH_SLOP: Int
private val TOUCH_SLOP_SQUARE: Int
private val DOUBLE_TAP_SLOP: Int
private val DOUBLE_TAP_SLOP_SQUARE: Int
private val LONG_PRESS_TIMEOUT: Int
private val TAP_TIMEOUT: Int
private val DOUBLE_TAP_TIMEOUT: Int
private val MIN_TRIANGLE_HEIGHT = 250
data class Vector(val x: Float, val y: Float) {
fun absSquared(): Float {
return this.x * this.x + this.y * this.y
}
fun plus(vector: Vector): Vector {
return Vector(this.x + vector.x, this.y + vector.y)
}
fun max(other: Vector): Vector {
return Vector(max(this.x, other.x), max(this.y, other.y))
}
fun min(other: Vector): Vector {
return Vector(min(this.x, other.x), min(this.y, other.y))
}
operator fun minus(vector: Vector): Vector {
return Vector(this.x - vector.x, this.y - vector.y)
}
}
class PointerPath(
val number: Int,
val start: Vector,
var last: Vector = start
) {
var min = Vector(Float.POSITIVE_INFINITY, Float.POSITIVE_INFINITY)
var max = Vector(Float.NEGATIVE_INFINITY, Float.NEGATIVE_INFINITY)
fun sizeSquared(): Float {
return (max - min).absSquared()
}
fun getDirection(): Vector {
return last - start
}
fun update(vector: Vector) {
min = min.min(vector)
max = max.max(vector)
last = vector
}
}
private fun PointerPath.isTap(): Boolean {
return sizeSquared() < TOUCH_SLOP_SQUARE
}
init {
val configuration = ViewConfiguration.get(context)
TOUCH_SLOP = configuration.scaledTouchSlop
TOUCH_SLOP_SQUARE = TOUCH_SLOP * TOUCH_SLOP
DOUBLE_TAP_SLOP = configuration.scaledDoubleTapSlop
DOUBLE_TAP_SLOP_SQUARE = DOUBLE_TAP_SLOP * DOUBLE_TAP_SLOP
LONG_PRESS_TIMEOUT = ViewConfiguration.getLongPressTimeout()
TAP_TIMEOUT = ViewConfiguration.getTapTimeout()
DOUBLE_TAP_TIMEOUT = ViewConfiguration.getDoubleTapTimeout()
}
private var paths = HashMap<Int, PointerPath>()
private var lastTappedTime = 0L
private var lastTappedLocation: Vector? = null
fun onTouchEvent(event: MotionEvent): Boolean {
val pointerIdToIndex =
(0..<event.pointerCount).associateBy { event.getPointerId(it) }
if (event.actionMasked == MotionEvent.ACTION_DOWN) {
paths = HashMap()
}
// add new pointers
for(i in 0..<event.pointerCount){
if(paths.containsKey(event.getPointerId(i))) {
continue
}
val index = pointerIdToIndex[i] ?: continue
paths[i] = PointerPath(
paths.entries.size,
Vector(event.getX(index), event.getY(index))
)
}
for( i in 0..<event.pointerCount) {
val index = pointerIdToIndex[i] ?: continue
repeat(event.historySize) {
paths[i]?.update(Vector(event.getHistoricalX(index), event.getHistoricalY(index)))
}
paths[i]?.update(Vector(event.getX(index), event.getY(index)))
}
if (event.actionMasked == MotionEvent.ACTION_UP) {
classifyPaths(paths, event.downTime, event.eventTime)
}
return true
}
private fun getGestureForDirection(direction: Vector): Gesture? {
return if (ANGULAR_THRESHOLD * abs(direction.x) > abs(direction.y)) { // horizontal swipe
if (direction.x > TOUCH_SLOP)
Gesture.SWIPE_RIGHT
else if (direction.x < -TOUCH_SLOP)
Gesture.SWIPE_LEFT
else null
} else if (ANGULAR_THRESHOLD * abs(direction.y) > abs(direction.x)) { // vertical swipe
if (direction.y < -TOUCH_SLOP)
Gesture.SWIPE_UP
else if (direction.y > TOUCH_SLOP)
Gesture.SWIPE_DOWN
else null
} else null
}
private fun classifyPaths(paths: Map<Int, PointerPath>, timeStart: Long, timeEnd: Long) {
val duration = timeEnd - timeStart
val pointerCount = paths.entries.size
if (paths.entries.isEmpty()) {
return
}
val mainPointerPath = paths.entries.firstOrNull { it.value.number == 0 }?.value ?: return
// Ignore swipes at the very top, since this interferes with the status bar.
// TODO: replace 100px by sensible dp value (e.g. twice the height of the status bar)
if (paths.entries.any { it.value.start.y < 100 }) {
return
}
if (pointerCount == 1 && mainPointerPath.isTap()) {
// detect taps
if (duration in 0..TAP_TIMEOUT) {
if (timeStart - lastTappedTime < DOUBLE_TAP_TIMEOUT &&
lastTappedLocation?.let {
(mainPointerPath.last - it).absSquared() < DOUBLE_TAP_SLOP_SQUARE} == true
) {
Gesture.DOUBLE_CLICK.invoke(context)
} else {
lastTappedTime = timeEnd
lastTappedLocation = mainPointerPath.last
}
} else if (duration > LONG_PRESS_TIMEOUT) {
// TODO: Don't wait until the finger is lifted.
// Instead set a timer to start long click as soon as LONG_PRESS_TIMEOUT is reached
Gesture.LONG_CLICK.invoke(context)
}
} else {
// detect swipes
val doubleActions = LauncherPreferences.enabled_gestures().doubleSwipe()
val edgeActions = LauncherPreferences.enabled_gestures().edgeSwipe()
var gesture = getGestureForDirection(mainPointerPath.getDirection())
if (doubleActions && pointerCount > 1) {
if (paths.entries.any { getGestureForDirection(it.value.getDirection()) != gesture }) {
// the directions of the pointers don't match
return
}
gesture = gesture?.let(Gesture::getDoubleVariant)
}
// detect triangles
val startEndMin = mainPointerPath.start.min(mainPointerPath.last)
val startEndMax = mainPointerPath.start.max(mainPointerPath.last)
when (gesture) {
Gesture.SWIPE_DOWN -> {
if(startEndMax.x + MIN_TRIANGLE_HEIGHT < mainPointerPath.max.x) {
gesture = Gesture.SWIPE_LARGER
} else if (startEndMin.x - MIN_TRIANGLE_HEIGHT > mainPointerPath.min.x) {
gesture = Gesture.SWIPE_SMALLER
}
}
Gesture.SWIPE_UP -> {
if(startEndMax.x + MIN_TRIANGLE_HEIGHT < mainPointerPath.max.x) {
gesture = Gesture.SWIPE_LARGER_REVERSE
} else if (startEndMin.x - MIN_TRIANGLE_HEIGHT > mainPointerPath.min.x) {
gesture = Gesture.SWIPE_SMALLER_REVERSE
}
}
Gesture.SWIPE_RIGHT -> {
if(startEndMax.y + MIN_TRIANGLE_HEIGHT < mainPointerPath.max.y) {
gesture = Gesture.SWIPE_V
} else if (startEndMin.y - MIN_TRIANGLE_HEIGHT > mainPointerPath.min.y) {
gesture = Gesture.SWIPE_LAMBDA
}
}
Gesture.SWIPE_LEFT -> {
if(startEndMax.y + MIN_TRIANGLE_HEIGHT < mainPointerPath.max.y) {
gesture = Gesture.SWIPE_V_REVERSE
} else if (startEndMin.y - MIN_TRIANGLE_HEIGHT > mainPointerPath.min.y) {
gesture = Gesture.SWIPE_LAMBDA_REVERSE
}
}
else -> { }
}
if (edgeActions) {
if (mainPointerPath.max.x < edgeWidth * width) {
gesture = gesture?.getEdgeVariant(Gesture.Edge.LEFT)
} else if (mainPointerPath.min.x > (1 - edgeWidth) * width) {
gesture = gesture?.getEdgeVariant(Gesture.Edge.RIGHT)
}
if (mainPointerPath.max.y < edgeWidth * height) {
gesture = gesture?.getEdgeVariant(Gesture.Edge.TOP)
} else if (mainPointerPath.min.y > (1 - edgeWidth) * height) {
gesture = gesture?.getEdgeVariant(Gesture.Edge.BOTTOM)
}
}
gesture?.invoke(context)
}
}
}

View file

@ -2,6 +2,8 @@
<resources> <resources>
<!-- Default Apps for different actions (button-press, swipes ...) --> <!-- Default Apps for different actions (button-press, swipes ...) -->
<string-array name="no_default">
</string-array>
<!-- Back - Apps list --> <!-- Back - Apps list -->
<string-array name="default_back"> <string-array name="default_back">

View file

@ -59,6 +59,24 @@
<string name="settings_gesture_description_down_left_edge">Swipe down at the left edge of the screen</string> <string name="settings_gesture_description_down_left_edge">Swipe down at the left edge of the screen</string>
<string name="settings_gesture_down_right_edge">Down (Right Edge)</string> <string name="settings_gesture_down_right_edge">Down (Right Edge)</string>
<string name="settings_gesture_description_down_right_edge">Swipe down at the right edge of the screen</string> <string name="settings_gesture_description_down_right_edge">Swipe down at the right edge of the screen</string>
<string name="settings_gesture_swipe_larger"><![CDATA[>]]></string>
<string name="settings_gesture_description_swipe_larger">Top left -> mid right -> bottom left</string>
<string name="settings_gesture_swipe_larger_reverse"><![CDATA[> (reverse)]]></string>
<string name="settings_gesture_description_swipe_larger_reverse">Bottom left -> mid right -> top left</string>
<string name="settings_gesture_swipe_smaller"><![CDATA[<]]></string>
<string name="settings_gesture_description_swipe_smaller">Top right -> mid left -> bottom right</string>
<string name="settings_gesture_swipe_smaller_reverse"><![CDATA[< (reverse)]]></string>
<string name="settings_gesture_description_swipe_smaller_reverse">Bottom right -> mid left -> top right</string>
<string name="settings_gesture_swipe_v">V</string>
<string name="settings_gesture_description_swipe_v">Top left -> bottom mid -> top right</string>
<string name="settings_gesture_swipe_v_reverse">V (reverse)</string>
<string name="settings_gesture_description_swipe_v_reverse">Top right -> bottom mid -> top left</string>
<string name="settings_gesture_swipe_lambda">Λ</string>
<string name="settings_gesture_description_swipe_lambda">Bottom left -> top mid -> bottom right</string>
<string name="settings_gesture_swipe_lambda_reverse">Λ (reverse)</string>
<string name="settings_gesture_description_swipe_lambda_reverse">Bottom right -> top mid -> bottom left</string>
<string name="settings_gesture_vol_up">Volume Up</string> <string name="settings_gesture_vol_up">Volume Up</string>
<string name="settings_gesture_description_vol_up">Press the volume up button</string> <string name="settings_gesture_description_vol_up">Press the volume up button</string>
<string name="settings_gesture_vol_down">Volume Down</string> <string name="settings_gesture_vol_down">Volume Down</string>

49
docs/launcher.md Normal file
View file

@ -0,0 +1,49 @@
# Notable changes compared to [Finn's Launcher][original-repo]:
µLauncher is a fork of [finnmglas's app Launcher][original-repo].
Here is an incomplete list of changes:
<!--The last commit of the original project is [340ee731](https://github.com/jrpie/launcher/commit/340ee7315293b028c060638e058516435bca296a)
The first commit of µLauncher is [cc2e7710](https://github.com/jrpie/launcher/commit/cc2e7710c824549c367d97a81a1646d27c6c8993),
which at the time was still intended as a patch for launcher.
The decision to create a hard fork was made two years later.-->
- Additional gestures:
- Back
- V,Λ,<,>
- Edge gestures: There is a setting to allow distinguishing swiping at the edges of the screen from swiping in the center.
- Compatible with [work profile](https://www.android.com/enterprise/work-profile/), so apps like [Shelter](https://gitea.angry.im/PeterCxy/Shelter) can be used.
- Compatible with [private space](https://source.android.com/docs/security/features/private-space)
- Option to rename apps
- Option to hide apps
- Favorite apps
- New actions:
- Toggle Torch
- Lock screen
- The home button now works as expected.
- Improved gesture detection.
### Visual
- This app uses the system wallpaper instead of a custom solution.
- The font has been changed to [Hack][hack-font], other fonts can be selected.
- Font Awesome Icons were replaced by Material icons.
- The gear button on the home screen was removed. A smaller button is show at the top right when necessary.
### Search
- The search algorithm was modified to prefer matches at the beginning of the app name, i.e. when searching for `"te"`, `"termux"` is sorted before `"notes"`.
- The search bar was moved to the bottom of the screen.
### Technical
- Improved gesture detection.
- Different apps set as default.
- Package name was changed to `de.jrpie.android.launcher` to avoid clashing with the original app.
- Dropped support for API < 21 (i.e. pre Lollypop)
- Fixed some bugs
- Some refactoring
The complete list of changes can be viewed [here](https://github.com/jrpie/launcher/compare/340ee731...master).
---
[original-repo]: https://github.com/finnmglas/Launcher