Compare commits

..

14 commits

Author SHA1 Message Date
Ahmet Çeliker
c448c51164 Translated using Weblate (Turkish)
Currently translated at 86.6% (13 of 15 strings)

Translation: jrpie-Launcher/metadata
Translate-URL: https://toolate.othing.xyz/projects/jrpie-launcher/metadata/tr/
2025-02-17 00:07:18 +00:00
Vossa Excelencia
68b79724e8 Translated using Weblate (Portuguese (Brazil))
Currently translated at 26.6% (4 of 15 strings)

Translation: jrpie-Launcher/metadata
Translate-URL: https://toolate.othing.xyz/projects/jrpie-launcher/metadata/pt_BR/
2025-02-17 00:07:18 +00:00
Vossa Excelencia
bef38c2657 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (225 of 225 strings)

Translation: jrpie-Launcher/Launcher
Translate-URL: https://toolate.othing.xyz/projects/jrpie-launcher/launcher/pt_BR/
2025-02-17 00:07:18 +00:00
Vossa Excelencia
d0b0c27b2c Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (220 of 220 strings)

Translation: jrpie-Launcher/Launcher
Translate-URL: https://toolate.othing.xyz/projects/jrpie-launcher/launcher/pt_BR/
2025-02-17 00:07:18 +00:00
Vossa Excelencia
4508e4ee5c Translated using Weblate (Portuguese (Brazil))
Currently translated at 20.0% (3 of 15 strings)

Translation: jrpie-Launcher/metadata
Translate-URL: https://toolate.othing.xyz/projects/jrpie-launcher/metadata/pt_BR/
2025-02-17 00:07:18 +00:00
Vossa Excelencia
e959e9d957 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (220 of 220 strings)

Translation: jrpie-Launcher/Launcher
Translate-URL: https://toolate.othing.xyz/projects/jrpie-launcher/launcher/pt_BR/
2025-02-17 00:07:18 +00:00
Nicola Bortoletto
958d4879f5 Translated using Weblate (Italian)
Currently translated at 99.0% (216 of 218 strings)

Translation: jrpie-Launcher/Launcher
Translate-URL: https://toolate.othing.xyz/projects/jrpie-launcher/launcher/it/
2025-02-17 00:07:18 +00:00
Vossa Excelencia
7841a99415 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (218 of 218 strings)

Translation: jrpie-Launcher/Launcher
Translate-URL: https://toolate.othing.xyz/projects/jrpie-launcher/launcher/pt_BR/
2025-02-17 00:07:18 +00:00
Xanadul
18b4fca933 Translated using Weblate (German)
Currently translated at 13.3% (2 of 15 strings)

Translation: jrpie-Launcher/metadata
Translate-URL: https://toolate.othing.xyz/projects/jrpie-launcher/metadata/de/
2025-02-17 00:07:18 +00:00
Nicola Bortoletto
5792c7f38c Translated using Weblate (Italian)
Currently translated at 98.1% (214 of 218 strings)

Translation: jrpie-Launcher/Launcher
Translate-URL: https://toolate.othing.xyz/projects/jrpie-launcher/launcher/it/
2025-02-17 00:07:18 +00:00
7257d4ca35
fix bug in gesture detection logic
Some checks are pending
Android CI / build (push) Waiting to run
2025-02-16 23:58:42 +01:00
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
21 changed files with 509 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,246 @@
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
for (j in 0..<event.historySize) {
paths[i]?.update(
Vector(
event.getHistoricalX(index, j),
event.getHistoricalY(index, j)
)
)
}
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

View file

@ -0,0 +1,15 @@
- Çince çeviri (teşekkürler, yzqzss!)
- Fransızca çeviri iyileştirildi (teşekkürler, toby-bro!)
- Almanca çeviri iyileştirildi
Tüm Uygulamalar:
- Uygulama listesindeki üç nokta kaldırıldı (bunun yerine uzun tıklama kullanın)
- Enter'a basıldığında sorguyla eşleşen ilk uygulama açılıyor
- Klavyeyi tam ekran modunda açarken oluşan hata için geçici çözüm düzeltildi
- Sistem uygulamaları için kaldırma seçeneği kaldırıldı
- Ana Sayfa Düğmesi artık düzgün çalışıyor
Ayarlar:
- Küçük ekranlar için ayarlar düzeltildi
- Hassasiyet ayarı kaldırıldı (herkes zaten maksimuma ayarlıyordu)
- Tarih ve saat ayarları yeniden düzenlendi

View file

@ -0,0 +1,8 @@
* Renk teması, yazı tipi, arka plan, monokrom simgeler için seçenekler eklendi
* Tarih ve saat için seçenekler eklendi
* Döndürmeye izin verme seçeneği eklendi
* İyileştirilmiş arama algoritması
* Brezilya Portekizcesine çeviri - teşekkürler, Jonatas de Almeida Barros!
* Ayarlardan seçilen uygulamaların kaybolmasına neden olan bir hata düzeltildi
* İyileştirilmiş kod kalitesi
* Güncellenmiş çeviriler

View file

@ -0,0 +1,4 @@
* Favori uygulamalar
* Uygulamaları gizleme seçeneği
* Birden fazla ana aktiviteye sahip uygulamalar için destek
* Küçük ekranlarda ayarların düzeni düzeltildi

View file

@ -0,0 +1 @@
hata düzeltme

View file

@ -0,0 +1,9 @@
* Matrix ve Discord'da sohbet
* Hareket algılama iyileştirildi
* Hareket açıklamaları iyileştirildi
* Yeni eylem: Ekranı kilitle - teşekkürler, yzqzss!
* Yeni eylem: Meşaleyi aç
* Yeni eylem: Hızlı ayarları
* Fransızca çevirisi iyileştirildi - teşekkürler, toby-bro!
* Portekizce çevirisi iyileştirildi - teşekkürler, "Vossa Excelencia"!
* Bazı hatalar düzeltildi

View file

@ -0,0 +1 @@
Ekranı kilitlemek için Cihaz Yöneticisi yerine Erişilebilirlik Hizmetini kullanma seçeneği eklendi.

View file

@ -0,0 +1,11 @@
* Uygulamaları yeniden adlandırma seçeneği
* Uygulama listesinden hareketlere bağlı uygulamaları otomatik olarak gizleme seçeneği
* Uygulama listesinden µLauncher'ı varsayılan olarak gizle
* Varsayılan uygulamaların seçimi iyileştirildi
* Açık tema (deneysel)
* Aramadaki hata düzeltildi
* Kilit ekranı iletişim kutusundaki hata düzeltildi (teşekkürler, yzqzss ve jeroen!)
* Portekizce çeviri güncellendi (teşekkürler, "Vossa Excelencia"!)
* Almanca çeviri güncellendi

View file

@ -0,0 +1,5 @@
* Uygulama listesi için alternatif düzenler (ızgara, metin)
* Hata düzeltildi: Uygulamaların yeniden adlandırılması artık düzgün çalışıyor
* Çince çeviri güncellendi (teşekkürler, yzqzss!)
* Portekizce çeviri güncellendi (teşekkürler, "Vossa Excelencia"!)

View file

@ -0,0 +1,9 @@
* Saat rengini seçme seçeneği
* Dinamik renk teması eklendi; açık tema kaldırıldı
* Erişilebilirlik hizmeti için onay iletişim kutusu eklendi
* Türkçe çeviri eklendi (teşekkürler, Ahmet Çeliker!)
* İtalyanca çeviri eklendi (teşekkürler, Samantha!)
* Portekizce çeviri iyileştirildi (teşekkürler, "Vossa Excelencia"!)
* Uygulama çekmecesinde hareketle gezinmeyle ilgili hata düzeltildi. Artık geri basıldığında çekmece hemen kapanıyor.

View file

@ -0,0 +1,6 @@
* Ayarlar için depolama biçimi yeniden düzenlendi. Eski biçim otomatik olarak dönüştürülecek.
* Uygulama listesinden web'de arama yapma seçeneği eklendi.
* Ekranı kilitlemek için tercih edilen yöntem olarak cihaz yöneticisini ayarlayın.
* Bir erişilebilirlik hizmetini etkinleştirmenin şifrelemeyle çakışabileceğine dair bir uyarı eklendi.
* Yeniden adlandırma iletişim kutusundaki bir hata düzeltildi.
* Kilit ekranı iletişim kutusu kaydırılabilir hale getirildi.

View file

@ -0,0 +1,6 @@
* yeni özellik: otomatik başlatmayı geçici olarak devre dışı bırakmak için boşluk içeren önek sorgusu
* iyileştirilmiş arama: sorguda görünmedikleri sürece diakritik işaretler artık yok sayılıyor. (Android 7+)
* açık kaynak lisanslarının listesi eklendi
* erişilebilirlik hizmeti uyarısı güncellendi
* iyileştirilmiş Fransızca çeviri (teşekkürler, Alexandre Ancel ve Nin Dan!)
* iyileştirilmiş Portekizce çeviri (teşekkürler, "Vossa Excelencia"!)

View file

@ -0,0 +1,8 @@
* private space için temel destek (Android 15)
* light tema yeniden tanıtıldı
* Portekizce çeviri iyileştirildi (teşekkürler, "Vossa Excelencia"!)
* Almanca çeviri iyileştirildi
* ayarlara sürüm adı eklendi
* hata raporları için iletişim kutusu
* saat performansı iyileştirildi
* bazı hatalar düzeltildi

View file

@ -0,0 +1 @@
µBaşlatıcı