Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added ability to scroll with keys: N, P, PageDown, PageUp #200

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ ij_wrap_on_typing = false
max_line_length = 200
ktlint_code_style = ktlint_official
ktlint_function_naming_ignore_when_annotated_with=Composable
twitter_compose_allowed_composition_locals = LocalTypographySettings,LocalDimens,LocalWindowSize,LocalFoldableHinge
twitter_compose_allowed_composition_locals = LocalTypographySettings,LocalDimens,LocalWindowSize,LocalFoldableHinge,LocalKeyEventHandlers

[*.{java,js,jsx,ts,tsx}]

[{*.ant,*.fxml,*.jhm,*.jnlp,*.jrxml,*.opml,*.rng,*.tld,*.wsdl,*.xml,*.xsd,*.xsl,*.xslt,*.xul,rss_kuketz,rss_morningpaper}]
indent_size = 2
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,15 @@ import com.nononsenseapps.feeder.base.diAwareViewModel
import com.nononsenseapps.feeder.ui.compose.editfeed.CreateFeedScreen
import com.nononsenseapps.feeder.ui.compose.navigation.AddFeedDestination
import com.nononsenseapps.feeder.ui.compose.searchfeed.SearchFeedScreen
import com.nononsenseapps.feeder.ui.compose.utils.KeyEventHandler
import com.nononsenseapps.feeder.ui.compose.utils.withAllProviders

/**
* This activity should only be started via a Send (share) or Open URL/Text intent.
*/
class AddFeedFromShareActivity : DIAwareComponentActivity() {
private val keyEventHandlers = mutableListOf<KeyEventHandler>()

@OptIn(ExperimentalAnimationApi::class)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Expand All @@ -34,7 +37,7 @@ class AddFeedFromShareActivity : DIAwareComponentActivity() {
(intent?.dataString ?: intent?.getStringExtra(Intent.EXTRA_TEXT))?.trim()

setContent {
withAllProviders {
withAllProviders(keyEventHandlers) {
val navController = rememberAnimatedNavController()
AnimatedNavHost(navController, startDestination = "search") {
composable(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import com.google.accompanist.navigation.animation.rememberAnimatedNavController
import com.nononsenseapps.feeder.base.DIAwareComponentActivity
import com.nononsenseapps.feeder.db.room.ID_ALL_FEEDS
import com.nononsenseapps.feeder.ui.compose.ompl.OpmlImportScreen
import com.nononsenseapps.feeder.ui.compose.utils.KeyEventHandler
import com.nononsenseapps.feeder.ui.compose.utils.withAllProviders
import com.nononsenseapps.feeder.util.DEEP_LINK_BASE_URI
import com.nononsenseapps.feeder.util.logDebug
Expand All @@ -22,6 +23,8 @@ import com.nononsenseapps.feeder.util.logDebug
* This activity should only be started via a Open File Intent.
*/
class ImportOMPLFileActivity : DIAwareComponentActivity() {
private val keyEventHandlers = mutableListOf<KeyEventHandler>()

@OptIn(ExperimentalAnimationApi::class)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Expand All @@ -34,7 +37,7 @@ class ImportOMPLFileActivity : DIAwareComponentActivity() {
logDebug(LOG_TAG, "Uri: $uri")

setContent {
withAllProviders {
withAllProviders(keyEventHandlers) {
val navController = rememberAnimatedNavController()
AnimatedNavHost(navController, startDestination = "import") {
composable(
Expand Down
19 changes: 18 additions & 1 deletion app/src/main/java/com/nononsenseapps/feeder/ui/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package com.nononsenseapps.feeder.ui
import android.content.Intent
import android.os.Bundle
import android.util.Log
import android.view.KeyEvent
import androidx.activity.compose.setContent
import androidx.compose.animation.ExperimentalAnimationApi
import androidx.compose.foundation.lazy.rememberLazyListState
Expand All @@ -24,6 +25,7 @@ import com.nononsenseapps.feeder.ui.compose.navigation.FeedDestination
import com.nononsenseapps.feeder.ui.compose.navigation.SearchFeedDestination
import com.nononsenseapps.feeder.ui.compose.navigation.SettingsDestination
import com.nononsenseapps.feeder.ui.compose.navigation.SyncScreenDestination
import com.nononsenseapps.feeder.ui.compose.utils.KeyEventHandler
import com.nononsenseapps.feeder.ui.compose.utils.withAllProviders
import kotlinx.coroutines.launch
import org.kodein.di.instance
Expand All @@ -32,6 +34,8 @@ class MainActivity : DIAwareComponentActivity() {
private val notificationsWorker: NotificationsWorker by instance()
private val mainActivityViewModel: MainActivityViewModel by instance(arg = this)

private val keyEventHandlers = mutableListOf<KeyEventHandler>()

override fun onStart() {
super.onStart()
notificationsWorker.runForever()
Expand Down Expand Up @@ -71,12 +75,25 @@ class MainActivity : DIAwareComponentActivity() {
WindowCompat.setDecorFitsSystemWindows(window, false)

setContent {
withAllProviders {
withAllProviders(keyEventHandlers) {
AppContent()
}
}
}

// Enables key actions without focus
override fun onKeyUp(
keyCode: Int,
event: KeyEvent?,
): Boolean {
for (i in keyEventHandlers.indices.reversed()) {
if (keyEventHandlers[i].invoke(keyCode)) {
return true
}
}
return super.onKeyUp(keyCode, event)
}

@OptIn(ExperimentalAnimationApi::class)
@Composable
fun AppContent() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,15 @@ import com.nononsenseapps.feeder.base.DIAwareComponentActivity
import com.nononsenseapps.feeder.base.diAwareViewModel
import com.nononsenseapps.feeder.ui.compose.navigation.SyncScreenDestination
import com.nononsenseapps.feeder.ui.compose.settings.SettingsScreen
import com.nononsenseapps.feeder.ui.compose.utils.KeyEventHandler
import com.nononsenseapps.feeder.ui.compose.utils.withAllProviders

/**
* Should only be opened from the MANAGE SETTINGS INTENT
*/
class ManageSettingsActivity : DIAwareComponentActivity() {
private val keyEventHandlers = mutableListOf<KeyEventHandler>()

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

Expand All @@ -23,7 +26,7 @@ class ManageSettingsActivity : DIAwareComponentActivity() {
WindowCompat.setDecorFitsSystemWindows(window, false)

setContent {
withAllProviders {
withAllProviders(keyEventHandlers) {
SettingsScreen(
onNavigateUp = {
onNavigateUpFromIntentActivities()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
@file:OptIn(ExperimentalComposeUiApi::class)

package com.nononsenseapps.feeder.ui.compose.feed

import android.content.Intent
import android.view.KeyEvent
import androidx.activity.compose.BackHandler
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
Expand All @@ -13,6 +16,7 @@ import androidx.compose.animation.fadeOut
import androidx.compose.animation.scaleIn
import androidx.compose.animation.scaleOut
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.gestures.animateScrollBy
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.PaddingValues
Expand Down Expand Up @@ -79,6 +83,7 @@ import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.graphics.toArgb
Expand Down Expand Up @@ -141,6 +146,7 @@ import com.nononsenseapps.feeder.ui.compose.theme.LocalDimens
import com.nononsenseapps.feeder.ui.compose.theme.SensibleTopAppBar
import com.nononsenseapps.feeder.ui.compose.theme.SetStatusBarColorToMatchScrollableTopAppBar
import com.nononsenseapps.feeder.ui.compose.utils.ImmutableHolder
import com.nononsenseapps.feeder.ui.compose.utils.ListenKeyEvents
import com.nononsenseapps.feeder.ui.compose.utils.addMargin
import com.nononsenseapps.feeder.ui.compose.utils.isCompactDevice
import com.nononsenseapps.feeder.ui.compose.utils.onKeyEventLikeEscape
Expand Down Expand Up @@ -1089,6 +1095,39 @@ fun FeedListContent(
val coroutineScope = rememberCoroutineScope()
val activityLauncher: ActivityLauncher by LocalDI.current.instance()

val screenHeightPx =
with(LocalDensity.current) {
LocalConfiguration.current.screenHeightDp.dp.toPx().toInt()
}

ListenKeyEvents { keyCode ->
when (keyCode) {
KeyEvent.KEYCODE_P, KeyEvent.KEYCODE_PAGE_UP -> {
// Hard to figure which item would be first one page up so just scroll one
// screen's worth of pixels when going up
coroutineScope.launch {
listState.animateScrollBy(-screenHeightPx.toFloat())
}
true
}

KeyEvent.KEYCODE_N, KeyEvent.KEYCODE_PAGE_DOWN -> {
// Last item can be partially visible, so scroll to it
listState.layoutInfo.visibleItemsInfo
.lastOrNull()
?.index
?.let { lastVisible ->
coroutineScope.launch {
listState.animateScrollToItem(lastVisible)
}
}
true
}

else -> false
}
}

Box(modifier = modifier) {
AnimatedVisibility(
enter = fadeIn(),
Expand Down Expand Up @@ -1122,10 +1161,6 @@ fun FeedListContent(
exit = fadeOut(),
visible = viewState.haveVisibleFeedItems,
) {
val screenHeightPx =
with(LocalDensity.current) {
LocalConfiguration.current.screenHeightDp.dp.toPx().toInt()
}
LazyColumn(
state = listState,
horizontalAlignment = Alignment.CenterHorizontally,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
package com.nononsenseapps.feeder.ui.compose.feedarticle

import android.util.Log
import android.view.KeyEvent.KEYCODE_N
import android.view.KeyEvent.KEYCODE_P
import android.view.KeyEvent.KEYCODE_PAGE_DOWN
import android.view.KeyEvent.KEYCODE_PAGE_UP
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.LocalIndication
import androidx.compose.foundation.clickable
import androidx.compose.foundation.focusGroup
import androidx.compose.foundation.gestures.animateScrollBy
import androidx.compose.foundation.indication
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Arrangement
Expand Down Expand Up @@ -34,12 +39,16 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.RectangleShape
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.pluralStringResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.CustomAccessibilityAction
Expand Down Expand Up @@ -68,9 +77,11 @@ import com.nononsenseapps.feeder.ui.compose.theme.FeederTheme
import com.nononsenseapps.feeder.ui.compose.theme.LinkTextStyle
import com.nononsenseapps.feeder.ui.compose.theme.LocalDimens
import com.nononsenseapps.feeder.ui.compose.theme.hasImageAspectRatioInReader
import com.nononsenseapps.feeder.ui.compose.utils.ListenKeyEvents
import com.nononsenseapps.feeder.ui.compose.utils.ProvideScaledText
import com.nononsenseapps.feeder.ui.compose.utils.ScreenType
import com.nononsenseapps.feeder.ui.compose.utils.focusableInNonTouchMode
import kotlinx.coroutines.launch
import java.time.format.DateTimeFormatter
import java.time.format.FormatStyle
import java.util.Locale
Expand All @@ -80,7 +91,7 @@ val dateTimeFormat: DateTimeFormatter =
DateTimeFormatter.ofLocalizedDateTime(FormatStyle.FULL, FormatStyle.SHORT)
.withLocale(Locale.getDefault())

@OptIn(ExperimentalFoundationApi::class)
@OptIn(ExperimentalFoundationApi::class, ExperimentalComposeUiApi::class)
@Composable
fun ReaderView(
screenType: ScreenType,
Expand All @@ -98,12 +109,39 @@ fun ReaderView(
articleBody: LazyListScope.() -> Unit,
) {
val dimens = LocalDimens.current
val coroutineScope = rememberCoroutineScope()

val scrollHeightPx =
with(LocalDensity.current) {
// Remove top and bottom bars from size and reduce slightly
(LocalConfiguration.current.screenHeightDp.dp - 64.dp).toPx() * 0.8f
}

val readTimeSecs =
remember(wordCount) {
wordsToReadTimeSecs(wordCount)
}

ListenKeyEvents { keyCode ->
when (keyCode) {
KEYCODE_P, KEYCODE_PAGE_UP -> {
coroutineScope.launch {
articleListState.animateScrollBy(-scrollHeightPx)
}
true
}

KEYCODE_N, KEYCODE_PAGE_DOWN -> {
coroutineScope.launch {
articleListState.animateScrollBy(scrollHeightPx)
}
true
}

else -> false
}
}

SelectionContainer {
LazyColumn(
state = articleListState,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,12 @@ import com.nononsenseapps.feeder.ui.compose.theme.FeederTheme
import com.nononsenseapps.feeder.ui.compose.theme.ProvideFontScale
import org.kodein.di.compose.withDI

@Suppress("ktlint:compose:mutable-params-check")
@Composable
fun DIAwareComponentActivity.withAllProviders(content: @Composable () -> Unit) {
fun DIAwareComponentActivity.withAllProviders(
handlers: MutableList<KeyEventHandler>,
content: @Composable () -> Unit,
) {
withDI {
val viewModel: CommonActivityViewModel = diAwareViewModel()
val currentTheme by viewModel.currentTheme.collectAsStateWithLifecycle()
Expand All @@ -25,8 +29,10 @@ fun DIAwareComponentActivity.withAllProviders(content: @Composable () -> Unit) {
dynamicColors = dynamicColors,
) {
withWindowSize {
ProvideFontScale(fontScale = textScale) {
WithFeederTextToolbar(content)
ProvideKeyEventHandler(handlers) {
ProvideFontScale(fontScale = textScale) {
WithFeederTextToolbar(content)
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,11 @@ import androidx.compose.ui.composed
import androidx.compose.ui.focus.focusProperties
import androidx.compose.ui.input.InputMode
import androidx.compose.ui.input.key.Key
import androidx.compose.ui.input.key.KeyEvent
import androidx.compose.ui.input.key.KeyEventType
import androidx.compose.ui.input.key.key
import androidx.compose.ui.input.key.onKeyEvent
import androidx.compose.ui.input.key.type
import androidx.compose.ui.platform.LocalInputModeManager
import androidx.compose.ui.platform.debugInspectorInfo

Expand All @@ -22,7 +25,7 @@ fun Modifier.onKeyEventLikeEscape(action: () -> Unit) =
properties["action"] = action
},
) {
onKeyEvent {
onKeyUp {
when (it.key) {
Key.Escape, Key.Back, Key.NavigateOut -> {
action()
Expand All @@ -34,6 +37,23 @@ fun Modifier.onKeyEventLikeEscape(action: () -> Unit) =
}
}

fun Modifier.onKeyUp(action: (keyEvent: KeyEvent) -> Boolean) =
composed(
inspectorInfo =
debugInspectorInfo {
name = "onKeyUp"
properties["action"] = action
},
) {
onKeyEvent {
return@onKeyEvent if (it.type == KeyEventType.KeyDown) {
action(it)
} else {
false
}
}
}

fun Modifier.focusableInNonTouchMode(
enabled: Boolean = true,
interactionSource: MutableInteractionSource? = null,
Expand Down
Loading