Skip to content
This repository was archived by the owner on Jul 18, 2026. It is now read-only.

Commit ee3bc11

Browse files
authored
Merge pull request #393 from misaka10032w/baseline/compose-without-player
[Added] 允许对首页内容排序和隐藏、修复公告无法暂时关闭的问题
2 parents ed6c4d7 + f8426be commit ee3bc11

16 files changed

Lines changed: 472 additions & 27 deletions

File tree

app/src/main/java/com/yenaly/han1meviewer/ui/component/VerticalScrollbar.kt

Lines changed: 24 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -44,22 +44,32 @@ fun Modifier.verticalScrollbar(
4444
drawWithContent {
4545
drawContent()
4646

47-
val layoutInfo = state.layoutInfo
48-
val visibleItemsInfo = layoutInfo.visibleItemsInfo
47+
val viewportHeight = size.height
48+
val visibleItems = state.layoutInfo.visibleItemsInfo
49+
if (visibleItems.isNotEmpty()) {
50+
val avgItemHeight =
51+
(visibleItems.last().offset + visibleItems.last().size - visibleItems.first().offset) / visibleItems.size
52+
val totalEstimatedHeight = state.layoutInfo.totalItemsCount * avgItemHeight
4953

50-
if (visibleItemsInfo.isNotEmpty() && layoutInfo.totalItemsCount > visibleItemsInfo.size) {
51-
val elementHeight = size.height / layoutInfo.totalItemsCount
52-
val firstVisibleElementIndex = visibleItemsInfo.first().index
54+
if (totalEstimatedHeight > viewportHeight) {
55+
val firstItem = visibleItems.first()
56+
val scrolledPastPx = (firstItem.index * avgItemHeight) - firstItem.offset
57+
val scrollbarHeight = (viewportHeight * viewportHeight / totalEstimatedHeight)
58+
.coerceAtLeast(32.dp.toPx())
59+
val scrollFraction = scrolledPastPx / (totalEstimatedHeight - viewportHeight)
60+
val scrollbarOffsetY =
61+
(scrollFraction * (viewportHeight - scrollbarHeight)).coerceIn(
62+
0f,
63+
viewportHeight - scrollbarHeight
64+
)
5365

54-
val scrollbarOffsetY = firstVisibleElementIndex * elementHeight
55-
val scrollbarHeight = visibleItemsInfo.size * elementHeight
56-
57-
drawRoundRect(
58-
color = resolvedColor.copy(alpha = resolvedColor.alpha * alpha.value),
59-
topLeft = Offset(size.width - width.toPx(), scrollbarOffsetY),
60-
size = Size(width.toPx(), scrollbarHeight),
61-
cornerRadius = CornerRadius(width.toPx() / 2)
62-
)
66+
drawRoundRect(
67+
color = resolvedColor.copy(alpha = resolvedColor.alpha * alpha.value),
68+
topLeft = Offset(size.width - width.toPx(), scrollbarOffsetY),
69+
size = Size(width.toPx(), scrollbarHeight),
70+
cornerRadius = CornerRadius(width.toPx() / 2)
71+
)
72+
}
6373
}
6474
}
6575
}

app/src/main/java/com/yenaly/han1meviewer/ui/navigation/settings/HomeSettingsRoute.kt

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
4444
import com.google.firebase.Firebase
4545
import com.google.firebase.analytics.analytics
4646
import com.yenaly.han1meviewer.BuildConfig
47+
import com.yenaly.han1meviewer.HanimeConstants
4748
import com.yenaly.han1meviewer.HA1_GITHUB_FORUM_URL
4849
import com.yenaly.han1meviewer.HA1_GITHUB_ISSUE_URL
4950
import com.yenaly.han1meviewer.HanimeApplication
@@ -56,6 +57,10 @@ import com.yenaly.han1meviewer.ui.component.ConfirmDialog
5657
import com.yenaly.han1meviewer.ui.screen.settings.HomeSettingsScreen
5758
import com.yenaly.han1meviewer.ui.screen.settings.dialog.LicenseDialog
5859
import com.yenaly.han1meviewer.ui.screen.settings.model.HomeSettingsUiState
60+
import com.yenaly.han1meviewer.ui.screen.home.homepage.defaultHomeCategoryPreferenceItems
61+
import com.yenaly.han1meviewer.ui.screen.home.homepage.hiddenHomeCategoryKeys
62+
import com.yenaly.han1meviewer.ui.screen.home.homepage.homeCategoryOrder
63+
import com.yenaly.han1meviewer.ui.screen.home.homepage.saveHomeCategoryPreferences
5964
import com.yenaly.han1meviewer.ui.theme.ThemeColorPreset
6065
import com.yenaly.han1meviewer.ui.viewmodel.AppViewModel
6166
import com.yenaly.han1meviewer.util.ThemeUtils
@@ -280,6 +285,10 @@ fun HomeSettingsRouteScreen(
280285
refreshKey++
281286
activity.recreate()
282287
},
288+
onHomeCategoryPreferencesChange = { order, hiddenKeys ->
289+
saveHomeCategoryPreferences(order, hiddenKeys)
290+
refreshKey++
291+
},
283292
onUseCIUpdateChannelChange = { value ->
284293
saveBoolean(HOME_USE_CI_UPDATE_CHANNEL, value)
285294
refreshKey++
@@ -605,5 +614,9 @@ private fun buildHomeSettingsUiState(
605614
horizontalCardCountConfig.expandedCount,
606615
).joinToString(" / "),
607616
horizontalCardCountConfig = horizontalCardCountConfig,
617+
homeCategoryItems = defaultHomeCategoryPreferenceItems,
618+
homeCategoryOrder = homeCategoryOrder,
619+
hiddenHomeCategoryKeys = hiddenHomeCategoryKeys,
620+
useAvHomeCategoryTitles = Preferences.baseUrl == HanimeConstants.HANIME_URL[3],
608621
)
609622
}

app/src/main/java/com/yenaly/han1meviewer/ui/navigation/settings/SettingsPreferenceKeys.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ object SettingsPreferenceKeys {
2525
const val HORIZONTAL_CARD_COUNT_COMPACT = "horizontal_card_count_compact"
2626
const val HORIZONTAL_CARD_COUNT_MEDIUM = "horizontal_card_count_medium"
2727
const val HORIZONTAL_CARD_COUNT_EXPANDED = "horizontal_card_count_expanded"
28+
const val HOME_CATEGORY_ORDER = "home_category_order"
29+
const val HOME_CATEGORY_HIDDEN = "home_category_hidden"
2830
const val SHOW_BOTTOM_PROGRESS = "show_bottom_progress"
2931
const val SWITCH_PLAYER_KERNEL = "switch_player_kernel"
3032
const val PLAYER_SPEED = "player_speed"

app/src/main/java/com/yenaly/han1meviewer/ui/preview/ComposePreviewDataSource.kt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -236,16 +236,19 @@ val fakeHomePageVideos = listOf(
236236

237237
val fakeCategories = listOf(
238238
HomeCategory(
239+
key = "preview_latest",
239240
titleRes = R.string.latest_hanime,
240241
genre = "裏番",
241242
videos = fakeHomePageVideos,
242243
),
243244
HomeCategory(
245+
key = "preview_release",
244246
titleRes = R.string.latest_release,
245247
sort = "最新上市",
246248
videos = fakeHomePageVideos.shuffled().take(4),
247249
),
248250
HomeCategory(
251+
key = "preview_watched",
249252
titleRes = R.string.they_watched,
250253
sort = "他們在看",
251254
videos = fakeHomePageVideos.shuffled().take(5),
@@ -413,4 +416,4 @@ val fakeVideoIntroduction = HanimeVideo(
413416
isFav = true,
414417
currentUserId = "10086",
415418
originalComic = "https://example.com/comic",
416-
)
419+
)

app/src/main/java/com/yenaly/han1meviewer/ui/screen/home/HomePageScreen.kt

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,6 @@ import com.yenaly.han1meviewer.ui.screen.home.homepage.buildCategoryList
4343
import com.yenaly.han1meviewer.ui.screen.home.homepage.toAdvancedSearchParams
4444
import com.yenaly.han1meviewer.ui.screen.home.homepage.toHomePageErrorMessageRes
4545
import com.yenaly.han1meviewer.ui.viewmodel.MainViewModel
46-
import com.yenaly.yenaly_libs.utils.putSpValue
4746

4847
/**
4948
* 首页容器屏幕,负责连接 ViewModel 状态与导航回调。
@@ -147,11 +146,7 @@ fun HomePageScreen(
147146
onBannerClick = { videoCode -> videoCode?.let(onOpenVideo) },
148147
onAnnouncementClick = onShowAnnouncementDialog,
149148
onCloseAnnouncement = {
150-
putSpValue(
151-
"last_dismiss_time",
152-
System.currentTimeMillis(),
153-
"setting_pref"
154-
)
149+
viewModel.dismissAnnouncements()
155150
},
156151
onMoreClick = { category ->
157152
val params = category.toAdvancedSearchParams()
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
package com.yenaly.han1meviewer.ui.screen.home.homepage
2+
3+
import androidx.annotation.StringRes
4+
import androidx.core.content.edit
5+
import com.yenaly.han1meviewer.R
6+
import com.yenaly.han1meviewer.Preferences
7+
import com.yenaly.han1meviewer.ui.navigation.settings.SettingsPreferenceKeys
8+
9+
const val HOME_CATEGORY_LATEST_HANIME = "latest_hanime"
10+
const val HOME_CATEGORY_LATEST_RELEASE = "latest_release"
11+
const val HOME_CATEGORY_LATEST_UPLOAD = "latest_upload"
12+
const val HOME_CATEGORY_WATCHING_NOW = "watching_now"
13+
const val HOME_CATEGORY_SHORT_EPISODE = "short_episode"
14+
const val HOME_CATEGORY_MOTION_ANIME = "motion_anime"
15+
const val HOME_CATEGORY_3D_CG = "3d_cg"
16+
const val HOME_CATEGORY_2_5D = "2_5d"
17+
const val HOME_CATEGORY_2D_ANIME = "2d_anime"
18+
const val HOME_CATEGORY_AI_GENERATED = "ai_generated"
19+
const val HOME_CATEGORY_MMD = "mmd"
20+
const val HOME_CATEGORY_COSPLAY = "cosplay"
21+
22+
data class HomeCategoryPreferenceItem(
23+
val key: String,
24+
@param:StringRes val normalTitleRes: Int,
25+
@param:StringRes val avTitleRes: Int? = null,
26+
)
27+
28+
val defaultHomeCategoryPreferenceItems = listOf(
29+
HomeCategoryPreferenceItem(HOME_CATEGORY_LATEST_HANIME, R.string.latest_hanime, R.string.latest_av),
30+
HomeCategoryPreferenceItem(HOME_CATEGORY_LATEST_RELEASE, R.string.latest_release),
31+
HomeCategoryPreferenceItem(HOME_CATEGORY_LATEST_UPLOAD, R.string.latest_upload),
32+
HomeCategoryPreferenceItem(HOME_CATEGORY_WATCHING_NOW, R.string.they_watched),
33+
HomeCategoryPreferenceItem(HOME_CATEGORY_SHORT_EPISODE, R.string.category_instant_noodle, R.string.amateur_nomask),
34+
HomeCategoryPreferenceItem(HOME_CATEGORY_MOTION_ANIME, R.string.category_motion_anime, R.string.hd_uncensored),
35+
HomeCategoryPreferenceItem(HOME_CATEGORY_3D_CG, R.string.category_3d_animation, R.string.ai_decensored),
36+
HomeCategoryPreferenceItem(HOME_CATEGORY_2_5D, R.string.animation_2_5d, R.string.china_av),
37+
HomeCategoryPreferenceItem(HOME_CATEGORY_2D_ANIME, R.string.animation_2d, R.string.chinese_amateur),
38+
HomeCategoryPreferenceItem(HOME_CATEGORY_AI_GENERATED, R.string.ai_generated, R.string.chinese_subtitle),
39+
HomeCategoryPreferenceItem(HOME_CATEGORY_MMD, R.string.mmd, R.string.ranking_today),
40+
HomeCategoryPreferenceItem(HOME_CATEGORY_COSPLAY, R.string.category_cosplay, R.string.ranking_this_month),
41+
)
42+
43+
val defaultHomeCategoryOrder: List<String>
44+
get() = defaultHomeCategoryPreferenceItems.map { it.key }
45+
46+
val homeCategoryOrder: List<String>
47+
get() = normalizeHomeCategoryKeys(
48+
Preferences.preferenceSp.getString(SettingsPreferenceKeys.HOME_CATEGORY_ORDER, null)
49+
?.split(',')
50+
.orEmpty()
51+
.filter { it.isNotBlank() }
52+
)
53+
54+
val hiddenHomeCategoryKeys: Set<String>
55+
get() = Preferences.preferenceSp.getString(SettingsPreferenceKeys.HOME_CATEGORY_HIDDEN, null)
56+
?.split(',')
57+
.orEmpty()
58+
.filter { it.isNotBlank() }
59+
.toSet()
60+
61+
fun saveHomeCategoryPreferences(order: List<String>, hiddenKeys: Set<String>) {
62+
Preferences.preferenceSp.edit {
63+
putString(SettingsPreferenceKeys.HOME_CATEGORY_ORDER, normalizeHomeCategoryKeys(order).joinToString(","))
64+
putString(
65+
SettingsPreferenceKeys.HOME_CATEGORY_HIDDEN,
66+
hiddenKeys.filter { it in defaultHomeCategoryOrder }.joinToString(",")
67+
)
68+
}
69+
}
70+
71+
private fun normalizeHomeCategoryKeys(keys: List<String>): List<String> {
72+
val defaults = defaultHomeCategoryOrder
73+
return keys.distinct().filter { it in defaults } + defaults.filterNot { it in keys }
74+
}

app/src/main/java/com/yenaly/han1meviewer/ui/screen/home/homepage/HomePageMappers.kt

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,73 +16,92 @@ fun buildCategoryList(homePage: HomePage): List<HomeCategory> {
1616

1717
return listOfNotNull(
1818
HomeCategory(
19+
key = HOME_CATEGORY_LATEST_HANIME,
1920
titleRes = if (isAVSite) R.string.latest_av else R.string.latest_hanime,
2021
genre = if (isAVSite) "日本AV" else "裏番",
2122
videos = homePage.ecchiAnime
2223
),
2324
HomeCategory(
25+
key = HOME_CATEGORY_LATEST_RELEASE,
2426
titleRes = R.string.latest_release,
2527
sort = "最新上市",
2628
videos = homePage.latestRelease
2729
),
2830
HomeCategory(
31+
key = HOME_CATEGORY_LATEST_UPLOAD,
2932
titleRes = R.string.latest_upload,
3033
sort = "最新上傳",
3134
videos = homePage.latestHanime
3235
),
3336
HomeCategory(
37+
key = HOME_CATEGORY_WATCHING_NOW,
3438
titleRes = R.string.they_watched,
3539
sort = "他們在看",
3640
videos = homePage.watchingNow
3741
),
3842
HomeCategory(
43+
key = HOME_CATEGORY_SHORT_EPISODE,
3944
titleRes = if (isAVSite) R.string.amateur_nomask else R.string.category_instant_noodle,
4045
genre = if (isAVSite) "素人業餘" else "泡麵番",
4146
sort = "最新上傳",
4247
videos = homePage.shortEpisodeAnime
4348
),
4449
HomeCategory(
50+
key = HOME_CATEGORY_MOTION_ANIME,
4551
titleRes = if (isAVSite) R.string.hd_uncensored else R.string.category_motion_anime,
4652
genre = if (isAVSite) "高清無碼" else "Motion Anime",
4753
sort = "最新上傳",
4854
videos = homePage.motionAnime
4955
),
5056
HomeCategory(
57+
key = HOME_CATEGORY_3D_CG,
5158
titleRes = if (isAVSite) R.string.ai_decensored else R.string.category_3d_animation,
5259
genre = if (isAVSite) "AI解碼" else "3DCG",
5360
sort = "最新上傳",
5461
videos = homePage.threeDCG
5562
),
5663
HomeCategory(
64+
key = HOME_CATEGORY_2_5D,
5765
titleRes = if (isAVSite) R.string.china_av else R.string.animation_2_5d,
5866
genre = if (isAVSite) "國產AV" else "2.5D",
5967
sort = "最新上傳",
6068
videos = homePage.twoPointFiveDAnime
6169
),
6270
HomeCategory(
71+
key = HOME_CATEGORY_2D_ANIME,
6372
titleRes = if (isAVSite) R.string.chinese_amateur else R.string.animation_2d,
6473
genre = if (isAVSite) "國產素人" else "2D動畫",
6574
sort = "最新上傳",
6675
videos = homePage.twoDAnime
6776
),
6877
HomeCategory(
78+
key = HOME_CATEGORY_AI_GENERATED,
6979
titleRes = if (isAVSite) R.string.chinese_subtitle else R.string.ai_generated,
7080
genre = if (isAVSite) null else "AI生成",
7181
tags = if (isAVSite) "中文字幕" else null,
7282
sort = "最新上傳",
7383
videos = homePage.aiGenerated
7484
),
7585
HomeCategory(
86+
key = HOME_CATEGORY_MMD,
7687
titleRes = if (isAVSite) R.string.ranking_today else R.string.mmd,
7788
genre = if (isAVSite) null else "MMD",
7889
sort = if (isAVSite) "本日排行" else "最新上傳",
7990
videos = homePage.mmd
8091
),
8192
HomeCategory(
93+
key = HOME_CATEGORY_COSPLAY,
8294
titleRes = if (isAVSite) R.string.ranking_this_month else R.string.category_cosplay,
8395
genre = if (isAVSite) null else "Cosplay",
8496
sort = if (isAVSite) "本月排行" else "最新上傳",
8597
videos = homePage.cosplay
8698
)
8799
).filter { it.videos.isNotEmpty() }
100+
.let { categories ->
101+
val hiddenKeys = hiddenHomeCategoryKeys
102+
val orderIndex = homeCategoryOrder.withIndex().associate { it.value to it.index }
103+
categories
104+
.filterNot { it.key in hiddenKeys }
105+
.sortedBy { orderIndex[it.key] ?: Int.MAX_VALUE }
106+
}
88107
}

app/src/main/java/com/yenaly/han1meviewer/ui/screen/home/homepage/HomePageModels.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ val LocalSearchHistoryQuery = staticCompositionLocalOf<suspend (String) -> List<
2121
* @param videos 当前分类下展示的视频列表。
2222
*/
2323
data class HomeCategory(
24+
val key: String,
2425
@param:StringRes val titleRes: Int,
2526
val genre: String? = null,
2627
val sort: String? = null,

0 commit comments

Comments
 (0)