Initial gesture implementation

This commit is contained in:
Suphon Thanakornpakapong
2022-05-25 22:04:38 +07:00
parent 399460bd05
commit 101f835121
10 changed files with 228 additions and 28 deletions
+3
View File
@@ -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'
+4
View File
@@ -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
)
},
)
}
}
}
}
}
)
}