Skip to content

Commit c165e9a

Browse files
committed
Merge branch 'develop' into feature/mobile
# Conflicts: # bili-api/src/main/kotlin/dev/aaa1115910/biliapi/http/BiliHttpApi.kt # gradle/androidx.versions.toml
2 parents 5befdd9 + f2d5b10 commit c165e9a

File tree

23 files changed

+1601
-26
lines changed

23 files changed

+1601
-26
lines changed

.idea/kotlinc.xml

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

app/src/main/kotlin/dev/aaa1115910/bv/BVApp.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import dev.aaa1115910.biliapi.repositories.AuthRepository
1616
import dev.aaa1115910.biliapi.repositories.ChannelRepository
1717
import dev.aaa1115910.biliapi.repositories.FavoriteRepository
1818
import dev.aaa1115910.biliapi.repositories.HistoryRepository
19+
import dev.aaa1115910.biliapi.repositories.IndexRepository
1920
import dev.aaa1115910.biliapi.repositories.LoginRepository
2021
import dev.aaa1115910.biliapi.repositories.RecommendVideoRepository
2122
import dev.aaa1115910.biliapi.repositories.SearchRepository
@@ -40,6 +41,7 @@ import dev.aaa1115910.bv.viewmodel.home.AnimeViewModel
4041
import dev.aaa1115910.bv.viewmodel.home.DynamicViewModel
4142
import dev.aaa1115910.bv.viewmodel.home.PopularViewModel
4243
import dev.aaa1115910.bv.viewmodel.home.RecommendViewModel
44+
import dev.aaa1115910.bv.viewmodel.index.AnimeIndexViewModel
4345
import dev.aaa1115910.bv.viewmodel.login.AppQrLoginViewModel
4446
import dev.aaa1115910.bv.viewmodel.login.SmsLoginViewModel
4547
import dev.aaa1115910.bv.viewmodel.search.SearchInputViewModel
@@ -158,6 +160,7 @@ val appModule = module {
158160
single { VideoDetailRepository(get(), get(), get()) }
159161
single { SeasonRepository(get()) }
160162
single { dev.aaa1115910.biliapi.repositories.UserRepository(get(), get()) }
163+
single { IndexRepository() }
161164
viewModel { DynamicViewModel(get(), get()) }
162165
viewModel { RecommendViewModel(get()) }
163166
viewModel { PopularViewModel(get()) }
@@ -178,6 +181,7 @@ val appModule = module {
178181
viewModel { VideoDetailViewModel(get()) }
179182
viewModel { MobileVideoPlayerViewModel(get(), get()) }
180183
viewModel { UserSwitchViewModel(get()) }
184+
viewModel { AnimeIndexViewModel(get()) }
181185
}
182186

183187
val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "Settings")
Lines changed: 335 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,335 @@
1+
package dev.aaa1115910.bv.component.index
2+
3+
import android.content.Context
4+
import androidx.compose.animation.AnimatedVisibility
5+
import androidx.compose.foundation.layout.Arrangement
6+
import androidx.compose.foundation.layout.Column
7+
import androidx.compose.foundation.layout.PaddingValues
8+
import androidx.compose.foundation.layout.Row
9+
import androidx.compose.foundation.layout.fillMaxSize
10+
import androidx.compose.foundation.layout.fillMaxWidth
11+
import androidx.compose.foundation.layout.heightIn
12+
import androidx.compose.foundation.layout.padding
13+
import androidx.compose.foundation.layout.size
14+
import androidx.compose.material.icons.Icons
15+
import androidx.compose.material.icons.filled.Check
16+
import androidx.compose.material3.AlertDialog
17+
import androidx.compose.material3.HorizontalDivider
18+
import androidx.compose.material3.Icon
19+
import androidx.compose.runtime.Composable
20+
import androidx.compose.runtime.LaunchedEffect
21+
import androidx.compose.runtime.getValue
22+
import androidx.compose.runtime.mutableStateOf
23+
import androidx.compose.runtime.remember
24+
import androidx.compose.runtime.setValue
25+
import androidx.compose.ui.Alignment
26+
import androidx.compose.ui.Modifier
27+
import androidx.compose.ui.focus.FocusRequester
28+
import androidx.compose.ui.focus.focusRequester
29+
import androidx.compose.ui.platform.LocalContext
30+
import androidx.compose.ui.tooling.preview.Preview
31+
import androidx.compose.ui.unit.dp
32+
import androidx.compose.ui.window.DialogProperties
33+
import androidx.tv.foundation.lazy.list.TvLazyColumn
34+
import androidx.tv.foundation.lazy.list.TvLazyRow
35+
import androidx.tv.foundation.lazy.list.items
36+
import androidx.tv.material3.ExperimentalTvMaterial3Api
37+
import androidx.tv.material3.FilterChip
38+
import androidx.tv.material3.MaterialTheme
39+
import androidx.tv.material3.Surface
40+
import androidx.tv.material3.Text
41+
import dev.aaa1115910.biliapi.http.entity.index.IndexOrder
42+
import dev.aaa1115910.biliapi.http.entity.index.animeIndexOrders
43+
import dev.aaa1115910.biliapi.http.entity.index.indexFilterArea
44+
import dev.aaa1115910.biliapi.http.entity.index.indexFilterCopyright
45+
import dev.aaa1115910.biliapi.http.entity.index.indexFilterIsFinish
46+
import dev.aaa1115910.biliapi.http.entity.index.indexFilterSeasonMonth
47+
import dev.aaa1115910.biliapi.http.entity.index.indexFilterSeasonStatus
48+
import dev.aaa1115910.biliapi.http.entity.index.indexFilterSeasonVersion
49+
import dev.aaa1115910.biliapi.http.entity.index.indexFilterSpokenLanguageType
50+
import dev.aaa1115910.biliapi.http.entity.index.indexFilterStyleIdsAnime
51+
import dev.aaa1115910.biliapi.http.entity.index.indexFilterYear
52+
import dev.aaa1115910.bv.R
53+
import dev.aaa1115910.bv.component.createCustomInitialFocusRestorerModifiers
54+
import dev.aaa1115910.bv.component.ifElse
55+
import dev.aaa1115910.bv.ui.theme.BVTheme
56+
57+
@OptIn(ExperimentalTvMaterial3Api::class)
58+
@Composable
59+
fun AnimeIndexFilter(
60+
modifier: Modifier = Modifier,
61+
show: Boolean,
62+
onDismissRequest: () -> Unit,
63+
order: IndexOrder,
64+
seasonVersion: Int,
65+
spokenLanguageType: Int,
66+
area: Int,
67+
isFinish: Int,
68+
copyright: Int,
69+
seasonStatus: Int,
70+
seasonMonth: Int,
71+
year: String,
72+
styleId: Int,
73+
desc: Boolean,
74+
onOrderChange: (IndexOrder) -> Unit,
75+
onSeasonVersionChange: (Int) -> Unit,
76+
onSpokenLanguageTypeChange: (Int) -> Unit,
77+
onAreaChange: (Int) -> Unit,
78+
onIsFinishChange: (Int) -> Unit,
79+
onCopyrightChange: (Int) -> Unit,
80+
onSeasonStatusChange: (Int) -> Unit,
81+
onSeasonMonthChange: (Int) -> Unit,
82+
onYearChange: (String) -> Unit,
83+
onStyleIdChange: (Int) -> Unit,
84+
onDescChange: (Boolean) -> Unit,
85+
// TODO 重置筛选条件
86+
onReset: () -> Unit = {}
87+
) {
88+
val context = LocalContext.current
89+
val focusRequester = remember { FocusRequester() }
90+
91+
LaunchedEffect(show) {
92+
runCatching {
93+
if (show) focusRequester.requestFocus()
94+
}
95+
}
96+
97+
if (show) {
98+
AlertDialog(
99+
modifier = modifier
100+
.fillMaxWidth(0.8f),
101+
onDismissRequest = onDismissRequest,
102+
confirmButton = { },
103+
title = {
104+
Text(text = "番剧索引筛选")
105+
},
106+
text = {
107+
Column(
108+
modifier = Modifier
109+
.heightIn(max = 300.dp)
110+
) {
111+
IndexFilterChipRow(
112+
modifier = Modifier.focusRequester(focusRequester),
113+
title = "排序方式",
114+
filter = animeIndexOrders.associateWith { it.getDisplayName(context) },
115+
selectedFilterId = order,
116+
onFilterIdChange = onOrderChange
117+
)
118+
IndexFilterChipRow(
119+
title = "排序顺序",
120+
filter = mapOf(true to "降序", false to "升序"),
121+
selectedFilterId = desc,
122+
onFilterIdChange = onDescChange
123+
)
124+
HorizontalDivider(
125+
modifier = Modifier.padding(vertical = 8.dp)
126+
)
127+
TvLazyColumn {
128+
item {
129+
IndexFilterChipRow(
130+
title = "类型",
131+
filter = indexFilterSeasonVersion,
132+
selectedFilterId = seasonVersion,
133+
onFilterIdChange = onSeasonVersionChange
134+
)
135+
}
136+
item {
137+
IndexFilterChipRow(
138+
title = "配音",
139+
filter = indexFilterSpokenLanguageType,
140+
selectedFilterId = spokenLanguageType,
141+
onFilterIdChange = onSpokenLanguageTypeChange
142+
)
143+
}
144+
item {
145+
IndexFilterChipRow(
146+
title = "地区",
147+
filter = indexFilterArea,
148+
selectedFilterId = area,
149+
onFilterIdChange = onAreaChange
150+
)
151+
}
152+
item {
153+
IndexFilterChipRow(
154+
title = "状态",
155+
filter = indexFilterIsFinish,
156+
selectedFilterId = isFinish,
157+
onFilterIdChange = onIsFinishChange
158+
)
159+
}
160+
item {
161+
IndexFilterChipRow(
162+
title = "版权",
163+
filter = indexFilterCopyright,
164+
selectedFilterId = copyright,
165+
onFilterIdChange = onCopyrightChange
166+
)
167+
}
168+
item {
169+
IndexFilterChipRow(
170+
title = "付费",
171+
filter = indexFilterSeasonStatus,
172+
selectedFilterId = seasonStatus,
173+
onFilterIdChange = onSeasonStatusChange
174+
)
175+
}
176+
item {
177+
IndexFilterChipRow(
178+
title = "季度",
179+
filter = indexFilterSeasonMonth,
180+
selectedFilterId = seasonMonth,
181+
onFilterIdChange = onSeasonMonthChange
182+
)
183+
}
184+
item {
185+
IndexFilterChipRow(
186+
title = "年份",
187+
filter = indexFilterYear,
188+
selectedFilterId = year,
189+
onFilterIdChange = onYearChange
190+
)
191+
}
192+
item {
193+
IndexFilterChipRow(
194+
title = "风格",
195+
filter = indexFilterStyleIdsAnime,
196+
selectedFilterId = styleId,
197+
onFilterIdChange = onStyleIdChange
198+
)
199+
}
200+
}
201+
}
202+
},
203+
properties = DialogProperties(usePlatformDefaultWidth = false)
204+
205+
)
206+
}
207+
}
208+
209+
@OptIn(ExperimentalTvMaterial3Api::class)
210+
@Preview(device = "id:tv_1080p")
211+
@Composable
212+
private fun AnimeIndexFilterPreview() {
213+
var order by remember { mutableStateOf(IndexOrder.PlayCount) }
214+
var seasonVersion by remember { mutableStateOf(-1) }
215+
var spokenLanguageType by remember { mutableStateOf(-1) }
216+
var area by remember { mutableStateOf(-1) }
217+
var isFinish by remember { mutableStateOf(-1) }
218+
var copyright by remember { mutableStateOf(-1) }
219+
var seasonStatus by remember { mutableStateOf(-1) }
220+
var seasonMonth by remember { mutableStateOf(-1) }
221+
var year by remember { mutableStateOf("-1") }
222+
var styleId by remember { mutableStateOf(-1) }
223+
var desc by remember { mutableStateOf(true) }
224+
225+
BVTheme {
226+
Surface(
227+
modifier = Modifier.fillMaxSize()
228+
) {
229+
AnimeIndexFilter(
230+
show = true,
231+
onDismissRequest = { },
232+
order = order,
233+
seasonVersion = seasonVersion,
234+
spokenLanguageType = spokenLanguageType,
235+
area = area,
236+
isFinish = isFinish,
237+
copyright = copyright,
238+
seasonStatus = seasonStatus,
239+
seasonMonth = seasonMonth,
240+
year = year,
241+
styleId = styleId,
242+
desc = desc,
243+
onOrderChange = { order = it },
244+
onSeasonVersionChange = { seasonVersion = it },
245+
onSpokenLanguageTypeChange = { spokenLanguageType = it },
246+
onAreaChange = { area = it },
247+
onIsFinishChange = { isFinish = it },
248+
onCopyrightChange = { copyright = it },
249+
onSeasonStatusChange = { seasonStatus = it },
250+
onSeasonMonthChange = { seasonMonth = it },
251+
onYearChange = { year = it },
252+
onStyleIdChange = { styleId = it },
253+
onDescChange = { desc = it },
254+
onReset = { }
255+
)
256+
}
257+
}
258+
}
259+
260+
@OptIn(ExperimentalTvMaterial3Api::class)
261+
@Composable
262+
private fun IndexFilterChip(
263+
modifier: Modifier = Modifier,
264+
selected: Boolean,
265+
onClick: () -> Unit,
266+
label: String
267+
) {
268+
FilterChip(
269+
modifier = modifier,
270+
selected = selected,
271+
onClick = onClick
272+
) {
273+
Row(
274+
horizontalArrangement = Arrangement.spacedBy(4.dp),
275+
verticalAlignment = Alignment.CenterVertically
276+
) {
277+
AnimatedVisibility(visible = selected) {
278+
Icon(
279+
modifier = Modifier.size(20.dp),
280+
imageVector = Icons.Default.Check,
281+
contentDescription = null
282+
)
283+
}
284+
Text(text = label)
285+
}
286+
}
287+
}
288+
289+
@OptIn(ExperimentalTvMaterial3Api::class)
290+
@Composable
291+
fun <T> IndexFilterChipRow(
292+
modifier: Modifier = Modifier,
293+
title: String,
294+
filter: Map<T, String>,
295+
selectedFilterId: T,
296+
onFilterIdChange: (T) -> Unit
297+
) {
298+
val focusRestorerModifiers = createCustomInitialFocusRestorerModifiers()
299+
300+
Row(
301+
horizontalArrangement = Arrangement.spacedBy(8.dp),
302+
verticalAlignment = Alignment.CenterVertically
303+
) {
304+
Text(
305+
text = title,
306+
style = MaterialTheme.typography.labelLarge
307+
)
308+
TvLazyRow(
309+
modifier = modifier
310+
.then(focusRestorerModifiers.parentModifier),
311+
horizontalArrangement = Arrangement.spacedBy(8.dp),
312+
contentPadding = PaddingValues(horizontal = 8.dp, vertical = 4.dp)
313+
) {
314+
items(items = filter.entries.toList()) { (id, label) ->
315+
IndexFilterChip(
316+
modifier = Modifier
317+
.ifElse(selectedFilterId == id, focusRestorerModifiers.childModifier),
318+
selected = selectedFilterId == id,
319+
onClick = { onFilterIdChange(id) },
320+
label = label
321+
)
322+
}
323+
}
324+
}
325+
}
326+
327+
fun IndexOrder.getDisplayName(context: Context) = when (this) {
328+
IndexOrder.UpdateTime -> context.getString(R.string.index_order_update_time)
329+
IndexOrder.DanmakuCount -> context.getString(R.string.index_order_danmaku_count)
330+
IndexOrder.PlayCount -> context.getString(R.string.index_order_play_count)
331+
IndexOrder.FollowCount -> context.getString(R.string.index_order_follow_count)
332+
IndexOrder.Score -> context.getString(R.string.index_order_score)
333+
IndexOrder.StartTime -> context.getString(R.string.index_order_start_time)
334+
IndexOrder.PublishTime -> context.getString(R.string.index_order_publish_time)
335+
}

0 commit comments

Comments
 (0)