Initial gesture implementation
This commit is contained in:
@@ -15,6 +15,7 @@ buildscript {
|
||||
classpath 'com.android.tools.build:gradle:7.2.0'
|
||||
classpath PROTOBUF_CLASS_PATH
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||
classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version"
|
||||
classpath 'com.google.android.gms:oss-licenses-plugin:0.10.5'
|
||||
}
|
||||
}
|
||||
@@ -88,6 +89,7 @@ apply plugin: 'com.google.protobuf'
|
||||
apply plugin: 'com.google.android.gms.oss-licenses-plugin'
|
||||
apply plugin: 'kotlin-parcelize'
|
||||
apply plugin: 'kotlin-kapt'
|
||||
apply plugin: 'org.jetbrains.kotlin.plugin.serialization'
|
||||
|
||||
final def commitHash = { ->
|
||||
final def stdout = new ByteArrayOutputStream()
|
||||
@@ -337,6 +339,7 @@ dependencies {
|
||||
withoutQuickstepImplementation fileTree(dir: "${FRAMEWORK_PREBUILTS_DIR}/libs", include: 'plugin_core.jar')
|
||||
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||
implementation 'org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.2'
|
||||
testImplementation 'junit:junit:4.13.2'
|
||||
androidTestImplementation 'org.mockito:mockito-core:4.5.1'
|
||||
androidTestImplementation 'com.google.dexmaker:dexmaker:1.2'
|
||||
|
||||
@@ -288,4 +288,8 @@
|
||||
<string name="invalid_backup_file">Invalid backup file.</string>
|
||||
<string name="google_folder_title" translatable="false">Google</string>
|
||||
<string name="battery_charging_percentage_charging_time">"%1$d%% — Full in %2$s"</string>
|
||||
<string name="gesture_double_tap">Double-Tap</string>
|
||||
<string name="gesture_handler_no_op">Do Nothing</string>
|
||||
<string name="gesture_handler_sleep">Sleep</string>
|
||||
<string name="gesture_handler_open_notifications">Open Notification Panel</string>
|
||||
</resources>
|
||||
|
||||
@@ -18,31 +18,31 @@ package app.lawnchair.gestures
|
||||
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import app.lawnchair.LawnchairLauncher
|
||||
import app.lawnchair.gestures.handlers.SleepGestureHandler
|
||||
import app.lawnchair.gestures.config.GestureHandlerConfig
|
||||
import app.lawnchair.preferences2.PreferenceManager2
|
||||
import com.patrykmichalik.preferencemanager.onEach
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import com.patrykmichalik.preferencemanager.Preference
|
||||
import kotlinx.coroutines.MainScope
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class GestureController(private val launcher: LawnchairLauncher) {
|
||||
private val preferenceManager = PreferenceManager2.getInstance(launcher)
|
||||
private val doubleTapHandler = SleepGestureHandler(launcher)
|
||||
private val coroutineScope = CoroutineScope(Dispatchers.IO)
|
||||
private var dt2s = false
|
||||
private val prefs = PreferenceManager2.getInstance(launcher)
|
||||
private val scope = MainScope()
|
||||
|
||||
init {
|
||||
preferenceManager.dt2s.onEach(launchIn = coroutineScope) {
|
||||
dt2s = it
|
||||
}
|
||||
}
|
||||
private val doubleTapHandler = handler(prefs.doubleTapHandler)
|
||||
|
||||
fun onDoubleTap() {
|
||||
// TODO: proper gesture selection system
|
||||
if (dt2s) {
|
||||
launcher.lifecycleScope.launch {
|
||||
doubleTapHandler.onTrigger(launcher)
|
||||
}
|
||||
launcher.lifecycleScope.launch {
|
||||
doubleTapHandler.first().onTrigger(launcher)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handler(pref: Preference<GestureHandlerConfig, String>) = pref.get()
|
||||
.distinctUntilChanged()
|
||||
.map { it.createHandler(launcher) }
|
||||
.shareIn(
|
||||
scope,
|
||||
SharingStarted.Lazily,
|
||||
replay = 1
|
||||
)
|
||||
}
|
||||
|
||||
@@ -23,3 +23,7 @@ abstract class GestureHandler(val context: Context) {
|
||||
|
||||
abstract suspend fun onTrigger(launcher: LawnchairLauncher)
|
||||
}
|
||||
|
||||
class NoOpGestureHandler(context: Context) : GestureHandler(context) {
|
||||
override suspend fun onTrigger(launcher: LawnchairLauncher) = Unit
|
||||
}
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
package app.lawnchair.gestures.config
|
||||
|
||||
import android.content.Context
|
||||
import app.lawnchair.gestures.GestureHandler
|
||||
import app.lawnchair.gestures.NoOpGestureHandler
|
||||
import app.lawnchair.gestures.handlers.OpenNotificationsHandler
|
||||
import app.lawnchair.gestures.handlers.SleepGestureHandler
|
||||
import com.android.launcher3.R
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.Transient
|
||||
|
||||
@Serializable
|
||||
sealed class GestureHandlerConfig {
|
||||
|
||||
abstract fun getLabel(context: Context): String
|
||||
abstract fun createHandler(context: Context): GestureHandler
|
||||
|
||||
@Serializable
|
||||
sealed class Simple(
|
||||
private val labelRes: Int,
|
||||
@Transient private val creator: (Context) -> GestureHandler = {
|
||||
throw IllegalArgumentException("default creator not supported")
|
||||
}
|
||||
) : GestureHandlerConfig() {
|
||||
override fun getLabel(context: Context) = context.getString(labelRes)
|
||||
override fun createHandler(context: Context) = creator(context)
|
||||
}
|
||||
|
||||
@Serializable
|
||||
@SerialName("noOp")
|
||||
object NoOp : Simple(R.string.gesture_handler_no_op, { NoOpGestureHandler(it) })
|
||||
|
||||
@Serializable
|
||||
@SerialName("sleep")
|
||||
object Sleep : Simple(R.string.gesture_handler_sleep, { SleepGestureHandler(it) })
|
||||
|
||||
@Serializable
|
||||
@SerialName("openNotifications")
|
||||
object OpenNotifications : Simple(R.string.gesture_handler_open_notifications, { OpenNotificationsHandler(it) })
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package app.lawnchair.gestures.config
|
||||
|
||||
import android.content.Context
|
||||
import com.android.launcher3.R
|
||||
|
||||
abstract class GestureHandlerOption(
|
||||
val labelRes: Int,
|
||||
val configClass: Class<*>
|
||||
) {
|
||||
|
||||
fun getLabel(context: Context) = context.getString(labelRes)
|
||||
|
||||
abstract suspend fun buildConfig(): GestureHandlerConfig
|
||||
|
||||
open class Simple(labelRes: Int, val obj: GestureHandlerConfig) : GestureHandlerOption(labelRes, obj::class.java) {
|
||||
override suspend fun buildConfig() = obj
|
||||
}
|
||||
|
||||
object NoOp : Simple(R.string.gesture_handler_no_op, GestureHandlerConfig.NoOp)
|
||||
object Sleep : Simple(R.string.gesture_handler_sleep, GestureHandlerConfig.Sleep)
|
||||
object OpenNotifications : Simple(R.string.gesture_handler_open_notifications, GestureHandlerConfig.OpenNotifications)
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package app.lawnchair.gestures.handlers
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import app.lawnchair.LawnchairLauncher
|
||||
import app.lawnchair.gestures.GestureHandler
|
||||
import java.lang.reflect.InvocationTargetException
|
||||
|
||||
class OpenNotificationsHandler(context: Context) : GestureHandler(context) {
|
||||
|
||||
@SuppressLint("WrongConstant")
|
||||
override suspend fun onTrigger(launcher: LawnchairLauncher) {
|
||||
try {
|
||||
Class.forName("android.app.StatusBarManager")
|
||||
.getMethod("expandNotificationsPanel")
|
||||
.invoke(context.getSystemService("statusbar"))
|
||||
} catch (e: IllegalAccessException) {
|
||||
e.printStackTrace()
|
||||
} catch (e: InvocationTargetException) {
|
||||
e.printStackTrace()
|
||||
} catch (e: NoSuchMethodException) {
|
||||
e.printStackTrace()
|
||||
} catch (e: ClassNotFoundException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,7 @@ import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.datastore.preferences.core.*
|
||||
import androidx.datastore.preferences.preferencesDataStore
|
||||
import app.lawnchair.font.FontCache
|
||||
import app.lawnchair.gestures.config.GestureHandlerConfig
|
||||
import app.lawnchair.icons.CustomAdaptiveIconDrawable
|
||||
import app.lawnchair.icons.shape.IconShape
|
||||
import app.lawnchair.icons.shape.IconShapeManager
|
||||
@@ -41,6 +42,9 @@ import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.drop
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.serialization.decodeFromString
|
||||
import kotlinx.serialization.encodeToString
|
||||
import kotlinx.serialization.json.Json
|
||||
import app.lawnchair.preferences.PreferenceManager as LawnchairPreferenceManager
|
||||
import com.android.launcher3.graphics.IconShape as L3IconShape
|
||||
|
||||
@@ -160,11 +164,6 @@ class PreferenceManager2(private val context: Context) : PreferenceManager {
|
||||
defaultValue = context.resources.getBoolean(R.bool.config_default_enable_smartspace_calendar_selection),
|
||||
)
|
||||
|
||||
val dt2s = preference(
|
||||
key = booleanPreferencesKey(name = "dt2s"),
|
||||
defaultValue = context.resources.getBoolean(R.bool.config_default_dts2),
|
||||
)
|
||||
|
||||
val autoShowKeyboardInDrawer = preference(
|
||||
key = booleanPreferencesKey(name = "auto_show_keyboard_in_drawer"),
|
||||
defaultValue = context.resources.getBoolean(R.bool.config_default_auto_show_keyboard_in_drawer),
|
||||
@@ -304,6 +303,21 @@ class PreferenceManager2(private val context: Context) : PreferenceManager {
|
||||
save = { it.toString() },
|
||||
)
|
||||
|
||||
val doubleTapHandler = serializablePreference<GestureHandlerConfig>(
|
||||
key = stringPreferencesKey("double_tap_handler"),
|
||||
defaultValue = GestureHandlerConfig.Sleep
|
||||
)
|
||||
|
||||
private inline fun <reified T> serializablePreference(
|
||||
key: Preferences.Key<String>,
|
||||
defaultValue: T
|
||||
) = preference(
|
||||
key = key,
|
||||
defaultValue = defaultValue,
|
||||
parse = { Json.decodeFromString(it) },
|
||||
save = { Json.encodeToString(it) }
|
||||
)
|
||||
|
||||
init {
|
||||
initializeIconShape(iconShape.firstBlocking())
|
||||
iconShape.get()
|
||||
|
||||
@@ -16,8 +16,10 @@
|
||||
|
||||
package app.lawnchair.ui.preferences
|
||||
|
||||
import androidx.compose.animation.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.navigation.NavGraphBuilder
|
||||
@@ -57,9 +59,9 @@ fun HomeScreenPreferences() {
|
||||
prefs.wallpaperScrolling.getAdapter(),
|
||||
label = stringResource(id = R.string.wallpaper_scrolling_label),
|
||||
)
|
||||
SwitchPreference(
|
||||
adapter = prefs2.dt2s.getAdapter(),
|
||||
label = stringResource(id = R.string.workspace_dt2s),
|
||||
GestureHandlerPreference(
|
||||
adapter = prefs2.doubleTapHandler.getAdapter(),
|
||||
label = stringResource(id = R.string.gesture_double_tap)
|
||||
)
|
||||
val feedAvailable = OverlayCallbackImpl.minusOneAvailable(LocalContext.current)
|
||||
SwitchPreference(
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
package app.lawnchair.ui.preferences.components
|
||||
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.itemsIndexed
|
||||
import androidx.compose.material.RadioButton
|
||||
import androidx.compose.material3.OutlinedButton
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import app.lawnchair.gestures.config.GestureHandlerConfig
|
||||
import app.lawnchair.gestures.config.GestureHandlerOption
|
||||
import app.lawnchair.preferences.PreferenceAdapter
|
||||
import app.lawnchair.ui.AlertBottomSheetContent
|
||||
import app.lawnchair.ui.util.LocalBottomSheetHandler
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
val options = listOf(
|
||||
GestureHandlerOption.NoOp,
|
||||
GestureHandlerOption.Sleep,
|
||||
GestureHandlerOption.OpenNotifications,
|
||||
)
|
||||
|
||||
@Composable
|
||||
fun GestureHandlerPreference(
|
||||
adapter: PreferenceAdapter<GestureHandlerConfig>,
|
||||
label: String
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val scope = rememberCoroutineScope()
|
||||
val bottomSheetHandler = LocalBottomSheetHandler.current
|
||||
|
||||
val currentConfig = adapter.state.value
|
||||
|
||||
fun onSelect(option: GestureHandlerOption) {
|
||||
scope.launch {
|
||||
adapter.onChange(option.buildConfig())
|
||||
}
|
||||
}
|
||||
|
||||
PreferenceTemplate(
|
||||
title = { Text(text = label) },
|
||||
description = { Text(text = currentConfig.getLabel(context)) },
|
||||
modifier = Modifier.clickable {
|
||||
bottomSheetHandler.show {
|
||||
AlertBottomSheetContent(
|
||||
title = { Text(label) },
|
||||
buttons = {
|
||||
OutlinedButton(onClick = { bottomSheetHandler.hide() }) {
|
||||
Text(text = stringResource(id = android.R.string.cancel))
|
||||
}
|
||||
}
|
||||
) {
|
||||
LazyColumn {
|
||||
itemsIndexed(options) { index, option ->
|
||||
if (index > 0) {
|
||||
PreferenceDivider(startIndent = 40.dp)
|
||||
}
|
||||
val selected = currentConfig::class.java == option.configClass
|
||||
PreferenceTemplate(
|
||||
title = { Text(option.getLabel(context)) },
|
||||
modifier = Modifier.clickable {
|
||||
bottomSheetHandler.hide()
|
||||
onSelect(option)
|
||||
},
|
||||
startWidget = {
|
||||
RadioButton(
|
||||
selected = selected,
|
||||
onClick = null
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user