fix focus for android tv
This commit is contained in:
+1
@@ -39,6 +39,7 @@ fun AppBarWithSearchWithBackPreview() {
|
|||||||
@Composable
|
@Composable
|
||||||
fun InternalAppBarPreview() {
|
fun InternalAppBarPreview() {
|
||||||
SearchAppBar(
|
SearchAppBar(
|
||||||
|
Modifier,
|
||||||
TextFieldValue(""),
|
TextFieldValue(""),
|
||||||
FocusRequester(),
|
FocusRequester(),
|
||||||
{},
|
{},
|
||||||
|
|||||||
+20
-2
@@ -18,7 +18,7 @@ import androidx.compose.foundation.layout.calculateStartPadding
|
|||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.lazy.grid.GridCells
|
import androidx.compose.foundation.lazy.grid.GridCells
|
||||||
import androidx.compose.foundation.lazy.grid.items
|
import androidx.compose.foundation.lazy.grid.itemsIndexed
|
||||||
import androidx.compose.foundation.lazy.grid.rememberLazyGridState
|
import androidx.compose.foundation.lazy.grid.rememberLazyGridState
|
||||||
import androidx.compose.material3.Button
|
import androidx.compose.material3.Button
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
@@ -33,9 +33,12 @@ import androidx.compose.runtime.getValue
|
|||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.runtime.withFrameNanos
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.focus.FocusRequester
|
||||||
import androidx.compose.ui.focus.FocusState
|
import androidx.compose.ui.focus.FocusState
|
||||||
|
import androidx.compose.ui.focus.focusRequester
|
||||||
import androidx.compose.ui.focus.onFocusEvent
|
import androidx.compose.ui.focus.onFocusEvent
|
||||||
import androidx.compose.ui.graphics.Brush
|
import androidx.compose.ui.graphics.Brush
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
@@ -142,6 +145,7 @@ fun VideosInfo(
|
|||||||
emptyResource: StringResource,
|
emptyResource: StringResource,
|
||||||
onClick: (item: VideoItem) -> Unit
|
onClick: (item: VideoItem) -> Unit
|
||||||
) {
|
) {
|
||||||
|
val requester = remember { FocusRequester() }
|
||||||
val blur = LocalBlurState.current
|
val blur = LocalBlurState.current
|
||||||
val gridState = rememberLazyGridState()
|
val gridState = rememberLazyGridState()
|
||||||
val layoutDirection = LocalLayoutDirection.current
|
val layoutDirection = LocalLayoutDirection.current
|
||||||
@@ -160,11 +164,19 @@ fun VideosInfo(
|
|||||||
),
|
),
|
||||||
state = gridState
|
state = gridState
|
||||||
) {
|
) {
|
||||||
items(items) { item ->
|
itemsIndexed(
|
||||||
|
items,
|
||||||
|
key = { _, item -> item.id }
|
||||||
|
) { index, item ->
|
||||||
Poster(
|
Poster(
|
||||||
imageUrl = item.poster,
|
imageUrl = item.poster,
|
||||||
label = item.title,
|
label = item.title,
|
||||||
onClick = { onClick(item) },
|
onClick = { onClick(item) },
|
||||||
|
modifier = if (index == 0) {
|
||||||
|
Modifier.focusRequester(requester)
|
||||||
|
} else {
|
||||||
|
Modifier
|
||||||
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -172,6 +184,12 @@ fun VideosInfo(
|
|||||||
VideosNotFound(emptyResource)
|
VideosNotFound(emptyResource)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
LaunchedEffect(items) {
|
||||||
|
if (items.isNotEmpty()) {
|
||||||
|
withFrameNanos {}
|
||||||
|
requester.requestFocus()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
|||||||
+10
-1
@@ -35,6 +35,7 @@ import androidx.compose.material3.TopAppBarDefaults
|
|||||||
import androidx.compose.material3.TopAppBarScrollBehavior
|
import androidx.compose.material3.TopAppBarScrollBehavior
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.CompositionLocalProvider
|
import androidx.compose.runtime.CompositionLocalProvider
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.MutableState
|
import androidx.compose.runtime.MutableState
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
@@ -42,9 +43,12 @@ import androidx.compose.runtime.mutableStateOf
|
|||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.saveable.rememberSaveable
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.runtime.withFrameNanos
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.focus.FocusRequester
|
||||||
|
import androidx.compose.ui.focus.focusRequester
|
||||||
import androidx.compose.ui.layout.ContentScale
|
import androidx.compose.ui.layout.ContentScale
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
@@ -240,6 +244,7 @@ fun DetailsInfo(
|
|||||||
paddingValues: PaddingValues,
|
paddingValues: PaddingValues,
|
||||||
onWatchClicked: () -> Unit
|
onWatchClicked: () -> Unit
|
||||||
) {
|
) {
|
||||||
|
val requester = remember { FocusRequester() }
|
||||||
val blur = LocalBlurState.current
|
val blur = LocalBlurState.current
|
||||||
val showDetails = rememberSaveable { mutableStateOf(false) }
|
val showDetails = rememberSaveable { mutableStateOf(false) }
|
||||||
LazyColumn(
|
LazyColumn(
|
||||||
@@ -251,8 +256,12 @@ fun DetailsInfo(
|
|||||||
item {
|
item {
|
||||||
Button(
|
Button(
|
||||||
onClick = onWatchClicked,
|
onClick = onWatchClicked,
|
||||||
modifier = Modifier.fillMaxWidth().padding(8.dp)
|
modifier = Modifier.fillMaxWidth().padding(8.dp).focusRequester(requester)
|
||||||
) { Text(stringResource(Res.string.watch)) }
|
) { Text(stringResource(Res.string.watch)) }
|
||||||
|
LaunchedEffect(null) {
|
||||||
|
withFrameNanos {}
|
||||||
|
requester.requestFocus()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+30
-13
@@ -30,14 +30,18 @@ import androidx.compose.material3.TopAppBar
|
|||||||
import androidx.compose.material3.TopAppBarDefaults
|
import androidx.compose.material3.TopAppBarDefaults
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.CompositionLocalProvider
|
import androidx.compose.runtime.CompositionLocalProvider
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.saveable.rememberSaveable
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.runtime.withFrameNanos
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.focus.FocusRequester
|
||||||
|
import androidx.compose.ui.focus.focusRequester
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import org.jetbrains.compose.resources.painterResource
|
import org.jetbrains.compose.resources.painterResource
|
||||||
import org.jetbrains.compose.resources.stringResource
|
import org.jetbrains.compose.resources.stringResource
|
||||||
@@ -183,26 +187,39 @@ fun EpisodesOverviewList(
|
|||||||
ErrorAndRetry(errorMsg = errorCode) { loadLinks() }
|
ErrorAndRetry(errorMsg = errorCode) { loadLinks() }
|
||||||
} else if (episodes != null) {
|
} else if (episodes != null) {
|
||||||
val blur = LocalBlurState.current
|
val blur = LocalBlurState.current
|
||||||
|
val requester = remember { FocusRequester() }
|
||||||
ExpressiveLazyColumn(
|
ExpressiveLazyColumn(
|
||||||
modifier = Modifier.applyBlurSource(blur).fillMaxSize(),
|
modifier = Modifier.applyBlurSource(blur).fillMaxSize(),
|
||||||
contentPadding = contentPadding,
|
contentPadding = contentPadding,
|
||||||
content = {
|
content = {
|
||||||
section {
|
section {
|
||||||
episodes.mapIndexed { index, episode ->
|
itemsIndexed(
|
||||||
item(key = episode.episode.episode) {
|
episodes,
|
||||||
EpisodeInfo(
|
{ it.episode.episode }
|
||||||
onLaunch,
|
) { index, episode ->
|
||||||
episode,
|
EpisodeInfo(
|
||||||
onClipboardAction,
|
if (index == 0) {
|
||||||
index,
|
Modifier.focusRequester(requester)
|
||||||
onOfflineClicked,
|
} else {
|
||||||
isSaveMode
|
Modifier
|
||||||
)
|
},
|
||||||
}
|
onLaunch,
|
||||||
|
episode,
|
||||||
|
onClipboardAction,
|
||||||
|
index,
|
||||||
|
onOfflineClicked,
|
||||||
|
isSaveMode
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
LaunchedEffect(episodes) {
|
||||||
|
if (episodes.isNotEmpty()) {
|
||||||
|
withFrameNanos {}
|
||||||
|
requester.requestFocus()
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
Loading()
|
Loading()
|
||||||
}
|
}
|
||||||
@@ -211,6 +228,7 @@ fun EpisodesOverviewList(
|
|||||||
@OptIn(ExperimentalFoundationApi::class)
|
@OptIn(ExperimentalFoundationApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
private fun EpisodeInfo(
|
private fun EpisodeInfo(
|
||||||
|
modifier: Modifier,
|
||||||
onLaunch: (Episode) -> Unit,
|
onLaunch: (Episode) -> Unit,
|
||||||
ep: EpisodeWithOfflineInfo,
|
ep: EpisodeWithOfflineInfo,
|
||||||
onClipboardAction: (Episode) -> Unit,
|
onClipboardAction: (Episode) -> Unit,
|
||||||
@@ -219,8 +237,7 @@ private fun EpisodeInfo(
|
|||||||
isSaveMode: Boolean
|
isSaveMode: Boolean
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = modifier.fillMaxWidth()
|
||||||
.fillMaxWidth()
|
|
||||||
.combinedClickable(
|
.combinedClickable(
|
||||||
onClick = {
|
onClick = {
|
||||||
if (isSaveMode) {
|
if (isSaveMode) {
|
||||||
|
|||||||
+23
-4
@@ -14,7 +14,11 @@ import androidx.compose.runtime.Composable
|
|||||||
import androidx.compose.runtime.CompositionLocalProvider
|
import androidx.compose.runtime.CompositionLocalProvider
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.withFrameNanos
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.focus.FocusRequester
|
||||||
|
import androidx.compose.ui.focus.focusRequester
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import org.jetbrains.compose.resources.stringResource
|
import org.jetbrains.compose.resources.stringResource
|
||||||
import ru.shadowsparky.ui.LocalBlurState
|
import ru.shadowsparky.ui.LocalBlurState
|
||||||
@@ -73,20 +77,35 @@ fun SeasonsInfo(
|
|||||||
onClick: (Long) -> Unit
|
onClick: (Long) -> Unit
|
||||||
) {
|
) {
|
||||||
val blur = LocalBlurState.current
|
val blur = LocalBlurState.current
|
||||||
|
val requester = remember { FocusRequester() }
|
||||||
ExpressiveLazyColumn(
|
ExpressiveLazyColumn(
|
||||||
Modifier.applyBlurSource(blur).fillMaxWidth(),
|
Modifier.applyBlurSource(blur).fillMaxWidth(),
|
||||||
contentPadding = paddingValues,
|
contentPadding = paddingValues,
|
||||||
content = {
|
content = {
|
||||||
section {
|
section {
|
||||||
items(seasons) {
|
itemsIndexed(seasons) { index, season ->
|
||||||
SeasonInfo(it.title) { onClick(it.id) }
|
SeasonInfo(
|
||||||
|
if (index == 0) {
|
||||||
|
Modifier.focusRequester(requester)
|
||||||
|
} else {
|
||||||
|
Modifier
|
||||||
|
},
|
||||||
|
season.title
|
||||||
|
) { onClick(season.id) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
LaunchedEffect(seasons) {
|
||||||
|
if (seasons.isNotEmpty()) {
|
||||||
|
withFrameNanos {}
|
||||||
|
requester.requestFocus()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun SeasonInfo(titleText: String, onClick: () -> Unit) {
|
fun SeasonInfo(modifier: Modifier, titleText: String, onClick: () -> Unit) {
|
||||||
Text(titleText, modifier = Modifier.fillMaxWidth().clickable { onClick() }.padding(16.dp))
|
Text(titleText, modifier = modifier.fillMaxWidth().clickable { onClick() }.padding(16.dp))
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-1
@@ -92,6 +92,7 @@ fun SearchScreen(
|
|||||||
BaseMainScreen(
|
BaseMainScreen(
|
||||||
appBar = {
|
appBar = {
|
||||||
SearchAppBar(
|
SearchAppBar(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
textFieldValue = textFieldValue,
|
textFieldValue = textFieldValue,
|
||||||
onDone = { component.done(textFieldValue.text) },
|
onDone = { component.done(textFieldValue.text) },
|
||||||
onValueChange = { textFieldValue = it },
|
onValueChange = { textFieldValue = it },
|
||||||
@@ -213,6 +214,7 @@ expect fun Modifier.requestFocusOnDownEvent(focusRequester: FocusRequester): Mod
|
|||||||
@Composable
|
@Composable
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
fun SearchAppBar(
|
fun SearchAppBar(
|
||||||
|
modifier: Modifier,
|
||||||
textFieldValue: TextFieldValue,
|
textFieldValue: TextFieldValue,
|
||||||
focusRequester: FocusRequester,
|
focusRequester: FocusRequester,
|
||||||
onDone: () -> Unit,
|
onDone: () -> Unit,
|
||||||
@@ -228,7 +230,7 @@ fun SearchAppBar(
|
|||||||
}
|
}
|
||||||
val blur = LocalBlurState.current
|
val blur = LocalBlurState.current
|
||||||
AppBarWithSearch(
|
AppBarWithSearch(
|
||||||
modifier = Modifier.applyBlurEffect(blur),
|
modifier = modifier.applyBlurEffect(blur),
|
||||||
state = rememberSearchBarState(),
|
state = rememberSearchBarState(),
|
||||||
colors = ComponentDefaults.appBarWithSearchDefaults(),
|
colors = ComponentDefaults.appBarWithSearchDefaults(),
|
||||||
inputField = {
|
inputField = {
|
||||||
|
|||||||
+12
-1
@@ -12,9 +12,14 @@ import androiddev.apps.vbox.client.shared.generated.resources.user_tags
|
|||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.withFrameNanos
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.focus.FocusRequester
|
||||||
|
import androidx.compose.ui.focus.focusRequester
|
||||||
import org.jetbrains.compose.resources.stringResource
|
import org.jetbrains.compose.resources.stringResource
|
||||||
import ru.shadowsparky.ui.LocalBlurState
|
import ru.shadowsparky.ui.LocalBlurState
|
||||||
import ru.shadowsparky.ui.applyBlurSource
|
import ru.shadowsparky.ui.applyBlurSource
|
||||||
@@ -28,6 +33,7 @@ fun SettingsContent(comp: SettingsComponent, paddingValues: PaddingValues) {
|
|||||||
val tokenInfo by comp.tokenInfo.collectAsState(null)
|
val tokenInfo by comp.tokenInfo.collectAsState(null)
|
||||||
val externalPlayerState by comp.externalPlayerState.collectAsState(null)
|
val externalPlayerState by comp.externalPlayerState.collectAsState(null)
|
||||||
val blur = LocalBlurState.current
|
val blur = LocalBlurState.current
|
||||||
|
val requester = remember { FocusRequester() }
|
||||||
ExpressiveLazyColumn(
|
ExpressiveLazyColumn(
|
||||||
modifier = Modifier.applyBlurSource(blur),
|
modifier = Modifier.applyBlurSource(blur),
|
||||||
contentPadding = paddingValues,
|
contentPadding = paddingValues,
|
||||||
@@ -47,7 +53,8 @@ fun SettingsContent(comp: SettingsComponent, paddingValues: PaddingValues) {
|
|||||||
SwitchPreference(
|
SwitchPreference(
|
||||||
text = stringResource(Res.string.external_player_title),
|
text = stringResource(Res.string.external_player_title),
|
||||||
state = it,
|
state = it,
|
||||||
onStateChanged = { newState -> comp.setExternalPlayerState(newState) }
|
onStateChanged = { newState -> comp.setExternalPlayerState(newState) },
|
||||||
|
modifier = Modifier.focusRequester(requester)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -99,4 +106,8 @@ fun SettingsContent(comp: SettingsComponent, paddingValues: PaddingValues) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
LaunchedEffect(null) {
|
||||||
|
withFrameNanos {}
|
||||||
|
requester.requestFocus()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,11 +39,20 @@ class ExpressiveSectionScope {
|
|||||||
contentType: ((T) -> Any)? = null,
|
contentType: ((T) -> Any)? = null,
|
||||||
itemContent: @Composable (T) -> Unit
|
itemContent: @Composable (T) -> Unit
|
||||||
) {
|
) {
|
||||||
items.forEach { t ->
|
itemsIndexed(items, key, contentType) { _, t -> itemContent(t) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <T> itemsIndexed(
|
||||||
|
items: List<T>,
|
||||||
|
key: ((T) -> Any)? = null,
|
||||||
|
contentType: ((T) -> Any)? = null,
|
||||||
|
itemContent: @Composable (Int, T) -> Unit
|
||||||
|
) {
|
||||||
|
items.forEachIndexed { index, t ->
|
||||||
nodes += ExpressiveNode(
|
nodes += ExpressiveNode(
|
||||||
key = key?.invoke(t),
|
key = key?.invoke(t),
|
||||||
contentType = contentType?.invoke(t),
|
contentType = contentType?.invoke(t),
|
||||||
content = { itemContent(t) }
|
content = { itemContent(index, t) }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+14
-44
@@ -42,11 +42,10 @@ import androidx.compose.runtime.remember
|
|||||||
import androidx.compose.runtime.rememberCoroutineScope
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import androidx.compose.runtime.saveable.rememberSaveable
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.runtime.withFrameNanos
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.focus.FocusProperties
|
|
||||||
import androidx.compose.ui.focus.FocusRequester
|
import androidx.compose.ui.focus.FocusRequester
|
||||||
import androidx.compose.ui.focus.focusProperties
|
|
||||||
import androidx.compose.ui.focus.focusRequester
|
import androidx.compose.ui.focus.focusRequester
|
||||||
import androidx.compose.ui.focus.onFocusChanged
|
import androidx.compose.ui.focus.onFocusChanged
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
@@ -144,10 +143,7 @@ fun DefaultControls(
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
modifier = Modifier.fillMaxWidth()
|
modifier = Modifier.fillMaxWidth()
|
||||||
.focusableWithBorder(focus.slider) {
|
.focusableWithBorder(focus.slider)
|
||||||
next = if (audioTracksState.isNotEmpty()) focus.audioTrack else focus.lock
|
|
||||||
previous = focus.next
|
|
||||||
}
|
|
||||||
.onPreviewKeyEvent {
|
.onPreviewKeyEvent {
|
||||||
if (it.type == KeyEventType.KeyDown) {
|
if (it.type == KeyEventType.KeyDown) {
|
||||||
when (it.key) {
|
when (it.key) {
|
||||||
@@ -179,10 +175,7 @@ private fun LockUiButton(
|
|||||||
) {
|
) {
|
||||||
IconButton(
|
IconButton(
|
||||||
onClick = { onChanged(!state) },
|
onClick = { onChanged(!state) },
|
||||||
modifier = Modifier.focusableWithBorder(focusContainer.lock) {
|
modifier = Modifier.focusableWithBorder(focusContainer.lock)
|
||||||
next = focusContainer.prev
|
|
||||||
previous = if (hasAudioTrack) focusContainer.audioTrack else focusContainer.slider
|
|
||||||
}
|
|
||||||
) {
|
) {
|
||||||
val icon = if (state) Res.drawable.lock_24px else Res.drawable.lock_open_24px
|
val icon = if (state) Res.drawable.lock_24px else Res.drawable.lock_open_24px
|
||||||
Icon(painterResource(icon), null, tint = Color.White)
|
Icon(painterResource(icon), null, tint = Color.White)
|
||||||
@@ -202,10 +195,7 @@ private fun AudioTracksButton(
|
|||||||
val tracks = tracks.map { track -> track to FocusRequester() }
|
val tracks = tracks.map { track -> track to FocusRequester() }
|
||||||
IconButton(
|
IconButton(
|
||||||
onClick = { expanded = true },
|
onClick = { expanded = true },
|
||||||
modifier = Modifier.focusableWithBorder(focusContainer.audioTrack) {
|
modifier = Modifier.focusableWithBorder(focusContainer.audioTrack)
|
||||||
next = focusContainer.lock
|
|
||||||
previous = focusContainer.slider
|
|
||||||
}
|
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
painter = painterResource(Res.drawable.music_note_24px),
|
painter = painterResource(Res.drawable.music_note_24px),
|
||||||
@@ -239,10 +229,7 @@ private fun AudioTracksButton(
|
|||||||
expanded = false
|
expanded = false
|
||||||
onSelect(track.id)
|
onSelect(track.id)
|
||||||
},
|
},
|
||||||
modifier = Modifier.focusableWithBorder(requester) {
|
modifier = Modifier.focusableWithBorder(requester)
|
||||||
previous = tracks.getOrNull(i - 1)?.second ?: focusContainer.audioTrack
|
|
||||||
next = tracks.getOrNull(i + 1)?.second ?: focusContainer.audioTrack
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -311,10 +298,7 @@ private fun BoxScope.CenterControls(
|
|||||||
controller.seekTo(0)
|
controller.seekTo(0)
|
||||||
controller.play()
|
controller.play()
|
||||||
}
|
}
|
||||||
}, modifier = Modifier.focusableWithBorder(focusContainer.next) {
|
}, modifier = Modifier.focusableWithBorder(focusContainer.next)
|
||||||
previous = focusContainer.lock
|
|
||||||
next = focusContainer.slider
|
|
||||||
}
|
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
painter = painterResource(Res.drawable.replay_24px),
|
painter = painterResource(Res.drawable.replay_24px),
|
||||||
@@ -333,10 +317,7 @@ private fun BoxScope.CenterControls(
|
|||||||
IconButton(
|
IconButton(
|
||||||
onClick = {
|
onClick = {
|
||||||
scope.launch { controller.seek(true) }
|
scope.launch { controller.seek(true) }
|
||||||
}, modifier = Modifier.focusableWithBorder(focusContainer.prev) {
|
}, modifier = Modifier.focusableWithBorder(focusContainer.prev)
|
||||||
next = focusContainer.next
|
|
||||||
previous = focusContainer.play
|
|
||||||
}
|
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = "-$DEF_SEEK_TIME",
|
text = "-$DEF_SEEK_TIME",
|
||||||
@@ -350,10 +331,7 @@ private fun BoxScope.CenterControls(
|
|||||||
scope.launch {
|
scope.launch {
|
||||||
if (state.isPlaying) controller.pause() else controller.play()
|
if (state.isPlaying) controller.pause() else controller.play()
|
||||||
}
|
}
|
||||||
}, modifier = Modifier.focusableWithBorder(focusContainer.next) {
|
}, modifier = Modifier.focusableWithBorder(focusContainer.play)
|
||||||
next = focusContainer.next
|
|
||||||
previous = focusContainer.prev
|
|
||||||
}
|
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
painter = painterResource(
|
painter = painterResource(
|
||||||
@@ -371,10 +349,7 @@ private fun BoxScope.CenterControls(
|
|||||||
IconButton(
|
IconButton(
|
||||||
onClick = {
|
onClick = {
|
||||||
scope.launch { controller.seek(false) }
|
scope.launch { controller.seek(false) }
|
||||||
}, modifier = Modifier.focusableWithBorder(focusContainer.next) {
|
}, modifier = Modifier.focusableWithBorder(focusContainer.next)
|
||||||
next = focusContainer.slider
|
|
||||||
previous = focusContainer.play
|
|
||||||
}
|
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = "+$DEF_SEEK_TIME",
|
text = "+$DEF_SEEK_TIME",
|
||||||
@@ -383,9 +358,8 @@ private fun BoxScope.CenterControls(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
LaunchedEffect(null) {
|
LaunchedEffect(null) {
|
||||||
delay(1_000)
|
withFrameNanos {}
|
||||||
val result = focusContainer.play.requestFocus()
|
focusContainer.play.requestFocus()
|
||||||
println("DefaultControls focus changed? $result")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -434,18 +408,14 @@ private class FocusContainer(
|
|||||||
)
|
)
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun Modifier.focusableWithBorder(
|
private fun Modifier.focusableWithBorder(requester: FocusRequester): Modifier {
|
||||||
requester: FocusRequester,
|
|
||||||
focusProperties: FocusProperties.() -> Unit
|
|
||||||
): Modifier {
|
|
||||||
var isFocused by remember { mutableStateOf(false) }
|
var isFocused by remember { mutableStateOf(false) }
|
||||||
return focusRequester(requester)
|
return this.focusRequester(requester)
|
||||||
.focusProperties(focusProperties)
|
|
||||||
.onFocusChanged { isFocused = it.isFocused }
|
.onFocusChanged { isFocused = it.isFocused }
|
||||||
.border(
|
.border(
|
||||||
width = 2.dp,
|
width = 2.dp,
|
||||||
color = if (isFocused) {
|
color = if (isFocused) {
|
||||||
MaterialTheme.colorScheme.primary.copy(alpha = 0.5f)
|
MaterialTheme.colorScheme.primary
|
||||||
} else {
|
} else {
|
||||||
Color.Transparent
|
Color.Transparent
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user