diff --git a/app/src/main/java/com/olup/notable/components/EditorGestureReceiver.kt b/app/src/main/java/com/olup/notable/components/EditorGestureReceiver.kt index f45a044d..f1d3387b 100644 --- a/app/src/main/java/com/olup/notable/components/EditorGestureReceiver.kt +++ b/app/src/main/java/com/olup/notable/components/EditorGestureReceiver.kt @@ -1,17 +1,21 @@ package com.olup.notable import io.shipbook.shipbooksdk.Log -import androidx.compose.foundation.gestures.* +import androidx.compose.foundation.gestures.awaitEachGesture +import androidx.compose.foundation.gestures.awaitFirstDown import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.livedata.observeAsState import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier import androidx.compose.ui.input.pointer.PointerType import androidx.compose.ui.input.pointer.pointerInput -import com.olup.notable.EditorControlTower +import androidx.compose.ui.platform.LocalContext +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch @@ -25,6 +29,10 @@ fun EditorGestureReceiver( ) { val coroutineScope = rememberCoroutineScope() + val appSettings by AppRepository(LocalContext.current) + .kvProxy + .observeKv("APP_SETTINGS", AppSettings.serializer(), AppSettings(version = 1)) + .observeAsState() Box( modifier = Modifier .pointerInput(Unit) { @@ -34,7 +42,7 @@ fun EditorGestureReceiver( val inputId = down.id val initialPosition = down.position - val initialTimestamp = System.currentTimeMillis(); + val initialTimestamp = System.currentTimeMillis() var lastPosition = initialPosition var lastTimestamp = initialTimestamp @@ -50,8 +58,8 @@ fun EditorGestureReceiver( val fingerChange = event.changes.filter { it.type == PointerType.Touch } // is already consumed return if (fingerChange.find { it.isConsumed } != null) { + Log.i(TAG, "Canceling gesture - already consumed") return@awaitEachGesture - Log.i(TAG, "Canceling gesture - already consumemd") } fingerChange.forEach { it.consume() } @@ -59,7 +67,7 @@ fun EditorGestureReceiver( fingerChange.find { it.id.value == inputId.value } ?: break lastPosition = eventReference.position - lastTimestamp = System.currentTimeMillis(); + lastTimestamp = System.currentTimeMillis() inputsCount = fingerChange.size if (fingerChange.any { !it.pressed }) break @@ -75,20 +83,36 @@ fun EditorGestureReceiver( if (withTimeoutOrNull(100) { awaitFirstDown() if (inputsCount == 1) { - state.isToolbarOpen = !state.isToolbarOpen + resolveGesture( + settings = appSettings, + default = AppSettings.defaultDoubleTapAction, + override = AppSettings::doubleTapAction, + state = state, + scope = coroutineScope, + previousPage = goToPreviousPage, + nextPage = goToNextPage, + ) } } != null) return@awaitEachGesture // in case of single tap if (inputsCount == 2) { - state.mode = if (state.mode == Mode.Draw) Mode.Erase else Mode.Draw + resolveGesture( + settings = appSettings, + default = AppSettings.defaultTwoFingerTapAction, + override = AppSettings::twoFingerTapAction, + state = state, + scope = coroutineScope, + previousPage = goToPreviousPage, + nextPage = goToNextPage, + ) } return@awaitEachGesture } val verticalDrag = lastPosition.y - initialPosition.y - val horinzontalDrag = lastPosition.x - initialPosition.x + val horizontalDrag = lastPosition.x - initialPosition.x if (verticalDrag < -200) { @@ -113,33 +137,93 @@ fun EditorGestureReceiver( } } } - if (horinzontalDrag < -200) { + if (horizontalDrag < -200) { if (inputsCount == 1) { - goToNextPage() + resolveGesture( + settings = appSettings, + default = AppSettings.defaultSwipeLeftAction, + override = AppSettings::swipeLeftAction, + state = state, + scope = coroutineScope, + previousPage = goToPreviousPage, + nextPage = goToNextPage, + ) } else if (inputsCount == 2) { - Log.i(TAG, "Redo") - coroutineScope.launch { - History.moveHistory(UndoRedoType.Redo) - DrawCanvas.refreshUi.emit(Unit) - } + resolveGesture( + settings = appSettings, + default = AppSettings.defaultTwoFingerSwipeLeftAction, + override = AppSettings::twoFingerSwipeLeftAction, + state = state, + scope = coroutineScope, + previousPage = goToPreviousPage, + nextPage = goToNextPage, + ) } } - if (horinzontalDrag > 200) { + if (horizontalDrag > 200) { if (inputsCount == 1) { - goToPreviousPage() + resolveGesture( + settings = appSettings, + default = AppSettings.defaultSwipeRightAction, + override = AppSettings::swipeRightAction, + state = state, + scope = coroutineScope, + previousPage = goToPreviousPage, + nextPage = goToNextPage, + ) } else if (inputsCount == 2) { - Log.i(TAG, "Undo") - coroutineScope.launch { - History.moveHistory(UndoRedoType.Undo) - DrawCanvas.refreshUi.emit(Unit) - } + resolveGesture( + settings = appSettings, + default = AppSettings.defaultTwoFingerSwipeRightAction, + override = AppSettings::twoFingerSwipeRightAction, + state = state, + scope = coroutineScope, + previousPage = goToPreviousPage, + nextPage = goToNextPage, + ) } - } - } } .fillMaxWidth() .fillMaxHeight() ) -} \ No newline at end of file +} + +private fun resolveGesture( + settings: AppSettings?, + default: AppSettings.GestureAction, + override: AppSettings.() -> AppSettings.GestureAction?, + state: EditorState, + scope: CoroutineScope, + previousPage: () -> Unit, + nextPage: () -> Unit, +) { + when (if (settings != null) override(settings) else default) { + null -> Log.i(TAG, "No Action") + AppSettings.GestureAction.PreviousPage -> previousPage() + AppSettings.GestureAction.NextPage -> nextPage() + + AppSettings.GestureAction.ChangeTool -> + state.mode = if (state.mode == Mode.Draw) Mode.Erase else Mode.Draw + + AppSettings.GestureAction.ToggleZen -> + state.isToolbarOpen = !state.isToolbarOpen + + AppSettings.GestureAction.Undo -> { + Log.i(TAG, "Undo") + scope.launch { + History.moveHistory(UndoRedoType.Undo) + DrawCanvas.refreshUi.emit(Unit) + } + } + + AppSettings.GestureAction.Redo -> { + Log.i(TAG, "Redo") + scope.launch { + History.moveHistory(UndoRedoType.Redo) + DrawCanvas.refreshUi.emit(Unit) + } + } + } +} diff --git a/app/src/main/java/com/olup/notable/components/Select.kt b/app/src/main/java/com/olup/notable/components/Select.kt index 453ed500..698bca70 100644 --- a/app/src/main/java/com/olup/notable/components/Select.kt +++ b/app/src/main/java/com/olup/notable/components/Select.kt @@ -7,9 +7,6 @@ import androidx.compose.material.Icon import androidx.compose.material.Text import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.ArrowDropDown -import androidx.compose.material.icons.rounded.Edit -import androidx.compose.material.icons.sharp.Edit -import androidx.compose.material.icons.sharp.KeyboardArrowDown import androidx.compose.runtime.* import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color @@ -20,11 +17,11 @@ import androidx.compose.ui.window.Popup import com.olup.notable.noRippleClickable @Composable -fun SelectMenu(options: List>, value: String, onChange: (String) -> Unit) { +fun SelectMenu(options: List>, value: T, onChange: (T) -> Unit) { var isExpanded by remember { mutableStateOf(false) } - Box() { - Row() { + Box { + Row { Text(text = options.find { it.first == value }?.second ?: "Undefined", fontWeight = FontWeight.Light, modifier = Modifier.noRippleClickable { isExpanded = true }) @@ -52,11 +49,10 @@ fun SelectMenu(options: List>, value: String, onChange: (St .noRippleClickable { onChange(it.first) isExpanded = false - }) + } + ) } } - } } - } \ No newline at end of file diff --git a/app/src/main/java/com/olup/notable/modals/AppSettings.kt b/app/src/main/java/com/olup/notable/modals/AppSettings.kt index e975fb76..cddc192a 100644 --- a/app/src/main/java/com/olup/notable/modals/AppSettings.kt +++ b/app/src/main/java/com/olup/notable/modals/AppSettings.kt @@ -19,14 +19,35 @@ import androidx.compose.ui.window.Dialog import androidx.compose.ui.window.DialogProperties import com.olup.notable.components.SelectMenu import com.olup.notable.db.KvProxy +import kotlinx.serialization.Serializable import kotlin.concurrent.thread -@kotlinx.serialization.Serializable +@Serializable data class AppSettings( - val version: Int, - val defaultNativeTemplate: String = "blank", - val quickNavPages: List = listOf() -) + val version: Int, + val defaultNativeTemplate: String = "blank", + val quickNavPages: List = listOf(), + + val doubleTapAction: GestureAction? = defaultDoubleTapAction, + val twoFingerTapAction: GestureAction? = defaultTwoFingerTapAction, + val swipeLeftAction: GestureAction? = defaultSwipeLeftAction, + val swipeRightAction: GestureAction? = defaultSwipeRightAction, + val twoFingerSwipeLeftAction: GestureAction? = defaultTwoFingerSwipeLeftAction, + val twoFingerSwipeRightAction: GestureAction? = defaultTwoFingerSwipeRightAction, +) { + companion object { + val defaultDoubleTapAction get() = GestureAction.ToggleZen + val defaultTwoFingerTapAction get() = GestureAction.ChangeTool + val defaultSwipeLeftAction get() = GestureAction.NextPage + val defaultSwipeRightAction get() = GestureAction.PreviousPage + val defaultTwoFingerSwipeLeftAction get() = GestureAction.Redo + val defaultTwoFingerSwipeRightAction get() = GestureAction.Undo + } + + enum class GestureAction { + Undo, Redo, PreviousPage, NextPage, ChangeTool, ToggleZen, + } +} @Composable fun AppSettingsModal(onClose: () -> Unit) { @@ -47,78 +68,164 @@ fun AppSettingsModal(onClose: () -> Unit) { properties = DialogProperties(usePlatformDefaultWidth = false), ) { Column( - modifier = - Modifier.padding(40.dp) - .background(Color.White) - .fillMaxWidth() - .border(2.dp, Color.Black, RectangleShape) + modifier = Modifier + .padding(40.dp) + .background(Color.White) + .fillMaxWidth() + .border(2.dp, Color.Black, RectangleShape) ) { Column(Modifier.padding(20.dp, 10.dp)) { - Text( - text = - "App setting - v${BuildConfig.VERSION_NAME}${if(isNext) " [NEXT]" else ""}" - ) + Text(text = "App setting - v${BuildConfig.VERSION_NAME}${if(isNext) " [NEXT]" else ""}") } Box(Modifier.height(0.5.dp).fillMaxWidth().background(Color.Black)) Column(Modifier.padding(20.dp, 10.dp)) { - Row() { + Row { Text(text = "Default Page Background Template") Spacer(Modifier.width(10.dp)) SelectMenu( - options = - listOf( - "blank" to "Blank page", - "dotted" to "Dot grid", - "lined" to "Lines", - "squared" to "Small squares grid" - ), - onChange = { - kv.setKv( - "APP_SETTINGS", - settings!!.copy(defaultNativeTemplate = it), - AppSettings.serializer() - ) - }, - value = settings?.defaultNativeTemplate ?: "blank" + options = listOf( + "blank" to "Blank page", + "dotted" to "Dot grid", + "lined" to "Lines", + "squared" to "Small squares grid" + ), + onChange = { + kv.setKv( + "APP_SETTINGS", + settings!!.copy(defaultNativeTemplate = it), + AppSettings.serializer() + ) + }, + value = settings?.defaultNativeTemplate ?: "blank" ) } Spacer(Modifier.height(10.dp)) + GestureSelectorRow( + title = "Double Tap Action", + kv = kv, + settings = settings, + update = { copy(doubleTapAction = it) }, + default = AppSettings.defaultDoubleTapAction, + override = { doubleTapAction } + ) + Spacer(Modifier.height(10.dp)) + + GestureSelectorRow( + title = "Two Finger Tap Action", + kv = kv, + settings = settings, + update = { copy(twoFingerTapAction = it) }, + default = AppSettings.defaultTwoFingerTapAction, + override = { twoFingerTapAction } + ) + Spacer(Modifier.height(10.dp)) + + GestureSelectorRow( + title = "Swipe Left Action", + kv = kv, + settings = settings, + update = { copy(swipeLeftAction = it) }, + default = AppSettings.defaultSwipeLeftAction, + override = { swipeLeftAction } + ) + Spacer(Modifier.height(10.dp)) + + GestureSelectorRow( + title = "Swipe Right Action", + kv = kv, + settings = settings, + update = { copy(swipeRightAction = it) }, + default = AppSettings.defaultSwipeRightAction, + override = { swipeRightAction } + ) + Spacer(Modifier.height(10.dp)) + + GestureSelectorRow( + title = "Two Finger Swipe Left Action", + kv = kv, + settings = settings, + update = { copy(twoFingerSwipeLeftAction = it) }, + default = AppSettings.defaultTwoFingerSwipeLeftAction, + override = { twoFingerSwipeLeftAction } + ) + Spacer(Modifier.height(10.dp)) + + GestureSelectorRow( + title = "Two Finger Swipe Right Action", + kv = kv, + settings = settings, + update = { copy(twoFingerSwipeRightAction = it) }, + default = AppSettings.defaultTwoFingerSwipeRightAction, + override = { twoFingerSwipeRightAction } + ) + Spacer(Modifier.height(10.dp)) + if (!isLatestVersion) { Text( - text = "It seems a new version of Notable is available on github.", - fontStyle = FontStyle.Italic + text = "It seems a new version of Notable is available on github.", + fontStyle = FontStyle.Italic ) - Spacer(Modifier.height(10.dp)) Text( - text = "See release in browser", - textDecoration = TextDecoration.Underline, - modifier = - Modifier.noRippleClickable { - val urlIntent = - Intent( - Intent.ACTION_VIEW, - Uri.parse( - "https://github.com/olup/notable/releases" - ) - ) - context.startActivity(urlIntent) - } + text = "See release in browser", + textDecoration = TextDecoration.Underline, + modifier = Modifier.noRippleClickable { + val urlIntent = Intent( + Intent.ACTION_VIEW, + Uri.parse("https://github.com/olup/notable/releases") + ) + context.startActivity(urlIntent) + } ) Spacer(Modifier.height(10.dp)) } else { Text( - text = "Check for newer version", - textDecoration = TextDecoration.Underline, - modifier = - Modifier.noRippleClickable { - thread { isLatestVersion = isLatestVersion(context, true) } - } + text = "Check for newer version", + textDecoration = TextDecoration.Underline, + modifier = Modifier.noRippleClickable { + thread { isLatestVersion = isLatestVersion(context, true) } + } ) } } } } } + +@Composable +fun GestureSelectorRow( + title: String, + kv: KvProxy, + settings: AppSettings?, + update: AppSettings.(AppSettings.GestureAction?) -> AppSettings, + default: AppSettings.GestureAction, + override: AppSettings.() -> AppSettings.GestureAction?, +) { + Row { + Text(text = title) + Spacer(Modifier.width(10.dp)) + SelectMenu( + options = listOf( + null to "None", + AppSettings.GestureAction.Undo to "Undo", + AppSettings.GestureAction.Redo to "Redo", + AppSettings.GestureAction.PreviousPage to "Previous Page", + AppSettings.GestureAction.NextPage to "Next Page", + AppSettings.GestureAction.ChangeTool to "Toggle Pen / Eraser", + AppSettings.GestureAction.ToggleZen to "Toggle Zen Mode", + ), + value = if (settings != null) settings.override() else default, + onChange = { + if (settings != null) { + kv.setKv( + "APP_SETTINGS", + settings.update(it), + AppSettings.serializer(), + ) + } + }, + ) + } +}