Skip to content

Commit a26ae2e

Browse files
feat: support shorts tab
1 parent 900038d commit a26ae2e

File tree

5 files changed

+137
-3
lines changed

5 files changed

+137
-3
lines changed

android/src/main/kotlin/project/pipepipe/app/ui/screens/ChannelScreen.kt

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -63,8 +63,12 @@ fun ChannelScreen(
6363
val uiState by viewModel.uiState.collectAsState()
6464
val tabs = uiState.channelInfo?.tabs.orEmpty()
6565
val hasDescription = !uiState.channelInfo?.description.isNullOrEmpty()
66-
val tabTypes = remember(tabs, hasDescription) {
66+
val filterShorts = remember { SharedContext.settingsManager.getBoolean("filter_shorts_key", false) }
67+
val tabTypes = remember(tabs, hasDescription, filterShorts) {
6768
val types = tabs.map { it.type }.toMutableList()
69+
if (filterShorts) {
70+
types.removeAll { it == ChannelTabType.SHORTS }
71+
}
6872
if (hasDescription) {
6973
types.add(ChannelTabType.INFO)
7074
}
@@ -77,13 +81,15 @@ fun ChannelScreen(
7781
val scope = rememberCoroutineScope()
7882
val videoListState = rememberLazyListState()
7983
val liveListState = rememberLazyListState()
84+
val shortsListState = rememberLazyListState()
8085
var showGroupDialog by remember { mutableStateOf(false) }
8186
var showMoreMenu by remember { mutableStateOf(false) }
8287
var notificationMode by remember { mutableStateOf(0L) }
8388

8489
val deferredTabLoaders = remember(channelUrl, serviceId, viewModel) {
8590
mapOf(
86-
ChannelTabType.LIVES to { viewModel.loadChannelLiveTab(uiState.channelInfo!!.tabs.first{it.type == ChannelTabType.LIVES}.url, serviceId) },
91+
ChannelTabType.LIVE to { viewModel.loadChannelLiveTab(uiState.channelInfo!!.tabs.first{it.type == ChannelTabType.LIVE}.url, serviceId) },
92+
ChannelTabType.SHORTS to { viewModel.loadChannelShortsTab(uiState.channelInfo!!.tabs.first{it.type == ChannelTabType.SHORTS}.url, serviceId) },
8793
ChannelTabType.PLAYLISTS to { viewModel.loadChannelPlaylistTab(uiState.channelInfo!!.tabs.first{it.type == ChannelTabType.PLAYLISTS}.url, serviceId) },
8894
ChannelTabType.ALBUMS to { viewModel.loadChannelAlbumTab(uiState.channelInfo!!.tabs.first{it.type == ChannelTabType.ALBUMS}.url, serviceId) },
8995
// 在这里继续为其它 Tab 注册加载逻辑
@@ -244,7 +250,7 @@ fun ChannelScreen(
244250
}
245251
}
246252

247-
ChannelTabType.LIVES -> TabContent(
253+
ChannelTabType.LIVE -> TabContent(
248254
listState = liveListState,
249255
items = uiState.liveTab.itemList,
250256
isLoading = uiState.common.isLoading,
@@ -260,6 +266,22 @@ fun ChannelScreen(
260266
emptyMessage = stringResource(MR.strings.empty_channel_lives)
261267
)
262268

269+
ChannelTabType.SHORTS -> TabContent(
270+
listState = shortsListState,
271+
items = uiState.shortsTab.itemList,
272+
isLoading = uiState.common.isLoading,
273+
hasMore = uiState.shortsTab.nextPageUrl != null,
274+
onLoadMore = { viewModel.loadShortsTabMoreItems(serviceId) },
275+
getUrl = { it.url },
276+
onItemClick = { item ->
277+
SharedContext.sharedVideoDetailViewModel.loadVideoDetails(
278+
item.url,
279+
item.serviceId
280+
)
281+
},
282+
emptyMessage = stringResource(MR.strings.empty_channel_shorts)
283+
)
284+
263285
ChannelTabType.VIDEOS -> TabContent(
264286
listState = videoListState,
265287
items = uiState.videoTab.itemList,

android/src/main/kotlin/project/pipepipe/app/ui/viewmodel/ChannelViewModel.kt

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,19 @@ class ChannelViewModel : ViewModel() {
3636
}
3737
}
3838

39+
fun loadChannelShortsTab(url: String, serviceId: String) {
40+
viewModelScope.launch {
41+
sharedViewModel.loadChannelShortsTab(url, serviceId)
42+
}
43+
}
44+
45+
fun loadShortsTabMoreItems(serviceId: String) {
46+
viewModelScope.launch {
47+
sharedViewModel.loadShortsTabMoreItems(serviceId)
48+
}
49+
}
50+
51+
3952
fun loadChannelPlaylistTab(url: String, serviceId: String) {
4053
viewModelScope.launch {
4154
sharedViewModel.loadChannelPlaylistTab(url, serviceId)

library/src/commonMain/kotlin/project/pipepipe/app/uistate/BaseUiState.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ data class ChannelUiState(
115115
val channelInfo: ChannelInfo? = null,
116116
val videoTab: ListUiState<StreamInfo> = ListUiState(),
117117
val liveTab: ListUiState<StreamInfo> = ListUiState(),
118+
val shortsTab: ListUiState<StreamInfo> = ListUiState(),
118119
val playlistTab: ListUiState<PlaylistInfo> = ListUiState(),
119120
val albumTab: ListUiState<PlaylistInfo> = ListUiState(),
120121
val isSubscribed: Boolean = false

library/src/commonMain/kotlin/project/pipepipe/app/viewmodel/ChannelViewModel.kt

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,103 @@ class ChannelViewModel : BaseViewModel<ChannelUiState>(ChannelUiState()) {
212212
}
213213
}
214214

215+
suspend fun loadChannelShortsTab(url: String, serviceId: String) {
216+
setState {
217+
it.copy(
218+
common = it.common.copy(isLoading = true)
219+
)
220+
}
221+
val result = withContext(Dispatchers.IO) {
222+
executeJobFlow(
223+
SupportedJobType.FETCH_FIRST_PAGE,
224+
url,
225+
serviceId
226+
)
227+
}
228+
229+
// Check for fatal error first
230+
if (result.fatalError != null) {
231+
setState {
232+
it.copy(
233+
common = it.common.copy(
234+
isLoading = false,
235+
error = ErrorInfo(result.fatalError!!.errorId!!, result.fatalError!!.code, serviceId)
236+
)
237+
)
238+
}
239+
return
240+
}
241+
242+
// Apply filters
243+
val rawItems = (result.pagedData?.itemList as? List<StreamInfo>).orEmpty()
244+
val (filteredItems, _) = FilterHelper.filterStreamInfoList(
245+
rawItems,
246+
FilterHelper.FilterScope.CHANNELS
247+
)
248+
249+
setState {
250+
it.copy(
251+
common = it.common.copy(
252+
isLoading = false,
253+
error = null
254+
),
255+
shortsTab = ListUiState(
256+
itemList = filteredItems,
257+
nextPageUrl = result.pagedData?.nextPageUrl
258+
)
259+
)
260+
}
261+
}
262+
263+
suspend fun loadShortsTabMoreItems(serviceId: String) {
264+
val nextUrl = uiState.value.shortsTab.nextPageUrl ?: return
265+
setState {
266+
it.copy(
267+
common = it.common.copy(isLoading = true)
268+
)
269+
}
270+
val result = withContext(Dispatchers.IO) {
271+
executeJobFlow(
272+
SupportedJobType.FETCH_GIVEN_PAGE,
273+
nextUrl,
274+
serviceId
275+
)
276+
}
277+
278+
// Check for fatal error first
279+
if (result.fatalError != null) {
280+
setState {
281+
it.copy(
282+
common = it.common.copy(
283+
isLoading = false,
284+
error = ErrorInfo(result.fatalError!!.errorId!!, result.fatalError!!.code, serviceId)
285+
)
286+
)
287+
}
288+
return
289+
}
290+
291+
// Apply filters
292+
val rawItems = (result.pagedData?.itemList as? List<StreamInfo>).orEmpty()
293+
val (filteredItems, _) = FilterHelper.filterStreamInfoList(
294+
rawItems,
295+
FilterHelper.FilterScope.CHANNELS
296+
)
297+
298+
setState {
299+
it.copy(
300+
common = it.common.copy(
301+
isLoading = false,
302+
error = null
303+
),
304+
shortsTab = it.shortsTab.copy(
305+
itemList = it.shortsTab.itemList + filteredItems,
306+
nextPageUrl = result.pagedData?.nextPageUrl
307+
)
308+
)
309+
}
310+
}
311+
215312
suspend fun loadChannelPlaylistTab(url: String, serviceId: String) {
216313
setState {
217314
it.copy(

library/src/commonMain/moko-resources/base/strings.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -412,6 +412,7 @@
412412
<string name="empty_history">No watch history yet</string>
413413
<string name="empty_channel_videos">No videos</string>
414414
<string name="empty_channel_lives">No live streams</string>
415+
<string name="empty_channel_shorts">No shorts</string>
415416
<string name="empty_channel_playlists">No playlists</string>
416417
<string name="empty_channel_albums">No albums</string>
417418
<string name="subscription_export_json_title">Export subscriptions (JSON)</string>

0 commit comments

Comments
 (0)