Skip to content

Commit 90e6919

Browse files
authored
Merge branch 'LawnchairLauncher:15-dev' into trunk
2 parents 3250eb6 + 964d86e commit 90e6919

22 files changed

+869
-193
lines changed

lawnchair/AndroidManifest.xml

+1
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
<uses-permission android:name="android.permission.FORCE_STOP_PACKAGES" tools:ignore="ProtectedPermissions"/>
3636
<uses-permission android:name="android.permission.STATUS_BAR_SERVICE" tools:ignore="ProtectedPermissions"/>
3737
<uses-permission android:name="android.permission.SUSPEND_APPS" tools:ignore="ProtectedPermissions" />
38+
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/>
3839

3940
<!--override minSdk declared in it-->
4041
<uses-sdk tools:overrideLibrary="com.kieronquinn.app.smartspacer.sdk" />

lawnchair/res/values/config.xml

+7
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,13 @@
9999
<!-- which overlay to use by default -->
100100
<string name="config_default_overlay" translatable="false">suck_in</string>
101101

102+
103+
<!-- swipe gesture key -->
104+
<string name="pref_key_swipe_up" translatable="false">pref_swipe_up</string>
105+
<string name="pref_key_swipe_down" translatable="false">pref_swipe_down</string>
106+
<string name="pref_key_swipe_right" translatable="false">pref_swipe_right</string>
107+
<string name="pref_key_swipe_left" translatable="false">pref_swipe_left</string>
108+
102109
<bool name="config_default_show_hotseat">true</bool>
103110
<bool name="config_default_always_reload_icons">true</bool>
104111
<bool name="config_default_dark_status_bar">false</bool>

lawnchair/res/values/strings.xml

+12
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,16 @@
4545
<string name="action_paste">Paste</string>
4646
<string name="action_done">Done</string>
4747

48+
<string name="select_all">Select all</string>
49+
<string name="deselect_all">Deselect all</string>
50+
<string name="inverse_selection">Inverse selection</string>
51+
4852
<string name="loading">Loading…</string>
4953

54+
<string name="download_update">Download update</string>
55+
<string name="install_update">Install update</string>
56+
<string name="pro_updated">You\'re up-to-date!</string>
57+
5058
<string name="managed_by_lawnchair">Managed by Lawnchair</string>
5159

5260
<!-- When mentioning settings UI -->
@@ -109,6 +117,7 @@
109117
<string name="apps_in_folder_label">Hide folder apps</string>
110118
<string name="apps_in_folder_description">Apps assigned to folders are excluded from app lists</string>
111119

120+
<string name="folders_filter_duplicates">Only show unique apps</string>
112121
<string name="my_folder_label">My folder</string>
113122

114123
<!-- A11y description -->
@@ -117,6 +126,7 @@
117126
<string name="iconPackPackageDefault" translatable="false">""</string>
118127

119128
<string name="n_percent" translatable="false">%1$d%%</string>
129+
<string name="x_with_y_count">%1$s (%2$d)</string>
120130
<string name="x_by_y">%1$d x %2$d</string>
121131
<string name="x_and_y">%1$s &amp; %2$s</string>
122132

@@ -466,6 +476,8 @@
466476
<string name="gesture_swipe_down">Swipe down</string>
467477
<string name="gesture_home_tap">Home button</string>
468478
<string name="gesture_back_tap">Back button</string>
479+
<string name="gesture_swipe_left">Swipe left</string>
480+
<string name="gesture_swipe_right">Swipe right</string>
469481

470482
<string name="gesture_handler_no_op">Do nothing</string>
471483
<string name="gesture_handler_sleep">Sleep</string>

lawnchair/src/app/lawnchair/allapps/LawnchairAlphabeticalAppsList.kt

+2-2
Original file line numberDiff line numberDiff line change
@@ -98,11 +98,11 @@ class LawnchairAlphabeticalAppsList<T>(
9898
}
9999
} else {
100100
folderList.forEach { folder ->
101-
if (folder.contents.size > 1) {
101+
if (folder.getContents().size > 1) {
102102
val folderInfo = FolderInfo()
103103
folderInfo.title = folder.title
104104
mAdapterItems.add(AdapterItem.asFolder(folderInfo))
105-
folder.contents.forEach { app ->
105+
folder.getContents().forEach { app ->
106106
(appsStore.getApp(app.componentKey) as? AppInfo)?.let {
107107
folderInfo.add(it)
108108
if (prefs.folderApps.get()) filteredList.add(it)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
package app.lawnchair.api.gh
2+
3+
import app.lawnchair.util.kotlinxJson
4+
import kotlinx.serialization.Serializable
5+
import okhttp3.MediaType.Companion.toMediaType
6+
import retrofit2.Retrofit
7+
import retrofit2.converter.kotlinx.serialization.asConverterFactory
8+
import retrofit2.http.GET
9+
import retrofit2.http.Path
10+
11+
interface GitHubApi {
12+
@GET("repos/LawnchairLauncher/lawnchair/releases")
13+
suspend fun getReleases(): List<GitHubRelease>
14+
15+
@GET("repos/{owner}/{repo}/events")
16+
suspend fun getRepositoryEvents(
17+
@Path("owner") owner: String,
18+
@Path("repo") repo: String,
19+
): List<GitHubEvent>
20+
}
21+
22+
const val BASE_URL = "https://api.github.com/"
23+
24+
val retrofit: Retrofit by lazy {
25+
Retrofit.Builder()
26+
.baseUrl(BASE_URL)
27+
.addConverterFactory(kotlinxJson.asConverterFactory("application/json".toMediaType()))
28+
.build()
29+
}
30+
31+
val api: GitHubApi by lazy {
32+
retrofit.create(GitHubApi::class.java)
33+
}
34+
35+
@Serializable
36+
data class GitHubRelease(
37+
val tag_name: String,
38+
val assets: List<GitHubAsset>,
39+
)
40+
41+
@Serializable
42+
data class GitHubAsset(
43+
val name: String,
44+
val browser_download_url: String,
45+
)
46+
47+
@Serializable
48+
data class GitHubEvent(
49+
val type: String,
50+
val actor: Actor,
51+
val created_at: String,
52+
)
53+
54+
@Serializable
55+
data class Actor(
56+
val login: String,
57+
)

lawnchair/src/app/lawnchair/data/folder/model/FolderViewModel.kt

+18-27
Original file line numberDiff line numberDiff line change
@@ -6,54 +6,44 @@ import androidx.lifecycle.MutableLiveData
66
import androidx.lifecycle.ViewModel
77
import androidx.lifecycle.viewModelScope
88
import app.lawnchair.data.folder.service.FolderService
9+
import app.lawnchair.preferences2.ReloadHelper
910
import com.android.launcher3.model.data.AppInfo
1011
import com.android.launcher3.model.data.FolderInfo
1112
import kotlinx.coroutines.flow.MutableStateFlow
1213
import kotlinx.coroutines.flow.StateFlow
1314
import kotlinx.coroutines.flow.asStateFlow
15+
import kotlinx.coroutines.flow.update
1416
import kotlinx.coroutines.launch
1517
import kotlinx.coroutines.sync.Mutex
1618
import kotlinx.coroutines.sync.withLock
1719

18-
class FolderViewModel(context: Context) : ViewModel() {
19-
20-
private val repository = FolderService.INSTANCE.get(context)
21-
20+
class FolderViewModel(
21+
context: Context,
22+
private val repository: FolderService = FolderService.INSTANCE.get(context),
23+
) : ViewModel() {
2224
private val _folders = MutableStateFlow<List<FolderInfo>>(emptyList())
2325
val folders: StateFlow<List<FolderInfo>> = _folders.asStateFlow()
2426

2527
private val _foldersMutable = MutableLiveData<List<FolderInfo>>()
2628
val foldersMutable: LiveData<List<FolderInfo>> = _foldersMutable
2729

28-
private val _items = MutableStateFlow<Set<String>>(setOf())
29-
val items: StateFlow<Set<String>> = _items.asStateFlow()
30-
3130
private val _folderInfo = MutableStateFlow<FolderInfo?>(null)
3231
val folderInfo = _folderInfo.asStateFlow()
3332

3433
private val mutex = Mutex()
34+
private val reloadHelper = ReloadHelper(context)
3535

3636
init {
37-
viewModelScope.launch {
38-
mutex.withLock {
39-
loadFolders()
40-
}
41-
}
37+
refreshFolders()
4238
}
4339

44-
fun refreshFolders() {
40+
fun refreshFolders(isReloadGrid: Boolean = false) {
4541
viewModelScope.launch {
4642
mutex.withLock {
4743
loadFolders()
4844
}
4945
}
50-
}
51-
52-
fun setItems(id: Int) {
53-
viewModelScope.launch {
54-
val items = repository.getItems(id)
55-
_items.value = items
56-
}
46+
if (isReloadGrid) reloadHelper.reloadGrid()
5747
}
5848

5949
fun setFolderInfo(folderInfoId: Int, hasId: Boolean) {
@@ -66,7 +56,7 @@ class FolderViewModel(context: Context) : ViewModel() {
6656
viewModelScope.launch {
6757
repository.updateFolderInfo(folderInfo, hide)
6858
}
69-
refreshFolders()
59+
refreshFolders(true)
7060
}
7161

7262
fun saveFolder(folderInfo: FolderInfo) {
@@ -76,22 +66,23 @@ class FolderViewModel(context: Context) : ViewModel() {
7666
refreshFolders()
7767
}
7868

79-
fun deleteFolderInfo(id: Int) {
69+
fun updateFolder(id: Int, title: String, appInfo: List<AppInfo>) {
8070
viewModelScope.launch {
81-
repository.deleteFolderInfo(id)
71+
repository.updateFolderWithItems(id, title, appInfo)
8272
}
83-
refreshFolders()
73+
refreshFolders(true)
8474
}
8575

86-
fun updateFolderWithItems(id: Int, title: String, appInfos: List<AppInfo>) {
76+
fun deleteFolder(id: Int) {
8777
viewModelScope.launch {
88-
repository.updateFolderWithItems(id, title, appInfos)
78+
repository.deleteFolderInfo(id)
8979
}
80+
refreshFolders(true)
9081
}
9182

9283
private suspend fun loadFolders() {
9384
val folders = repository.getAllFolders()
94-
_folders.value = folders
85+
_folders.update { folders }
9586
_foldersMutable.postValue(folders)
9687
}
9788
}

lawnchair/src/app/lawnchair/data/folder/service/FolderService.kt

-9
Original file line numberDiff line numberDiff line change
@@ -76,15 +76,6 @@ class FolderService(val context: Context) : SafeCloseable {
7676
return null
7777
}
7878

79-
suspend fun getItems(id: Int): Set<String> = withContext(Dispatchers.IO) {
80-
return@withContext try {
81-
folderDao.getItems(id).mapNotNull { it.componentKey }.toSet()
82-
} catch (e: Exception) {
83-
Log.e("FolderService", "Failed to get all items", e)
84-
setOf()
85-
}
86-
}
87-
8879
suspend fun getAllFolders(): List<FolderInfo> = withContext(Dispatchers.Main) {
8980
try {
9081
val folderEntities = folderDao.getAllFolders().firstOrNull() ?: emptyList()

lawnchair/src/app/lawnchair/gestures/DirectionalGestureListener.kt

+39-27
Original file line numberDiff line numberDiff line change
@@ -2,60 +2,72 @@ package app.lawnchair.gestures
22

33
import android.annotation.SuppressLint
44
import android.content.Context
5+
import android.util.Log
56
import android.view.GestureDetector
67
import android.view.GestureDetector.SimpleOnGestureListener
78
import android.view.MotionEvent
89
import android.view.View
910
import android.view.View.OnTouchListener
1011
import kotlin.math.abs
1112

12-
open class DirectionalGestureListener(ctx: Context?) : OnTouchListener {
13-
private val mGestureDetector: GestureDetector
13+
abstract class DirectionalGestureListener(ctx: Context?) : OnTouchListener {
14+
private val mGestureDetector = GestureDetector(ctx, GestureListener())
1415

1516
@SuppressLint("ClickableViewAccessibility")
1617
override fun onTouch(v: View, event: MotionEvent): Boolean {
1718
return mGestureDetector.onTouchEvent(event)
1819
}
1920

20-
private inner class GestureListener : SimpleOnGestureListener() {
21+
inner class GestureListener : SimpleOnGestureListener() {
22+
23+
private fun shouldReactToSwipe(diff: Float, velocity: Float): Boolean = abs(diff) > SWIPE_THRESHOLD && abs(velocity) > SWIPE_VELOCITY_THRESHOLD
2124

2225
override fun onDown(e: MotionEvent): Boolean {
2326
return true
2427
}
2528

26-
private fun shouldReactToSwipe(diff: Float, velocity: Float): Boolean = abs(diff) > SWIPE_THRESHOLD && abs(velocity) > SWIPE_VELOCITY_THRESHOLD
29+
override fun onFling(
30+
e1: MotionEvent?,
31+
e2: MotionEvent,
32+
velocityX: Float,
33+
velocityY: Float,
34+
): Boolean {
35+
val diffY = e2.y - (e1?.y ?: 0f)
36+
val diffX = e2.x - (e1?.x ?: 0f)
2737

28-
override fun onFling(e1: MotionEvent?, e2: MotionEvent, velocityX: Float, velocityY: Float): Boolean {
29-
return try {
30-
val diffY = e2.y - (e1?.y ?: 0f)
31-
val diffX = e2.x - (e1?.x ?: 0f)
38+
Log.d("GESTURE_DETECTION", "onFling: y " + shouldReactToSwipe(diffY, velocityY))
39+
Log.d("GESTURE_DETECTION", "onFling: X " + shouldReactToSwipe(diffX, velocityX))
3240

33-
when {
34-
abs(diffX) > abs(diffY) && shouldReactToSwipe(diffX, velocityX) -> {
35-
if (diffX > 0) onSwipeRight() else onSwipeLeft()
36-
true
41+
return when {
42+
shouldReactToSwipe(diffY, velocityY) -> {
43+
if (diffY < 0) {
44+
Log.d("GESTURE_DETECTION", "Swipe Up Detected")
45+
onSwipeTop()
46+
} else {
47+
Log.d("GESTURE_DETECTION", "Swipe Down Detected")
48+
onSwipeDown()
3749
}
38-
shouldReactToSwipe(diffY, velocityY) -> {
39-
if (diffY > 0) onSwipeBottom() else onSwipeTop()
40-
true
50+
true
51+
}
52+
shouldReactToSwipe(diffX, velocityX) -> {
53+
if (diffX > 0) {
54+
Log.d("GESTURE_DETECTION", "Swipe Right Detected")
55+
onSwipeRight()
56+
} else {
57+
Log.d("GESTURE_DETECTION", "Swipe Left Detected")
58+
onSwipeLeft()
4159
}
42-
else -> false
60+
true
4361
}
44-
} catch (e: Exception) {
45-
e.printStackTrace()
46-
false
62+
else -> false
4763
}
4864
}
4965
}
5066

51-
fun onSwipeRight() {}
52-
fun onSwipeLeft() {}
53-
fun onSwipeTop() {}
54-
open fun onSwipeBottom() {}
55-
56-
init {
57-
mGestureDetector = GestureDetector(ctx, GestureListener())
58-
}
67+
abstract fun onSwipeRight()
68+
abstract fun onSwipeLeft()
69+
abstract fun onSwipeTop()
70+
abstract fun onSwipeDown()
5971

6072
companion object {
6173
private const val SWIPE_THRESHOLD = 100
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package app.lawnchair.gestures
2+
3+
import android.content.Context
4+
import android.util.Log
5+
import androidx.lifecycle.lifecycleScope
6+
import app.lawnchair.gestures.config.GestureHandlerConfig
7+
import app.lawnchair.gestures.type.GestureType
8+
import app.lawnchair.launcher
9+
import app.lawnchair.preferences2.PreferenceManager2
10+
import com.android.launcher3.model.data.ItemInfo
11+
import com.android.launcher3.util.VibratorWrapper
12+
import kotlinx.coroutines.flow.firstOrNull
13+
import kotlinx.coroutines.launch
14+
15+
class IconGestureListener(
16+
private val context: Context,
17+
private val prefs: PreferenceManager2,
18+
private val cmp: ItemInfo?,
19+
) : DirectionalGestureListener(context) {
20+
21+
override fun onSwipeRight() = handleGesture(GestureType.SWIPE_RIGHT)
22+
override fun onSwipeLeft() = handleGesture(GestureType.SWIPE_LEFT)
23+
override fun onSwipeTop() = handleGesture(GestureType.SWIPE_UP)
24+
override fun onSwipeDown() = handleGesture(GestureType.SWIPE_DOWN)
25+
26+
private fun handleGesture(gestureType: GestureType) {
27+
Log.d("GESTURE_HANDLER", "Handling gesture: ${gestureType.name}")
28+
29+
cmp?.componentKey?.let {
30+
context.launcher.lifecycleScope.launch {
31+
val gesture = prefs.getGestureForApp(it, gestureType).firstOrNull()
32+
if (gesture !is GestureHandlerConfig.NoOp) {
33+
Log.d("GESTURE_HANDLER", "Triggering gesture: ${gestureType.name}")
34+
VibratorWrapper.INSTANCE.get(context.launcher).vibrate(VibratorWrapper.OVERVIEW_HAPTIC)
35+
gesture?.createHandler(context)?.onTrigger(context.launcher)
36+
} else {
37+
Log.d("GESTURE_HANDLER", "NoOp gesture, ignoring")
38+
}
39+
}
40+
}
41+
}
42+
}

0 commit comments

Comments
 (0)