Skip to content

Commit e064c5d

Browse files
committed
Address PR comments
1 parent 514bdb2 commit e064c5d

7 files changed

Lines changed: 198 additions & 155 deletions

File tree

modules/services/localization/src/main/res/values/strings.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -275,7 +275,7 @@
275275
<string name="tv_your_podcasts_empty_title">Time to fill this up</string>
276276
<string name="tv_your_podcasts_empty_subtitle">Followed podcasts show up here, ready to play.</string>
277277
<string name="tv_your_podcasts_empty_action_title">Discover podcasts</string>
278-
<string name="tv_folder_empty_message">Edit your folder in the mobile app, they’ll be waiting here when you’re done.</string>
278+
<string name="tv_folder_empty_message">Add podcasts to this folder in the mobile app and they’ll show up here.</string>
279279
<string name="unavailable">Unavailable</string>
280280
<string name="browse_podcasts">Browse podcasts</string>
281281
<string name="pocket_casts_plus_badge">Pocket Casts Plus badge</string>

tv/src/main/java/au/com/shiftyjelly/pocketcasts/component/TvFolderCard.kt

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import androidx.compose.ui.Alignment
1616
import androidx.compose.ui.Modifier
1717
import androidx.compose.ui.draw.clip
1818
import androidx.compose.ui.graphics.Color
19+
import androidx.compose.ui.text.style.TextOverflow
1920
import androidx.compose.ui.tooling.preview.Devices
2021
import androidx.compose.ui.tooling.preview.Preview
2122
import androidx.compose.ui.unit.dp
@@ -54,15 +55,15 @@ fun TvFolderCard(
5455
.aspectRatio(1f),
5556
) {
5657
val cardWidth = maxWidth
57-
val coverSize = cardWidth * 0.32f
58-
val coverSpacing = cardWidth * 0.024f
59-
val coverCornerRadius = cardWidth * 0.024f
58+
val coverSize = cardWidth * COVER_SIZE_RATIO
59+
val coverSpacing = cardWidth * COVER_SPACING_RATIO
60+
val coverCornerRadius = cardWidth * COVER_SPACING_RATIO
6061

6162
Column(
6263
verticalArrangement = Arrangement.spacedBy(coverSpacing),
6364
modifier = Modifier
6465
.align(Alignment.TopCenter)
65-
.padding(top = cardWidth * 0.096f),
66+
.padding(top = cardWidth * COVERS_TOP_PADDING_RATIO),
6667
) {
6768
repeat(2) { row ->
6869
Row(horizontalArrangement = Arrangement.spacedBy(coverSpacing)) {
@@ -83,15 +84,22 @@ fun TvFolderCard(
8384
style = TvTextStyles.FolderCardTitle,
8485
color = Color.White,
8586
maxLines = 1,
87+
softWrap = false,
88+
overflow = TextOverflow.Ellipsis,
8689
modifier = Modifier
8790
.align(Alignment.BottomCenter)
88-
.padding(horizontal = 16.dp, vertical = cardWidth * 0.064f)
91+
.padding(horizontal = 16.dp, vertical = cardWidth * TITLE_VERTICAL_PADDING_RATIO)
8992
.fillMaxWidth(),
9093
)
9194
}
9295
}
9396
}
9497

98+
private const val COVER_SIZE_RATIO = 0.32f
99+
private const val COVER_SPACING_RATIO = 0.024f
100+
private const val COVERS_TOP_PADDING_RATIO = 0.096f
101+
private const val TITLE_VERTICAL_PADDING_RATIO = 0.064f
102+
95103
@Preview(device = Devices.TV_1080p)
96104
@Composable
97105
private fun TvFolderCardPreview() {
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
package au.com.shiftyjelly.pocketcasts.component
2+
3+
import androidx.compose.foundation.focusGroup
4+
import androidx.compose.foundation.layout.Arrangement
5+
import androidx.compose.foundation.layout.Column
6+
import androidx.compose.foundation.layout.PaddingValues
7+
import androidx.compose.foundation.layout.padding
8+
import androidx.compose.foundation.lazy.grid.GridCells
9+
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
10+
import androidx.compose.foundation.lazy.grid.rememberLazyGridState
11+
import androidx.compose.runtime.Composable
12+
import androidx.compose.runtime.LaunchedEffect
13+
import androidx.compose.runtime.getValue
14+
import androidx.compose.runtime.mutableStateOf
15+
import androidx.compose.runtime.remember
16+
import androidx.compose.runtime.saveable.rememberSaveable
17+
import androidx.compose.runtime.setValue
18+
import androidx.compose.ui.Modifier
19+
import androidx.compose.ui.focus.FocusRequester
20+
import androidx.compose.ui.focus.focusProperties
21+
import androidx.compose.ui.focus.focusRequester
22+
import androidx.compose.ui.focus.onFocusChanged
23+
import androidx.compose.ui.graphics.Color
24+
import androidx.compose.ui.unit.dp
25+
import androidx.tv.material3.Text
26+
import au.com.shiftyjelly.pocketcasts.theme.TvTextStyles
27+
28+
@Composable
29+
internal fun TvPodcastGridScaffold(
30+
title: String,
31+
itemKeys: List<Any>,
32+
modifier: Modifier = Modifier,
33+
autoFocusFirstItem: Boolean = false,
34+
itemContent: @Composable (index: Int, itemModifier: Modifier) -> Unit,
35+
) {
36+
Column(modifier = modifier) {
37+
Text(
38+
text = title,
39+
style = TvTextStyles.ScreenTitle,
40+
color = Color.White,
41+
modifier = Modifier.padding(start = 32.dp, top = 8.dp, bottom = 10.dp),
42+
)
43+
val gridState = rememberLazyGridState()
44+
var lastFocusedKey by rememberSaveable { mutableStateOf<String?>(null) }
45+
val focusRequesters = remember(itemKeys.size) { List(itemKeys.size) { FocusRequester() } }
46+
47+
if (autoFocusFirstItem) {
48+
LaunchedEffect(Unit) {
49+
focusRequesters.firstOrNull()?.requestFocus()
50+
}
51+
}
52+
53+
LazyVerticalGrid(
54+
state = gridState,
55+
columns = GridCells.Fixed(GRID_COLUMNS),
56+
horizontalArrangement = Arrangement.spacedBy(16.dp),
57+
verticalArrangement = Arrangement.spacedBy(16.dp),
58+
contentPadding = PaddingValues(start = 32.dp, top = 16.dp, end = 32.dp, bottom = 32.dp),
59+
modifier = Modifier
60+
.focusGroup()
61+
.focusProperties {
62+
onEnter = {
63+
val visible = gridState.layoutInfo.visibleItemsInfo
64+
val target = itemKeys.indexOfFirst { it.toString() == lastFocusedKey }
65+
.takeIf { index -> index >= 0 && visible.any { it.index == index } }
66+
?: visible.firstOrNull()?.index
67+
target?.let { focusRequesters.getOrNull(it)?.requestFocus() }
68+
}
69+
},
70+
) {
71+
items(
72+
count = itemKeys.size,
73+
key = { index -> itemKeys[index] },
74+
) { index ->
75+
itemContent(
76+
index,
77+
Modifier
78+
.focusRequester(focusRequesters[index])
79+
.onFocusChanged { focusState ->
80+
if (focusState.hasFocus) {
81+
lastFocusedKey = itemKeys[index].toString()
82+
}
83+
},
84+
)
85+
}
86+
}
87+
}
88+
}
89+
90+
private const val GRID_COLUMNS = 6

tv/src/main/java/au/com/shiftyjelly/pocketcasts/podcasts/TvFolderDetailScreen.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import androidx.compose.ui.unit.dp
3232
import androidx.tv.material3.Button
3333
import androidx.tv.material3.MaterialTheme
3434
import androidx.tv.material3.Text
35+
import au.com.shiftyjelly.pocketcasts.component.TvPodcastGridScaffold
3536
import au.com.shiftyjelly.pocketcasts.component.TvPodcastTile
3637
import au.com.shiftyjelly.pocketcasts.compose.AppTheme
3738
import au.com.shiftyjelly.pocketcasts.compose.loading.LoadingView
@@ -52,7 +53,7 @@ fun TvFolderDetailScreen(
5253
modifier: Modifier = Modifier,
5354
) {
5455
var uiState by remember(folderUuid) { mutableStateOf<TvFolderDetailUiState>(TvFolderDetailUiState.Loading) }
55-
LaunchedEffect(folderUuid, getFolderPodcasts) {
56+
LaunchedEffect(folderUuid) {
5657
val podcasts = getFolderPodcasts(folderUuid)
5758
uiState = if (podcasts.isEmpty()) TvFolderDetailUiState.Empty else TvFolderDetailUiState.Loaded(podcasts)
5859
}

tv/src/main/java/au/com/shiftyjelly/pocketcasts/podcasts/TvYourPodcastsScreen.kt

Lines changed: 6 additions & 110 deletions
Original file line numberDiff line numberDiff line change
@@ -7,42 +7,26 @@ import androidx.compose.animation.fadeIn
77
import androidx.compose.animation.fadeOut
88
import androidx.compose.animation.togetherWith
99
import androidx.compose.foundation.background
10-
import androidx.compose.foundation.focusGroup
11-
import androidx.compose.foundation.layout.Arrangement
1210
import androidx.compose.foundation.layout.Box
13-
import androidx.compose.foundation.layout.Column
14-
import androidx.compose.foundation.layout.PaddingValues
1511
import androidx.compose.foundation.layout.fillMaxSize
1612
import androidx.compose.foundation.layout.fillMaxWidth
17-
import androidx.compose.foundation.layout.padding
18-
import androidx.compose.foundation.lazy.grid.GridCells
19-
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
20-
import androidx.compose.foundation.lazy.grid.items
21-
import androidx.compose.foundation.lazy.grid.rememberLazyGridState
2213
import androidx.compose.runtime.Composable
23-
import androidx.compose.runtime.LaunchedEffect
2414
import androidx.compose.runtime.getValue
2515
import androidx.compose.runtime.mutableStateOf
26-
import androidx.compose.runtime.remember
2716
import androidx.compose.runtime.saveable.listSaver
2817
import androidx.compose.runtime.saveable.rememberSaveable
2918
import androidx.compose.runtime.setValue
3019
import androidx.compose.ui.Modifier
31-
import androidx.compose.ui.focus.FocusRequester
32-
import androidx.compose.ui.focus.focusProperties
33-
import androidx.compose.ui.focus.focusRequester
34-
import androidx.compose.ui.focus.onFocusChanged
3520
import androidx.compose.ui.graphics.Color
3621
import androidx.compose.ui.res.stringResource
3722
import androidx.compose.ui.tooling.preview.Devices
3823
import androidx.compose.ui.tooling.preview.Preview
39-
import androidx.compose.ui.unit.dp
4024
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
4125
import androidx.lifecycle.compose.collectAsStateWithLifecycle
4226
import androidx.tv.material3.MaterialTheme
43-
import androidx.tv.material3.Text
4427
import au.com.shiftyjelly.pocketcasts.component.TvEmptyState
4528
import au.com.shiftyjelly.pocketcasts.component.TvFolderCard
29+
import au.com.shiftyjelly.pocketcasts.component.TvPodcastGridScaffold
4630
import au.com.shiftyjelly.pocketcasts.component.TvPodcastTile
4731
import au.com.shiftyjelly.pocketcasts.compose.AppTheme
4832
import au.com.shiftyjelly.pocketcasts.compose.loading.LoadingView
@@ -52,7 +36,6 @@ import au.com.shiftyjelly.pocketcasts.models.to.FolderItem
5236
import au.com.shiftyjelly.pocketcasts.models.type.PodcastsSortType
5337
import au.com.shiftyjelly.pocketcasts.repositories.images.PodcastImage
5438
import au.com.shiftyjelly.pocketcasts.theme.TvColors
55-
import au.com.shiftyjelly.pocketcasts.theme.TvTextStyles
5639
import au.com.shiftyjelly.pocketcasts.ui.theme.Theme
5740
import java.util.Date
5841
import au.com.shiftyjelly.pocketcasts.localization.R as LR
@@ -81,7 +64,6 @@ fun TvYourPodcastsScreen(
8164
} else {
8265
TvYourPodcastsContent(
8366
uiState = uiState,
84-
getFolderCoverUuids = viewModel::folderCoverUuids,
8567
onNavigateToHome = onNavigateToHome,
8668
onOpenFolder = { openedFolder = OpenedFolder(it.uuid, it.name) },
8769
modifier = modifier,
@@ -99,7 +81,6 @@ private val OpenedFolderSaver = listSaver<OpenedFolder?, String>(
9981
@Composable
10082
private fun TvYourPodcastsContent(
10183
uiState: TvYourPodcastsUiState,
102-
getFolderCoverUuids: suspend (String) -> List<String>,
10384
onNavigateToHome: () -> Unit,
10485
onOpenFolder: (Folder) -> Unit,
10586
modifier: Modifier = Modifier,
@@ -130,7 +111,6 @@ private fun TvYourPodcastsContent(
130111

131112
is TvYourPodcastsUiState.Loaded -> TvYourPodcastsGrid(
132113
items = state.items,
133-
getFolderCoverUuids = getFolderCoverUuids,
134114
onOpenFolder = onOpenFolder,
135115
modifier = Modifier.fillMaxSize(),
136116
)
@@ -141,7 +121,6 @@ private fun TvYourPodcastsContent(
141121
@Composable
142122
private fun TvYourPodcastsGrid(
143123
items: List<FolderItem>,
144-
getFolderCoverUuids: suspend (String) -> List<String>,
145124
onOpenFolder: (Folder) -> Unit,
146125
modifier: Modifier = Modifier,
147126
) {
@@ -159,98 +138,17 @@ private fun TvYourPodcastsGrid(
159138
modifier = itemModifier,
160139
)
161140

162-
is FolderItem.Folder -> FolderGridItem(
141+
is FolderItem.Folder -> TvFolderCard(
163142
folder = item.folder,
164-
getFolderCoverUuids = getFolderCoverUuids,
165-
onOpenFolder = onOpenFolder,
143+
coverUrls = item.podcasts.take(FOLDER_COVER_COUNT).map { PodcastImage.getMediumArtworkUrl(it.uuid) },
144+
onClick = { onOpenFolder(item.folder) },
166145
modifier = itemModifier,
167146
)
168147
}
169148
}
170149
}
171150

172-
@Composable
173-
private fun FolderGridItem(
174-
folder: Folder,
175-
getFolderCoverUuids: suspend (String) -> List<String>,
176-
onOpenFolder: (Folder) -> Unit,
177-
modifier: Modifier = Modifier,
178-
) {
179-
var coverUrls by remember(folder.uuid) { mutableStateOf(emptyList<String>()) }
180-
LaunchedEffect(folder.uuid, getFolderCoverUuids) {
181-
coverUrls = getFolderCoverUuids(folder.uuid).map(PodcastImage::getMediumArtworkUrl)
182-
}
183-
TvFolderCard(
184-
folder = folder,
185-
coverUrls = coverUrls,
186-
onClick = { onOpenFolder(folder) },
187-
modifier = modifier,
188-
)
189-
}
190-
191-
@Composable
192-
internal fun TvPodcastGridScaffold(
193-
title: String,
194-
itemKeys: List<Any>,
195-
modifier: Modifier = Modifier,
196-
autoFocusFirstItem: Boolean = false,
197-
itemContent: @Composable (index: Int, itemModifier: Modifier) -> Unit,
198-
) {
199-
Column(modifier = modifier) {
200-
Text(
201-
text = title,
202-
style = TvTextStyles.ScreenTitle,
203-
color = Color.White,
204-
modifier = Modifier.padding(start = 32.dp, top = 8.dp, bottom = 10.dp),
205-
)
206-
val gridState = rememberLazyGridState()
207-
var lastFocusedKey by rememberSaveable { mutableStateOf<String?>(null) }
208-
val focusRequesters = remember(itemKeys.size) { List(itemKeys.size) { FocusRequester() } }
209-
210-
if (autoFocusFirstItem) {
211-
LaunchedEffect(focusRequesters) {
212-
focusRequesters.firstOrNull()?.requestFocus()
213-
}
214-
}
215-
216-
LazyVerticalGrid(
217-
state = gridState,
218-
columns = GridCells.Fixed(GRID_COLUMNS),
219-
horizontalArrangement = Arrangement.spacedBy(16.dp),
220-
verticalArrangement = Arrangement.spacedBy(16.dp),
221-
contentPadding = PaddingValues(start = 32.dp, top = 16.dp, end = 32.dp, bottom = 32.dp),
222-
modifier = Modifier
223-
.focusGroup()
224-
.focusProperties {
225-
onEnter = {
226-
val visible = gridState.layoutInfo.visibleItemsInfo
227-
val target = itemKeys.indexOfFirst { it.toString() == lastFocusedKey }
228-
.takeIf { index -> index >= 0 && visible.any { it.index == index } }
229-
?: visible.firstOrNull()?.index
230-
target?.let { focusRequesters.getOrNull(it)?.requestFocus() }
231-
}
232-
},
233-
) {
234-
items(
235-
count = itemKeys.size,
236-
key = { index -> itemKeys[index] },
237-
) { index ->
238-
itemContent(
239-
index,
240-
Modifier
241-
.focusRequester(focusRequesters[index])
242-
.onFocusChanged { focusState ->
243-
if (focusState.hasFocus) {
244-
lastFocusedKey = itemKeys[index].toString()
245-
}
246-
},
247-
)
248-
}
249-
}
250-
}
251-
}
252-
253-
private const val GRID_COLUMNS = 6
151+
private const val FOLDER_COVER_COUNT = 4
254152

255153
@Preview(device = Devices.TV_1080p)
256154
@Composable
@@ -273,15 +171,14 @@ private fun TvYourPodcastsGridPreview() {
273171
deleted = false,
274172
syncModified = 0,
275173
),
276-
podcasts = emptyList(),
174+
podcasts = List(4) { index -> Podcast(uuid = "cover-$index") },
277175
),
278176
)
279177
repeat(11) { index ->
280178
add(FolderItem.Podcast(Podcast(uuid = "podcast-$index", title = "Podcast $index")))
281179
}
282180
},
283181
),
284-
getFolderCoverUuids = { emptyList() },
285182
onNavigateToHome = {},
286183
onOpenFolder = {},
287184
)
@@ -298,7 +195,6 @@ private fun TvYourPodcastsEmptyPreview() {
298195
Box(modifier = Modifier.background(TvColors.Dark)) {
299196
TvYourPodcastsContent(
300197
uiState = TvYourPodcastsUiState.Empty,
301-
getFolderCoverUuids = { emptyList() },
302198
onNavigateToHome = {},
303199
onOpenFolder = {},
304200
)

0 commit comments

Comments
 (0)