优化
This commit is contained in:
@@ -101,8 +101,7 @@
|
||||
"Read(//c/Users/**)",
|
||||
"Bash(Get-ChildItem -Path \"D:\\\\AndroidPrj\\\\legado-with-MD3\" -Directory -Depth 0)",
|
||||
"Bash(Select-Object Name)",
|
||||
"PowerShell(Get-ChildItem -Path \"D:\\\\AndroidPrj\\\\legado-with-MD3\\\\app\\\\src\\\\main\\\\java\\\\io\\\\legado\\\\app\" -Directory -Depth 1 | ForEach-Object { $_.FullName.Replace\\(\"D:\\\\AndroidPrj\\\\legado-with-MD3\\\\app\\\\src\\\\main\\\\java\\\\io\\\\legado\\\\app\\\\\", \"\"\\) })",
|
||||
"Bash(powershell *)"
|
||||
"PowerShell(Get-ChildItem -Path \"D:\\\\AndroidPrj\\\\legado-with-MD3\\\\app\\\\src\\\\main\\\\java\\\\io\\\\legado\\\\app\" -Directory -Depth 1 | ForEach-Object { $_.FullName.Replace\\(\"D:\\\\AndroidPrj\\\\legado-with-MD3\\\\app\\\\src\\\\main\\\\java\\\\io\\\\legado\\\\app\\\\\", \"\"\\) })"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,240 +1,115 @@
|
||||
# 首页模块 (homepageModules)
|
||||
# 首页模块 (Homepage Modules) 配置规范
|
||||
|
||||
在书源编辑页的「发现」Tab 中,可以配置 `首页模块 (JSON)` 字段,声明该书源为首页提供哪些内容模块。
|
||||
书源的 `homepageModules` 字段允许开发者声明该书源在首页展示的内容模块。这些模块通过 JSON
|
||||
数组进行定义,支持高度自定义的布局和数据来源。
|
||||
|
||||
## 概述
|
||||
---
|
||||
|
||||
- 每个模块代表首页上的一个内容区块(如轮播图、排行榜、网格书架等)
|
||||
- 一个书源可以声明多个模块(例如同时提供"热门推荐"Banner 和"周榜"排行榜)
|
||||
- 用户在首页可以自由拖拽排序、隐藏/显示各个模块
|
||||
- 模块内容来自书源的**发现(explore)**接口,通过 `kindTitle` 匹配分类URL
|
||||
## 1. 数据结构 (Data Structure)
|
||||
|
||||
## JSON 格式
|
||||
`homepageModules` 是一个包含多个模块定义对象的 JSON 数组。
|
||||
|
||||
`homepageModules` 是一个 JSON 数组,每个元素定义一个模块:
|
||||
### 模块通用字段
|
||||
|
||||
| 字段 | 类型 | 必须 | 说明 |
|
||||
|:-----------------|:---------|:---|:------------------------------------------------------|
|
||||
| **key** | `String` | 是 | 模块唯一标识。建议使用 `[a-z0-9_]` 字符。用于保存用户的排序/显隐设置。 |
|
||||
| **type** | `Enum` | 是 | 模块类型。定义了渲染方式和交互逻辑。详见 [模块类型](#2-模块类型-module-types)。 |
|
||||
| **title** | `String` | 是 | 模块默认标题。用户可在本地自定义覆盖。 |
|
||||
| **kindTitle** | `String` | 否 | 用于匹配书源「发现」规则中的分类标题。匹配成功后自动继承其 URL 和规则。 |
|
||||
| **url** | `String` | 否 | 显式指定数据接口 URL。优先级高于 `kindTitle`。支持变量替换。 |
|
||||
| **args** | `String` | 否 | 附加参数。在 `buttonGroup` 类型中为 JSON 数组字符串。 |
|
||||
| **layoutConfig** | `Object` | 否 | 布局配置对象,用于调整列数、行数、图标等。详见 [布局配置](#3-布局配置-layoutconfig)。 |
|
||||
|
||||
---
|
||||
|
||||
## 2. 模块类型 (Module Types)
|
||||
|
||||
### 列表与轮播类
|
||||
|
||||
| 类型 (Type) | 描述 | 特点 |
|
||||
|:----------|:------|:-----------------------|
|
||||
| `banner` | 横滑轮播图 | 适合展示高权重的精品推荐,使用大图封面。 |
|
||||
| `ranking` | 排行榜列表 | 垂直列表展示,带排名序号。 |
|
||||
| `card` | 推荐卡片 | 横向滑动的卡片流,同时显示封面、标题及简介。 |
|
||||
|
||||
### 网格类
|
||||
|
||||
| 类型 (Type) | 描述 | 特点 |
|
||||
|:---------------|:------|:------------------|
|
||||
| `grid` | 标准网格 | 最常用的展示形式。支持自定义行列。 |
|
||||
| `gridRanking` | 网格排行榜 | 多行多列的排行展示。横向翻页。 |
|
||||
| `infiniteGrid` | 无限网格 | 垂直滚动的网格流。无限加载。 |
|
||||
| `waterfall` | 错位瀑布流 | 垂直错位排列的书架流。无限加载。 |
|
||||
|
||||
### 功能类
|
||||
|
||||
| 类型 (Type) | 描述 | 特点 |
|
||||
|:--------------|:------|:--------------------------------------------|
|
||||
| `buttonGroup` | 快捷按钮组 | 渲染为一组圆形/图标按钮,支持自动填充宽度与自动分列。通常用于放置常用分类或功能入口。 |
|
||||
|
||||
---
|
||||
|
||||
## 3. 布局配置 (LayoutConfig)
|
||||
|
||||
通过 `layoutConfig` 对象,可以精细化控制模块的表现。
|
||||
|
||||
| 属性 (Property) | 类型 | 适用类型 | 默认值 | 说明 |
|
||||
|:--------------|:---------|:------------------------------------|:----|:-----------------------------------------|
|
||||
| `columns` | `Int` | `grid`, `waterfall`, `infiniteGrid` | 3 | 每行显示的列数。 |
|
||||
| `icon` | `String` | `buttonGroup` | - | 按钮组的默认统一图标 URL。 |
|
||||
| `icons` | `Object` | `buttonGroup` | - | 图标映射表。例:`{"排行": "http://path/to/icon"}`。 |
|
||||
|
||||
---
|
||||
|
||||
## 4. 数据绑定逻辑 (Data Binding)
|
||||
|
||||
1. **自动匹配**:如果提供了 `kindTitle`,系统会遍历书源 `exploreKinds()` 返回的列表。如果某个分类的
|
||||
`title` 与之完全一致,该模块将自动使用该分类的 `url`。
|
||||
2. **静态指定**:如果提供了 `url`,系统将直接请求该 URL。
|
||||
3. **降级逻辑**:若 `kindTitle` 未匹配且无 `url`,模块将回退至书源的主 `exploreUrl`。
|
||||
|
||||
---
|
||||
|
||||
## 5. 完整 JSON 示例
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"key": "模块唯一标识",
|
||||
"type": "模块类型",
|
||||
"title": "模块标题",
|
||||
"kindTitle": "匹配的分类标题(可选)",
|
||||
"url": "覆盖分类URL(可选)",
|
||||
"args": "特殊参数(可选)",
|
||||
"layoutConfig": {
|
||||
"columns": 2,
|
||||
"rows": 3
|
||||
}
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
### 字段说明
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|----------------|-----------|----|-------------------------------------------------------------------------------------------------------|
|
||||
| `key` | `String` | 是 | 模块在书源内的唯一标识,用于关联用户偏好。建议使用英文,如 `"hot"`, `"rank_week"` |
|
||||
| `type` | `String` | 是 | 模块类型,可选值:`"banner"`, `"ranking"`, `"gridRanking"`, `"grid"`, `"card"`, `"waterfall"`, `"buttonGroup"` |
|
||||
| `title` | `String` | 是 | 模块标题,会在首页模块头部展示 |
|
||||
| `kindTitle` | `String?` | 否 | 用于匹配该书源「发现」中的分类标题。不填则使用默认 `exploreUrl` |
|
||||
| `url` | `String?` | 否 | 显式指定该模块的 URL,优先级高于 `kindTitle` 匹配到的 URL |
|
||||
| `args` | `String?` | 否 | 模块特有参数。在 `buttonGroup` 中为包含分类标题的 JSON 数组字符串 |
|
||||
| `layoutConfig` | `Object?` | 否 | 布局配置对象。支持 `columns` (列数), `rows` (行数) |
|
||||
|
||||
## 模块类型
|
||||
|
||||
### banner — 横滑轮播图
|
||||
|
||||
适合展示热门推荐、本周强推等内容。以大图封面横向滑动展示。
|
||||
|
||||
```json
|
||||
{
|
||||
"key": "hot_banner",
|
||||
"type": "banner",
|
||||
"title": "热门推荐",
|
||||
"kindTitle": "热门",
|
||||
"displayCount": 6
|
||||
}
|
||||
```
|
||||
|
||||
### ranking — 排行榜
|
||||
|
||||
带排名序号的列表。前三名高亮为橙色,其余为灰色。默认折叠显示前 5 本,末尾有"展开更多"按钮。
|
||||
|
||||
```json
|
||||
{
|
||||
"key": "week_rank",
|
||||
"type": "ranking",
|
||||
"title": "周排行榜",
|
||||
"kindTitle": "周榜",
|
||||
"displayCount": 10
|
||||
}
|
||||
```
|
||||
|
||||
### gridRanking — 网格排行
|
||||
|
||||
4×4 列式排列(可通过 `layoutConfig.rows` 修改行数),每页多本书,可横向翻页。封面较小,仅显示书名和作者。适合作为首页入口展示大量书籍。
|
||||
|
||||
```json
|
||||
{
|
||||
"key": "all_grid",
|
||||
"type": "gridRanking",
|
||||
"title": "全部分类",
|
||||
"kindTitle": "全部分类",
|
||||
"layoutConfig": {
|
||||
"rows": 4
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### grid — 网格书架
|
||||
|
||||
网格布局,适合展示分类书单。支持自定义行列。
|
||||
|
||||
- `columns`: 默认 3
|
||||
- `rows`: 默认 2。若设置为 `0`,则显示为平铺列表并支持下拉加载更多。
|
||||
|
||||
```json
|
||||
{
|
||||
"key": "scifi_grid",
|
||||
"type": "grid",
|
||||
"title": "科幻精选",
|
||||
"kindTitle": "科幻",
|
||||
"layoutConfig": {
|
||||
"columns": 3,
|
||||
"rows": 2
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### card — 推荐卡片
|
||||
|
||||
大图横向滑动卡片,展示封面 + 书名 + 简介。适合需要更多信息展示的场景。
|
||||
|
||||
```json
|
||||
{
|
||||
"key": "editor_pick",
|
||||
"type": "card",
|
||||
"title": "编辑推荐",
|
||||
"kindTitle": "编辑推荐"
|
||||
}
|
||||
```
|
||||
|
||||
### waterfall — 错位瀑布流
|
||||
|
||||
瀑布流布局,展示封面 + 书名 + 简介。支持 `layoutConfig.columns` 自定义列数(默认 2)。支持无限加载更多。
|
||||
|
||||
```json
|
||||
{
|
||||
"key": "hot_wf",
|
||||
"type": "waterfall",
|
||||
"title": "大家都在看",
|
||||
"kindTitle": "热门",
|
||||
"layoutConfig": {
|
||||
"columns": 2
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### buttonGroup — 按钮组
|
||||
|
||||
显示为网格排列的分类按钮。适合放置常用的分类或动作入口。
|
||||
|
||||
- **布局特性**:自动平衡每行按钮数量(例如 6 个按钮显示为 3+3,8 个显示为 4+4)。每行最多 5 个。
|
||||
- **args**: 可选。由分类标题组成的 JSON 数组字符串,如 `["排行", "分类", "完本"]`。若不填则默认显示该书源前
|
||||
5 个分类。
|
||||
- **layoutConfig**:
|
||||
- `icon`: 全局默认图标。支持网络图片 URL。
|
||||
- `icons`: 图标映射对象。以分类标题为键,网络图片 URL 为值。
|
||||
|
||||
```json
|
||||
{
|
||||
"key": "entry_buttons",
|
||||
"type": "buttonGroup",
|
||||
"title": "快捷入口",
|
||||
"args": "[\"排行\", \"分类\", \"我的\"]",
|
||||
"layoutConfig": {
|
||||
"icon": "https://example.com/default.png",
|
||||
"icons": {
|
||||
"排行": "https://example.com/rank.png",
|
||||
"我的": "https://example.com/my.png"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## kindTitle 匹配规则
|
||||
|
||||
模块加载时,系统会根据 `kindTitle` 在书源的「发现」分类列表中进行匹配:
|
||||
|
||||
1. 在 `exploreKinds()`(即发现页的分类列表)中查找 `title` 等于 `kindTitle` 的分类
|
||||
2. 如果匹配成功,使用该分类的 `url` 来加载数据
|
||||
3. 如果匹配失败,或 `kindTitle` 为空,则使用书源的默认 `exploreUrl`
|
||||
4. 如果两个 URL 都没有,该模块将显示加载错误
|
||||
|
||||
**建议**:确保 `kindTitle` 的值与发现页分类的标题**完全一致**(包括大小写和标点)。
|
||||
|
||||
## 完整示例
|
||||
|
||||
以下是一个书源配置了多种模块的完整 JSON:
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"key": "hot",
|
||||
"key": "top_banner",
|
||||
"type": "banner",
|
||||
"title": "热门推荐",
|
||||
"kindTitle": "热门",
|
||||
"displayCount": 6
|
||||
"title": "精品强推",
|
||||
"kindTitle": "首页推荐"
|
||||
},
|
||||
{
|
||||
"key": "rank_week",
|
||||
"key": "quick_nav",
|
||||
"type": "buttonGroup",
|
||||
"title": "分类导航",
|
||||
"args": "[\"武侠\", \"仙侠\", \"都市\", \"历史\"]",
|
||||
"layoutConfig": {
|
||||
"icon": "https://example.com/icons/default.png",
|
||||
"icons": {
|
||||
"武侠": "https://example.com/icons/wuxia.png"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "hot_rank",
|
||||
"type": "ranking",
|
||||
"title": "周榜",
|
||||
"kindTitle": "周排行",
|
||||
"displayCount": 10
|
||||
"title": "热门榜单",
|
||||
"kindTitle": "排行榜",
|
||||
"layoutConfig": {
|
||||
"rows": 5
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "rank_month",
|
||||
"type": "ranking",
|
||||
"title": "月榜",
|
||||
"kindTitle": "月排行",
|
||||
"displayCount": 10
|
||||
},
|
||||
{
|
||||
"key": "scifi",
|
||||
"type": "grid",
|
||||
"title": "科幻精选",
|
||||
"kindTitle": "科幻",
|
||||
"displayCount": 6
|
||||
},
|
||||
{
|
||||
"key": "new_book",
|
||||
"type": "card",
|
||||
"title": "新书上架",
|
||||
"kindTitle": "新书",
|
||||
"displayCount": 8
|
||||
"key": "explore_waterfall",
|
||||
"type": "waterfall",
|
||||
"title": "发现更多",
|
||||
"kindTitle": "全部",
|
||||
"layoutConfig": {
|
||||
"columns": 2
|
||||
}
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
每个 `key` 必须唯一,系统会按 JSON 数组中的声明顺序创建模块标识。
|
||||
|
||||
## 用户体验
|
||||
|
||||
- **发现模块**:首页会自动出现配置了 `homepageModules` 的已启用书源所声明的模块
|
||||
- **拖拽排序**:长按模块标题旁的拖拽手柄可调整顺序
|
||||
- **隐藏模块**:编辑模式下可隐藏不需要的模块
|
||||
- **下拉刷新**:刷新后所有模块重新加载数据
|
||||
- **点击书籍**:跳转到书籍详情页
|
||||
- **点击模块标题**:跳转到该书源的完整发现页
|
||||
|
||||
## 注意事项
|
||||
|
||||
- JSON 格式必须**严格有效**,建议使用在线 JSON 校验工具检查
|
||||
- 如果 JSON 解析失败,该书源的所有模块将被静默跳过
|
||||
- 模块使用的图片从书籍封面(`coverUrl`)获取,请确保发现规则正确提取了封面
|
||||
- `homepageModules` 为空的旧书源不会报错,也不会有首页模块展示
|
||||
- 同一书源可配置多个同类型模块(如多个排行榜),只需 `key` 不同即可
|
||||
- 模块标识全局唯一 ID 格式为 `"{setId}::{书源URL}::{key}"`,其中 `setId` 用于区分模块所属的集(书源集或自定义集)。
|
||||
|
||||
@@ -25,6 +25,11 @@ interface HomepageCustomSetDao {
|
||||
@Query("UPDATE homepage_custom_sets SET sortOrder = :order WHERE id = :id")
|
||||
suspend fun setSortOrder(id: String, order: Int)
|
||||
|
||||
@androidx.room.Transaction
|
||||
suspend fun batchSetSortOrders(orders: Map<String, Int>) {
|
||||
orders.forEach { (id, order) -> setSortOrder(id, order) }
|
||||
}
|
||||
|
||||
@Query("DELETE FROM homepage_custom_sets WHERE id = :id")
|
||||
suspend fun delete(id: String)
|
||||
}
|
||||
|
||||
@@ -28,6 +28,11 @@ interface HomepageModuleDao {
|
||||
@Query("UPDATE homepage_modules SET sortOrder = :order WHERE id = :id")
|
||||
suspend fun setSortOrder(id: String, order: Int)
|
||||
|
||||
@androidx.room.Transaction
|
||||
suspend fun batchSetSortOrders(orders: Map<String, Int>) {
|
||||
orders.forEach { (id, order) -> setSortOrder(id, order) }
|
||||
}
|
||||
|
||||
@Query("UPDATE homepage_modules SET customSetTitle = :title WHERE id = :id")
|
||||
suspend fun setCustomSetTitle(id: String, title: String?)
|
||||
|
||||
|
||||
@@ -34,6 +34,8 @@ class HomepageModulesRepository(
|
||||
moduleDao.setEnabled(id, enabled)
|
||||
|
||||
override suspend fun setSortOrder(id: String, order: Int) = moduleDao.setSortOrder(id, order)
|
||||
override suspend fun batchSetSortOrders(orders: Map<String, Int>) =
|
||||
moduleDao.batchSetSortOrders(orders)
|
||||
override suspend fun setCustomSetId(id: String, setId: String?) =
|
||||
moduleDao.setCustomSetId(id, setId)
|
||||
|
||||
@@ -55,6 +57,8 @@ class HomepageModulesRepository(
|
||||
|
||||
override suspend fun setCustomSetSortOrder(id: String, order: Int) =
|
||||
customSetDao.setSortOrder(id, order)
|
||||
override suspend fun batchSetCustomSetSortOrders(orders: Map<String, Int>) =
|
||||
customSetDao.batchSetSortOrders(orders)
|
||||
|
||||
override suspend fun createCustomSet(name: String): CustomSetItem {
|
||||
val entity = HomepageCustomSet(
|
||||
|
||||
@@ -15,6 +15,7 @@ interface HomepageModulesGateway {
|
||||
suspend fun upsertAll(modules: List<ModuleItem>)
|
||||
suspend fun setEnabled(id: String, enabled: Boolean)
|
||||
suspend fun setSortOrder(id: String, order: Int)
|
||||
suspend fun batchSetSortOrders(orders: Map<String, Int>)
|
||||
suspend fun setCustomSetId(id: String, setId: String?)
|
||||
suspend fun setCustomSetTitle(id: String, title: String?)
|
||||
suspend fun delete(id: String)
|
||||
@@ -27,6 +28,7 @@ interface HomepageModulesGateway {
|
||||
// Custom set mutations
|
||||
suspend fun upsertCustomSet(set: CustomSetItem)
|
||||
suspend fun setCustomSetSortOrder(id: String, order: Int)
|
||||
suspend fun batchSetCustomSetSortOrders(orders: Map<String, Int>)
|
||||
suspend fun createCustomSet(name: String): CustomSetItem
|
||||
suspend fun renameCustomSet(id: String, name: String)
|
||||
suspend fun deleteCustomSet(id: String)
|
||||
|
||||
@@ -33,8 +33,9 @@ class AddBookUseCase(
|
||||
if (source == null) {
|
||||
for (bookSourcePart in hasBookUrlPattern) {
|
||||
try {
|
||||
val bs = bookSourcePart.getBookSource()!!
|
||||
if (bookUrl.matches(bs.bookUrlPattern!!.toRegex())) {
|
||||
val bs = bookSourcePart.getBookSource() ?: continue
|
||||
val pattern = bs.bookUrlPattern ?: continue
|
||||
if (bookUrl.matches(pattern.toRegex())) {
|
||||
source = bs
|
||||
break
|
||||
}
|
||||
@@ -50,17 +51,16 @@ class AddBookUseCase(
|
||||
)
|
||||
|
||||
kotlin.runCatching {
|
||||
WebBook.getBookInfoAwait(bookSource, book)
|
||||
}.onSuccess {
|
||||
val dbBook = bookRepository.getBook(it.name, it.author)
|
||||
val bookInfo = WebBook.getBookInfoAwait(bookSource, book)
|
||||
val dbBook = bookRepository.getBook(bookInfo.name, bookInfo.author)
|
||||
if (dbBook != null) {
|
||||
val toc = WebBook.getChapterListAwait(bookSource, it).getOrThrow()
|
||||
dbBook.migrateTo(it, toc)
|
||||
bookRepository.insert(it)
|
||||
val toc = WebBook.getChapterListAwait(bookSource, bookInfo).getOrThrow()
|
||||
dbBook.migrateTo(bookInfo, toc)
|
||||
bookRepository.insert(bookInfo)
|
||||
bookRepository.insertChapters(*toc.toTypedArray())
|
||||
} else {
|
||||
it.order = bookRepository.getMinOrder() - 1
|
||||
bookRepository.insert(it)
|
||||
bookInfo.order = bookRepository.getMinOrder() - 1
|
||||
bookRepository.insert(bookInfo)
|
||||
}
|
||||
successCount++
|
||||
onProgress(successCount)
|
||||
|
||||
@@ -5,7 +5,9 @@ import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import io.legado.app.R
|
||||
import io.legado.app.ui.widget.components.modalBottomSheet.AppModalBottomSheet
|
||||
import io.legado.app.ui.widget.components.settingItem.DropdownListSettingItem
|
||||
|
||||
@@ -34,13 +36,16 @@ fun <T> HomepageLayoutSheet(
|
||||
AppModalBottomSheet(
|
||||
data = data,
|
||||
onDismissRequest = onDismissRequest,
|
||||
title = "布局设置",
|
||||
title = stringResource(R.string.homepage_layout_settings),
|
||||
) {
|
||||
Column {
|
||||
DropdownListSettingItem(
|
||||
title = "首页布局模式",
|
||||
title = stringResource(R.string.homepage_layout_mode),
|
||||
selectedValue = layoutMode.toString(),
|
||||
displayEntries = arrayOf("混合列表", "分源Tab"),
|
||||
displayEntries = arrayOf(
|
||||
stringResource(R.string.homepage_layout_mixed),
|
||||
stringResource(R.string.homepage_layout_tabs)
|
||||
),
|
||||
entryValues = arrayOf("0", "1"),
|
||||
onValueChange = { onLayoutModeChange(it.toInt()) }
|
||||
)
|
||||
|
||||
@@ -21,8 +21,6 @@ import androidx.compose.material.icons.filled.DriveFileRenameOutline
|
||||
import androidx.compose.material.icons.filled.Edit
|
||||
import androidx.compose.material.icons.filled.FilterList
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
@@ -32,14 +30,15 @@ import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.google.gson.JsonParser
|
||||
import io.legado.app.R
|
||||
import io.legado.app.domain.model.HomepageModuleType
|
||||
import io.legado.app.domain.model.ModuleDef
|
||||
import io.legado.app.ui.theme.LegadoTheme
|
||||
import io.legado.app.ui.widget.components.AppTextField
|
||||
import io.legado.app.ui.widget.components.JsonConfigEditor
|
||||
import io.legado.app.ui.widget.components.JsonKeyEditorConfig
|
||||
import io.legado.app.ui.widget.components.JsonRawEditor
|
||||
import io.legado.app.ui.widget.components.alert.AppAlertDialog
|
||||
import io.legado.app.ui.widget.components.button.SecondaryButton
|
||||
@@ -104,7 +103,8 @@ fun <T> HomepageModuleManageSheet(
|
||||
var selectedKindTitles by remember(data != null) { mutableStateOf<Set<String>>(emptySet()) }
|
||||
var showCustomSetAddModules by remember(data != null) { mutableStateOf(false) }
|
||||
var showAddButtonGroupDialog by remember(data != null) { mutableStateOf(false) }
|
||||
var tempButtonGroupTitle by remember(data != null) { mutableStateOf("快捷操作") }
|
||||
val defaultQuickActionsTitle = stringResource(R.string.homepage_quick_actions)
|
||||
var tempButtonGroupTitle by remember(data != null) { mutableStateOf(defaultQuickActionsTitle) }
|
||||
|
||||
val currentTargetSetId = remember(selectingSetUrl) {
|
||||
selectingSetUrl?.let { HomepageViewModel.customSetIdFromUrl(it) }
|
||||
@@ -130,15 +130,17 @@ fun <T> HomepageModuleManageSheet(
|
||||
groupFilter = null
|
||||
},
|
||||
title = when {
|
||||
showCustomSetAddModules -> "添加模块"
|
||||
showCustomSetAddModules -> stringResource(R.string.homepage_add_module)
|
||||
browsingSourceUrl != null && browsingDetail ->
|
||||
browseSources.find { it.sourceUrl == browsingSourceUrl }?.sourceName ?: "模块列表"
|
||||
browseSources.find { it.sourceUrl == browsingSourceUrl }?.sourceName
|
||||
?: stringResource(R.string.homepage_module_list)
|
||||
|
||||
showSourceBrowser || browsingSourceUrl != null -> "浏览书源模块"
|
||||
showSourceBrowser || browsingSourceUrl != null -> stringResource(R.string.homepage_browse_source_modules)
|
||||
selectingSetUrl != null && HomepageViewModel.isCustomSetUrl(selectingSetUrl!!) ->
|
||||
(sets.find { it.sourceUrl == selectingSetUrl }?.sourceName ?: "集详情")
|
||||
(sets.find { it.sourceUrl == selectingSetUrl }?.sourceName
|
||||
?: stringResource(R.string.homepage_set_detail))
|
||||
|
||||
else -> "首页模块管理"
|
||||
else -> stringResource(R.string.homepage_module_manage)
|
||||
},
|
||||
startAction = {
|
||||
if (showCustomSetAddModules) {
|
||||
@@ -179,7 +181,7 @@ fun <T> HomepageModuleManageSheet(
|
||||
expanded = expanded,
|
||||
onDismissRequest = { expanded = false }) {
|
||||
RoundDropdownMenuItem(
|
||||
text = "全部分组",
|
||||
text = stringResource(R.string.homepage_all_groups),
|
||||
onClick = { groupFilter = null; expanded = false },
|
||||
trailingIcon = if (groupFilter == null) {
|
||||
{ AppIcon(Icons.Default.Check, null, Modifier.size(18.dp)) }
|
||||
@@ -221,7 +223,11 @@ fun <T> HomepageModuleManageSheet(
|
||||
|
||||
Column {
|
||||
AppTabRow(
|
||||
tabTitles = listOf("已加入", "书源模块", "发现"),
|
||||
tabTitles = listOf(
|
||||
stringResource(R.string.homepage_tab_joined),
|
||||
stringResource(R.string.homepage_tab_source_modules),
|
||||
stringResource(R.string.homepage_tab_discover)
|
||||
),
|
||||
selectedTabIndex = browseTab,
|
||||
onTabSelected = { browseTab = it }
|
||||
)
|
||||
@@ -234,7 +240,7 @@ fun <T> HomepageModuleManageSheet(
|
||||
.padding(24.dp),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
AppText("暂无已加入的模块")
|
||||
AppText(stringResource(R.string.homepage_no_joined_modules))
|
||||
}
|
||||
} else {
|
||||
var listData by remember(displaySetUrl) {
|
||||
@@ -271,9 +277,9 @@ fun <T> HomepageModuleManageSheet(
|
||||
) {
|
||||
item(key = "header_standard") {
|
||||
AppText(
|
||||
text = "标准模块 (可拖拽排序)",
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
text = stringResource(R.string.homepage_standard_modules_sortable),
|
||||
style = LegadoTheme.typography.labelMedium,
|
||||
color = LegadoTheme.colorScheme.primary,
|
||||
modifier = Modifier.padding(
|
||||
horizontal = 16.dp,
|
||||
vertical = 4.dp
|
||||
@@ -318,9 +324,8 @@ fun <T> HomepageModuleManageSheet(
|
||||
)
|
||||
)
|
||||
AppText(
|
||||
text = "底栏无限模块",
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
color = MaterialTheme.colorScheme.secondary,
|
||||
text = stringResource(R.string.homepage_infinite_module_bottom),
|
||||
style = LegadoTheme.typography.labelMedium,
|
||||
modifier = Modifier.padding(
|
||||
horizontal = 16.dp,
|
||||
vertical = 4.dp
|
||||
@@ -333,7 +338,9 @@ fun <T> HomepageModuleManageSheet(
|
||||
infiniteModules.firstOrNull() == module
|
||||
SelectionItemCard(
|
||||
title = module.title,
|
||||
subtitle = HomepageModuleType.fromKey(module.type).title + if (isEffective) " · 当前生效" else " · 已被屏蔽",
|
||||
subtitle = HomepageModuleType.fromKey(module.type).title + if (isEffective) stringResource(
|
||||
R.string.homepage_status_in_effect
|
||||
) else stringResource(R.string.homepage_status_blocked),
|
||||
isEnabled = module.isVisible,
|
||||
containerColor = if (isEffective) LegadoTheme.colorScheme.surfaceContainerHigh else LegadoTheme.colorScheme.onSheetContent,
|
||||
onEnabledChange = { onToggleModule(module.id, it) },
|
||||
@@ -363,7 +370,7 @@ fun <T> HomepageModuleManageSheet(
|
||||
.padding(24.dp),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
AppText("该书源的 homepageModules JSON 为空")
|
||||
AppText(stringResource(R.string.homepage_source_json_empty))
|
||||
}
|
||||
} else {
|
||||
LazyColumn(
|
||||
@@ -376,7 +383,9 @@ fun <T> HomepageModuleManageSheet(
|
||||
val isJoined = joinedKeys.contains(module.moduleKey)
|
||||
SelectionItemCard(
|
||||
title = module.title,
|
||||
subtitle = module.moduleKey + if (isJoined) " · 已加入" else "",
|
||||
subtitle = module.moduleKey + if (isJoined) stringResource(
|
||||
R.string.homepage_status_joined
|
||||
) else "",
|
||||
containerColor = LegadoTheme.colorScheme.onSheetContent,
|
||||
isSelected = isJoined,
|
||||
inSelectionMode = true,
|
||||
@@ -405,7 +414,7 @@ fun <T> HomepageModuleManageSheet(
|
||||
HomepageModuleType.entries.filter { it != HomepageModuleType.Unknown }
|
||||
}
|
||||
CompactDropdownSettingItem(
|
||||
title = "模块类型",
|
||||
title = stringResource(R.string.homepage_module_type),
|
||||
selectedValue = browseModuleType,
|
||||
displayEntries = typeList.map { it.title }.toTypedArray(),
|
||||
entryValues = typeList.map { it.key }.toTypedArray(),
|
||||
@@ -421,14 +430,14 @@ fun <T> HomepageModuleManageSheet(
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
AppText(
|
||||
"该书源暂无发现项",
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
stringResource(R.string.homepage_source_no_discover),
|
||||
color = LegadoTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
}
|
||||
} else {
|
||||
AppText(
|
||||
"选择项",
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
stringResource(R.string.homepage_select_items),
|
||||
style = LegadoTheme.typography.labelMedium,
|
||||
modifier = Modifier.padding(
|
||||
horizontal = 16.dp,
|
||||
vertical = 4.dp
|
||||
@@ -462,7 +471,9 @@ fun <T> HomepageModuleManageSheet(
|
||||
val isJoined = joinedKeys.contains(kindTitle)
|
||||
SelectionItemCard(
|
||||
title = kindTitle,
|
||||
subtitle = kindUrl.take(60) + if (isJoined) " · 已加入" else "",
|
||||
subtitle = kindUrl.take(60) + if (isJoined) stringResource(
|
||||
R.string.homepage_status_joined
|
||||
) else "",
|
||||
containerColor = LegadoTheme.colorScheme.onSheetContent,
|
||||
isSelected = isJoined,
|
||||
inSelectionMode = true,
|
||||
@@ -482,7 +493,7 @@ fun <T> HomepageModuleManageSheet(
|
||||
}
|
||||
Spacer(modifier = Modifier.height(12.dp))
|
||||
SecondaryButton(
|
||||
text = "+ 手动添加",
|
||||
text = stringResource(R.string.homepage_manual_add),
|
||||
onClick = {
|
||||
addDialogPrefill = AddDialogPrefill(type = browseModuleType)
|
||||
},
|
||||
@@ -511,7 +522,7 @@ fun <T> HomepageModuleManageSheet(
|
||||
item(key = "header_$sourceUrl") {
|
||||
AppText(
|
||||
text = onGetSourceName(sourceUrl),
|
||||
style = MaterialTheme.typography.labelLarge,
|
||||
style = LegadoTheme.typography.labelLarge,
|
||||
modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp)
|
||||
)
|
||||
}
|
||||
@@ -551,7 +562,7 @@ fun <T> HomepageModuleManageSheet(
|
||||
val moduleCount = onGetSourceModules(source.sourceUrl, null).size
|
||||
SelectionItemCard(
|
||||
title = source.sourceName,
|
||||
subtitle = "$moduleCount 个模块",
|
||||
subtitle = stringResource(R.string.homepage_n_modules, moduleCount),
|
||||
containerColor = LegadoTheme.colorScheme.onSheetContent,
|
||||
onToggleSelection = {
|
||||
browsingSourceUrl = source.sourceUrl
|
||||
@@ -580,9 +591,9 @@ fun <T> HomepageModuleManageSheet(
|
||||
.padding(24.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
AppText("暂无模块")
|
||||
AppText(stringResource(R.string.homepage_no_modules))
|
||||
SecondaryButton(
|
||||
text = "浏览书源模块添加",
|
||||
text = stringResource(R.string.homepage_browse_to_add),
|
||||
onClick = {
|
||||
if (setId.startsWith("src_")) {
|
||||
browsingSourceUrl = setId.removePrefix("src_")
|
||||
@@ -623,8 +634,8 @@ fun <T> HomepageModuleManageSheet(
|
||||
if (listData.isNotEmpty()) {
|
||||
item(key = "header_std_detail") {
|
||||
AppText(
|
||||
text = "标准模块",
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
text = stringResource(R.string.homepage_standard_module),
|
||||
style = LegadoTheme.typography.labelMedium,
|
||||
modifier = Modifier.padding(horizontal = 16.dp, vertical = 4.dp)
|
||||
)
|
||||
}
|
||||
@@ -659,16 +670,10 @@ fun <T> HomepageModuleManageSheet(
|
||||
|
||||
if (infiniteModules.isNotEmpty()) {
|
||||
item(key = "header_inf_detail") {
|
||||
HorizontalDivider(
|
||||
modifier = Modifier.padding(
|
||||
vertical = 8.dp,
|
||||
horizontal = 16.dp
|
||||
)
|
||||
)
|
||||
PillDivider()
|
||||
AppText(
|
||||
text = "无限模块槽位",
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
color = MaterialTheme.colorScheme.secondary,
|
||||
text = stringResource(R.string.homepage_infinite_module_slot),
|
||||
style = LegadoTheme.typography.labelMedium,
|
||||
modifier = Modifier.padding(horizontal = 16.dp, vertical = 4.dp)
|
||||
)
|
||||
}
|
||||
@@ -697,7 +702,7 @@ fun <T> HomepageModuleManageSheet(
|
||||
|
||||
item(key = "browse_from_set") {
|
||||
SecondaryButton(
|
||||
text = "浏览书源模块",
|
||||
text = stringResource(R.string.homepage_browse_source_modules),
|
||||
onClick = {
|
||||
if (setId.startsWith("src_")) {
|
||||
browsingSourceUrl = setId.removePrefix("src_")
|
||||
@@ -746,7 +751,7 @@ fun <T> HomepageModuleManageSheet(
|
||||
state = setsReorderableState,
|
||||
key = set.sourceUrl,
|
||||
title = set.sourceName,
|
||||
subtitle = "${set.moduleCount} 个模块",
|
||||
subtitle = stringResource(R.string.homepage_n_modules, set.moduleCount),
|
||||
containerColor = LegadoTheme.colorScheme.onSheetContent,
|
||||
isEnabled = set.isSelected,
|
||||
onToggleSelection = { selectingSetUrl = set.sourceUrl },
|
||||
@@ -771,14 +776,14 @@ fun <T> HomepageModuleManageSheet(
|
||||
}
|
||||
item(key = "create_set") {
|
||||
SecondaryButton(
|
||||
text = "+ 新建自定义集",
|
||||
text = stringResource(R.string.homepage_new_custom_set),
|
||||
onClick = { showCreateSetDialog = true },
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
}
|
||||
item(key = "browse_sources") {
|
||||
SecondaryButton(
|
||||
text = "浏览书源模块",
|
||||
text = stringResource(R.string.homepage_browse_source_modules),
|
||||
onClick = { showSourceBrowser = true },
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
@@ -792,7 +797,7 @@ fun <T> HomepageModuleManageSheet(
|
||||
AppAlertDialog(
|
||||
data = renameSetId,
|
||||
onDismissRequest = { renameSetId = null },
|
||||
title = "重命名自定义集",
|
||||
title = stringResource(R.string.homepage_rename_custom_set),
|
||||
content = { setId ->
|
||||
val currentName =
|
||||
remember(setId) { sets.find { it.sourceUrl == setId }?.sourceName ?: "" }
|
||||
@@ -800,7 +805,7 @@ fun <T> HomepageModuleManageSheet(
|
||||
AppTextField(
|
||||
value = tempName,
|
||||
onValueChange = { tempName = it },
|
||||
label = "名称",
|
||||
label = stringResource(R.string.homepage_name_label),
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
},
|
||||
@@ -811,21 +816,21 @@ fun <T> HomepageModuleManageSheet(
|
||||
)
|
||||
renameSetId = null
|
||||
},
|
||||
confirmText = "确定",
|
||||
dismissText = "取消",
|
||||
confirmText = stringResource(R.string.dialog_confirm),
|
||||
dismissText = stringResource(R.string.dialog_cancel),
|
||||
onDismiss = { renameSetId = null }
|
||||
)
|
||||
|
||||
AppAlertDialog(
|
||||
data = if (showCreateSetDialog) Unit else null,
|
||||
onDismissRequest = { showCreateSetDialog = false },
|
||||
title = "新建自定义集",
|
||||
title = stringResource(R.string.homepage_new_custom_set_title),
|
||||
content = {
|
||||
LaunchedEffect(Unit) { tempName = "" }
|
||||
AppTextField(
|
||||
value = tempName,
|
||||
onValueChange = { tempName = it },
|
||||
label = "名称",
|
||||
label = stringResource(R.string.homepage_name_label),
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
},
|
||||
@@ -833,36 +838,36 @@ fun <T> HomepageModuleManageSheet(
|
||||
if (tempName.isNotBlank()) onCreateCustomSet(tempName)
|
||||
showCreateSetDialog = false
|
||||
},
|
||||
confirmText = "确定",
|
||||
dismissText = "取消",
|
||||
confirmText = stringResource(R.string.dialog_confirm),
|
||||
dismissText = stringResource(R.string.dialog_cancel),
|
||||
onDismiss = { showCreateSetDialog = false }
|
||||
)
|
||||
|
||||
AppAlertDialog(
|
||||
data = deleteSetConfirmId,
|
||||
onDismissRequest = { deleteSetConfirmId = null },
|
||||
title = "删除自定义集",
|
||||
text = "确定要删除该集及其包含的所有模块副本吗?",
|
||||
title = stringResource(R.string.homepage_delete_custom_set),
|
||||
text = stringResource(R.string.homepage_delete_custom_set_confirm),
|
||||
onConfirm = { setId ->
|
||||
onDeleteCustomSet(HomepageViewModel.customSetIdFromUrl(setId))
|
||||
deleteSetConfirmId = null
|
||||
},
|
||||
confirmText = "删除",
|
||||
dismissText = "取消",
|
||||
confirmText = stringResource(R.string.delete),
|
||||
dismissText = stringResource(R.string.dialog_cancel),
|
||||
onDismiss = { deleteSetConfirmId = null }
|
||||
)
|
||||
|
||||
AppAlertDialog(
|
||||
data = deleteConfirmId,
|
||||
onDismissRequest = { deleteConfirmId = null },
|
||||
title = "移除模块",
|
||||
text = "确定要从当前集中移除该模块吗?",
|
||||
title = stringResource(R.string.homepage_remove_module),
|
||||
text = stringResource(R.string.homepage_remove_module_confirm),
|
||||
onConfirm = { id ->
|
||||
onDeleteModule(id)
|
||||
deleteConfirmId = null
|
||||
},
|
||||
confirmText = "移除",
|
||||
dismissText = "取消",
|
||||
confirmText = stringResource(R.string.remove),
|
||||
dismissText = stringResource(R.string.dialog_cancel),
|
||||
onDismiss = { deleteConfirmId = null }
|
||||
)
|
||||
|
||||
@@ -900,13 +905,14 @@ fun <T> HomepageModuleManageSheet(
|
||||
AppAlertDialog(
|
||||
data = if (showAddButtonGroupDialog) Unit else null,
|
||||
onDismissRequest = { showAddButtonGroupDialog = false },
|
||||
title = "添加按钮组",
|
||||
title = stringResource(R.string.homepage_add_button_group),
|
||||
content = {
|
||||
LaunchedEffect(Unit) { tempButtonGroupTitle = "快捷操作" }
|
||||
val quickActionsLabel = stringResource(R.string.homepage_quick_actions)
|
||||
LaunchedEffect(Unit) { tempButtonGroupTitle = quickActionsLabel }
|
||||
AppTextField(
|
||||
value = tempButtonGroupTitle,
|
||||
onValueChange = { tempButtonGroupTitle = it },
|
||||
label = "模块标题",
|
||||
label = stringResource(R.string.homepage_module_title_label),
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
},
|
||||
@@ -920,21 +926,21 @@ fun <T> HomepageModuleManageSheet(
|
||||
selectedKindTitles = emptySet()
|
||||
showAddButtonGroupDialog = false
|
||||
},
|
||||
confirmText = "确定",
|
||||
dismissText = "取消",
|
||||
confirmText = stringResource(R.string.dialog_confirm),
|
||||
dismissText = stringResource(R.string.dialog_cancel),
|
||||
onDismiss = { showAddButtonGroupDialog = false }
|
||||
)
|
||||
|
||||
AppAlertDialog(
|
||||
data = customSetTitleEdit,
|
||||
onDismissRequest = { customSetTitleEdit = null },
|
||||
title = "自定义标题",
|
||||
title = stringResource(R.string.homepage_custom_title),
|
||||
content = { (_, title) ->
|
||||
LaunchedEffect(title) { titleState = title }
|
||||
AppTextField(
|
||||
value = titleState,
|
||||
onValueChange = { titleState = it },
|
||||
label = "标题",
|
||||
label = stringResource(R.string.homepage_title_label),
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
},
|
||||
@@ -942,8 +948,8 @@ fun <T> HomepageModuleManageSheet(
|
||||
onSetCustomSetTitle(id, titleState.takeIf { it.isNotBlank() })
|
||||
customSetTitleEdit = null
|
||||
},
|
||||
confirmText = "确定",
|
||||
dismissText = "取消",
|
||||
confirmText = stringResource(R.string.dialog_confirm),
|
||||
dismissText = stringResource(R.string.dialog_cancel),
|
||||
onDismiss = { customSetTitleEdit = null }
|
||||
)
|
||||
}
|
||||
@@ -974,24 +980,11 @@ fun <T> AddCustomModuleDialog(
|
||||
var layoutConfig by remember(data) { mutableStateOf(prefillLayoutConfig) }
|
||||
var showRawLayoutConfig by remember(data) { mutableStateOf(false) }
|
||||
|
||||
val layoutKeyConfigs = remember {
|
||||
mapOf(
|
||||
"fullWidth" to JsonKeyEditorConfig.Switch,
|
||||
"showTitle" to JsonKeyEditorConfig.Switch,
|
||||
"showMore" to JsonKeyEditorConfig.Switch,
|
||||
"isInfinite" to JsonKeyEditorConfig.Switch,
|
||||
"aspectRatio" to JsonKeyEditorConfig.Dropdown(
|
||||
displayEntries = arrayOf("默认", "1:1", "3:4", "2:3", "16:9"),
|
||||
entryValues = arrayOf("", "1:1", "3:4", "2:3", "16:9")
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
val hasVisualizableKeys = remember(layoutConfig) {
|
||||
runCatching {
|
||||
val jsonObject = JsonParser.parseString(layoutConfig).asJsonObject
|
||||
jsonObject.keySet().any { key ->
|
||||
key == "columns" || key == "rows" || layoutKeyConfigs.containsKey(key)
|
||||
key == "columns" || key == "rows"
|
||||
}
|
||||
}.getOrElse { false }
|
||||
}
|
||||
@@ -999,7 +992,9 @@ fun <T> AddCustomModuleDialog(
|
||||
AppAlertDialog(
|
||||
data = data,
|
||||
onDismissRequest = onDismissRequest,
|
||||
title = if (prefillTitle.isEmpty()) "添加模块" else "编辑模块",
|
||||
title = if (prefillTitle.isEmpty()) stringResource(R.string.homepage_add_module) else stringResource(
|
||||
R.string.homepage_edit_module
|
||||
),
|
||||
content = {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
@@ -1011,7 +1006,7 @@ fun <T> AddCustomModuleDialog(
|
||||
AppTextField(
|
||||
value = title,
|
||||
onValueChange = { title = it },
|
||||
label = "标题",
|
||||
label = stringResource(R.string.homepage_title_label),
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
AppTextField(
|
||||
@@ -1024,7 +1019,7 @@ fun <T> AddCustomModuleDialog(
|
||||
HomepageModuleType.entries.filter { it != HomepageModuleType.Unknown }
|
||||
}
|
||||
DropdownListSettingItem(
|
||||
title = "类型",
|
||||
title = stringResource(R.string.homepage_type_label),
|
||||
selectedValue = type,
|
||||
displayEntries = typeList.map { it.title }.toTypedArray(),
|
||||
entryValues = typeList.map { it.key }.toTypedArray(),
|
||||
@@ -1037,20 +1032,18 @@ fun <T> AddCustomModuleDialog(
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
AppText(
|
||||
text = "布局配置",
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
text = stringResource(R.string.homepage_layout_config_label),
|
||||
style = LegadoTheme.typography.labelMedium,
|
||||
modifier = Modifier.padding(top = 16.dp, bottom = 4.dp)
|
||||
)
|
||||
if (hasVisualizableKeys) {
|
||||
JsonConfigEditor(
|
||||
jsonString = layoutConfig,
|
||||
onJsonStringChange = { layoutConfig = it },
|
||||
keyConfigs = layoutKeyConfigs,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
CompactClickableSettingItem(
|
||||
title = "编辑原始 JSON (LayoutConfig)",
|
||||
title = stringResource(R.string.homepage_edit_raw_json),
|
||||
onClick = { showRawLayoutConfig = !showRawLayoutConfig }
|
||||
)
|
||||
if (showRawLayoutConfig) {
|
||||
@@ -1082,8 +1075,8 @@ fun <T> AddCustomModuleDialog(
|
||||
)
|
||||
)
|
||||
},
|
||||
confirmText = "确定",
|
||||
dismissText = "取消",
|
||||
confirmText = stringResource(R.string.dialog_confirm),
|
||||
dismissText = stringResource(R.string.dialog_cancel),
|
||||
onDismiss = onDismissRequest
|
||||
)
|
||||
}
|
||||
|
||||
@@ -44,11 +44,13 @@ import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import io.legado.app.R
|
||||
import io.legado.app.domain.model.HomepageModuleType
|
||||
import io.legado.app.ui.main.bookCoverSharedElementKey
|
||||
import io.legado.app.ui.main.homepage.modules.BannerModule
|
||||
@@ -102,6 +104,7 @@ fun HomepageScreen(
|
||||
})
|
||||
|
||||
val mixedGridState = rememberLazyStaggeredGridState()
|
||||
val homeString = stringResource(R.string.home)
|
||||
val currentTitle by remember(
|
||||
layoutMode,
|
||||
pagerState.currentPage,
|
||||
@@ -110,16 +113,16 @@ fun HomepageScreen(
|
||||
) {
|
||||
derivedStateOf {
|
||||
if (layoutMode == 1) {
|
||||
"首页"
|
||||
homeString
|
||||
} else {
|
||||
val firstHeader = mixedGridState.layoutInfo.visibleItemsInfo.firstOrNull {
|
||||
(it.key as? String)?.startsWith("header_") == true
|
||||
}
|
||||
if (firstHeader != null) {
|
||||
val id = (firstHeader.key as? String).orEmpty().substringAfter("header_", "")
|
||||
uiState.modules.find { it.globalId == id }?.setName ?: "首页"
|
||||
uiState.modules.find { it.globalId == id }?.setName ?: homeString
|
||||
} else {
|
||||
"首页"
|
||||
homeString
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -198,7 +201,7 @@ fun HomepageScreen(
|
||||
} else {
|
||||
if (selectedSets.isEmpty()) {
|
||||
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
|
||||
AppText("未选择任何书源集")
|
||||
AppText(stringResource(R.string.homepage_no_source_sets_selected))
|
||||
}
|
||||
} else {
|
||||
HorizontalPager(
|
||||
@@ -234,13 +237,13 @@ fun HomepageScreen(
|
||||
AppAlertDialog(
|
||||
data = errorMsg,
|
||||
onDismissRequest = { errorMsg = null },
|
||||
title = "模块错误",
|
||||
confirmText = "复制",
|
||||
title = stringResource(R.string.homepage_module_error),
|
||||
confirmText = stringResource(R.string.copy_text),
|
||||
onConfirm = {
|
||||
context.sendToClip(it)
|
||||
errorMsg = null
|
||||
},
|
||||
dismissText = "关闭",
|
||||
dismissText = stringResource(R.string.close),
|
||||
onDismiss = { errorMsg = null }
|
||||
)
|
||||
|
||||
@@ -316,7 +319,7 @@ private fun ModuleList(
|
||||
) {
|
||||
if (modules.isEmpty()) {
|
||||
Box(modifier = modifier, contentAlignment = Alignment.Center) {
|
||||
AppText("请在书源中添加首页模块定义")
|
||||
AppText(stringResource(R.string.homepage_add_module_definition))
|
||||
}
|
||||
} else {
|
||||
// 1. 过滤和重排模块:每个集只能有一个无限流模块,且必须在最下面
|
||||
@@ -403,7 +406,7 @@ private fun ModuleList(
|
||||
)
|
||||
Spacer(modifier = Modifier.height(4.dp))
|
||||
SecondaryButton(
|
||||
text = "重试",
|
||||
text = stringResource(R.string.retry),
|
||||
onClick = {
|
||||
viewModel.retryModule(moduleUi.globalId)
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ fun HomepageSourceSelectSheet(
|
||||
AppModalBottomSheet(
|
||||
show = show,
|
||||
onDismissRequest = onDismissRequest,
|
||||
title = "筛选书源",
|
||||
title = stringResource(R.string.homepage_filter_sources),
|
||||
) {
|
||||
Column {
|
||||
SearchBar(
|
||||
|
||||
@@ -2,6 +2,7 @@ package io.legado.app.ui.main.homepage
|
||||
|
||||
import android.app.Application
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import io.legado.app.R
|
||||
import io.legado.app.base.BaseViewModel
|
||||
import io.legado.app.data.entities.BookSource
|
||||
import io.legado.app.data.entities.SearchBook
|
||||
@@ -90,6 +91,7 @@ class HomepageViewModel(
|
||||
|
||||
private val localModulesFlow = gateway.flowEnabled()
|
||||
private val _bookSourcesCache = MutableStateFlow<Map<String, BookSource>>(emptyMap())
|
||||
private val _layoutConfigCache = MutableStateFlow<Map<String, Map<String, String>>>(emptyMap())
|
||||
|
||||
val allModulesCache = gateway.flowAll()
|
||||
.stateIn(viewModelScope, SharingStarted.Eagerly, emptyList())
|
||||
@@ -115,8 +117,8 @@ class HomepageViewModel(
|
||||
) { grouped, contentStates, flags, sourcesCache, customSets ->
|
||||
val setNames = customSets.associate { it.id to it.name }
|
||||
val sortedSetIds = customSets.sortedBy { it.sortOrder }.map { it.id }
|
||||
val configCache = _layoutConfigCache.value
|
||||
|
||||
// 按照集排序设置来排布模块
|
||||
val displayModules = sortedSetIds.flatMap { setId ->
|
||||
val setUrl = customSetUrl(setId)
|
||||
val mods = grouped[setUrl] ?: emptyList()
|
||||
@@ -125,17 +127,7 @@ class HomepageViewModel(
|
||||
val sourceName = source?.bookSourceName ?: module.sourceUrl
|
||||
val setName = module.customSetId?.let { setNames[it] } ?: sourceName
|
||||
val exploreUrl = module.url ?: source?.exploreUrl
|
||||
|
||||
val configMap = mutableMapOf<String, String>()
|
||||
module.layoutConfig?.let { configStr ->
|
||||
try {
|
||||
val json = GSON.fromJson(configStr, Map::class.java)
|
||||
json?.forEach { (k, v) ->
|
||||
configMap["layout_$k"] = v.toString()
|
||||
}
|
||||
} catch (_: Exception) {
|
||||
}
|
||||
}
|
||||
val configMap = configCache[module.id] ?: emptyMap()
|
||||
|
||||
HomepageModuleUi(
|
||||
sourceUrl = module.sourceUrl,
|
||||
@@ -203,6 +195,26 @@ class HomepageViewModel(
|
||||
private val _pendingUserModules = MutableStateFlow<List<ModuleItem>>(emptyList())
|
||||
|
||||
init {
|
||||
// 解析并缓存模块 layoutConfig,避免在 combine 中重复解析
|
||||
viewModelScope.launch {
|
||||
localModulesFlow.collect { modules ->
|
||||
val cache = mutableMapOf<String, Map<String, String>>()
|
||||
for (module in modules) {
|
||||
val configStr = module.layoutConfig ?: continue
|
||||
try {
|
||||
val json = GSON.fromJson(configStr, Map::class.java)
|
||||
if (json != null) {
|
||||
val map = mutableMapOf<String, String>()
|
||||
json.forEach { (k, v) -> map["layout_$k"] = v.toString() }
|
||||
cache[module.id] = map
|
||||
}
|
||||
} catch (_: Exception) {
|
||||
}
|
||||
}
|
||||
_layoutConfigCache.value = cache
|
||||
}
|
||||
}
|
||||
|
||||
// sync: 只处理有 homepageModules 的书源
|
||||
viewModelScope.launch {
|
||||
initModulesSyncFlow.collect { sources ->
|
||||
@@ -438,7 +450,14 @@ class HomepageViewModel(
|
||||
states[globalId] as? ModuleLoadState.Loaded ?: return@update states
|
||||
states + (globalId to lastState.copy(isLoadingMore = false))
|
||||
}
|
||||
_effects.tryEmit(HomepageEffect.ShowSnackbar("加载更多失败: ${e.message}"))
|
||||
_effects.tryEmit(
|
||||
HomepageEffect.ShowSnackbar(
|
||||
getApplication<Application>().getString(
|
||||
R.string.homepage_load_more_failed,
|
||||
e.message ?: ""
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -529,7 +548,13 @@ class HomepageViewModel(
|
||||
}
|
||||
if (hasInfinite) {
|
||||
viewModelScope.launch {
|
||||
_effects.emit(HomepageEffect.ShowSnackbar("该分组已存在无限加载模块"))
|
||||
_effects.emit(
|
||||
HomepageEffect.ShowSnackbar(
|
||||
getApplication<Application>().getString(
|
||||
R.string.homepage_module_duplicate_infinite
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -603,19 +628,18 @@ class HomepageViewModel(
|
||||
|
||||
fun reorderJoinedModules(orderedIds: List<String>) {
|
||||
viewModelScope.launch {
|
||||
orderedIds.forEachIndexed { index, id ->
|
||||
gateway.setSortOrder(id, index)
|
||||
}
|
||||
val orders = orderedIds.mapIndexed { index, id -> id to index }.toMap()
|
||||
gateway.batchSetSortOrders(orders)
|
||||
notifyConfigChanged()
|
||||
}
|
||||
}
|
||||
|
||||
fun reorderCustomSets(orderedUrls: List<String>) {
|
||||
viewModelScope.launch {
|
||||
orderedUrls.forEachIndexed { index, url ->
|
||||
val id = customSetIdFromUrl(url)
|
||||
gateway.setCustomSetSortOrder(id, index)
|
||||
}
|
||||
val orders = orderedUrls.mapIndexed { index, url ->
|
||||
customSetIdFromUrl(url) to index
|
||||
}.toMap()
|
||||
gateway.batchSetCustomSetSortOrders(orders)
|
||||
notifyConfigChanged()
|
||||
}
|
||||
}
|
||||
@@ -749,7 +773,13 @@ class HomepageViewModel(
|
||||
}
|
||||
if (hasInfinite) {
|
||||
viewModelScope.launch {
|
||||
_effects.emit(HomepageEffect.ShowSnackbar("该分组已存在无限加载模块"))
|
||||
_effects.emit(
|
||||
HomepageEffect.ShowSnackbar(
|
||||
getApplication<Application>().getString(
|
||||
R.string.homepage_module_duplicate_infinite
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -133,7 +133,7 @@ fun ButtonGroupModule(
|
||||
verticalArrangement = Arrangement.Center,
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(vertical = 12.dp, horizontal = 4.dp)
|
||||
.padding(vertical = 8.dp, horizontal = 4.dp)
|
||||
) {
|
||||
if (hasIcon) {
|
||||
SourceIcon(
|
||||
@@ -143,12 +143,12 @@ fun ButtonGroupModule(
|
||||
|
||||
}
|
||||
)
|
||||
Spacer(modifier = Modifier.height(4.dp))
|
||||
Spacer(modifier = Modifier.height(6.dp))
|
||||
}
|
||||
|
||||
AppText(
|
||||
text = displayName,
|
||||
style = LegadoTheme.typography.labelMedium,
|
||||
style = LegadoTheme.typography.labelSmallEmphasized,
|
||||
textAlign = TextAlign.Center,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Clip,
|
||||
|
||||
@@ -22,10 +22,12 @@ import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontStyle
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import io.legado.app.R
|
||||
import io.legado.app.data.entities.SearchBook
|
||||
import io.legado.app.domain.model.BookShelfState
|
||||
import io.legado.app.ui.main.bookCoverSharedElementKey
|
||||
@@ -93,7 +95,9 @@ fun RankingModule(
|
||||
modifier = Modifier.size(20.dp)
|
||||
)
|
||||
AppText(
|
||||
text = if (isExpanded) "收起" else "显示全部",
|
||||
text = if (isExpanded) stringResource(R.string.homepage_collapse) else stringResource(
|
||||
R.string.homepage_show_all
|
||||
),
|
||||
style = LegadoTheme.typography.labelMediumEmphasized,
|
||||
color = if (isExpanded) LegadoTheme.colorScheme.outline else LegadoTheme.colorScheme.primary,
|
||||
modifier = Modifier.padding(start = 4.dp)
|
||||
|
||||
@@ -77,8 +77,8 @@ fun CoilBookCover(
|
||||
}
|
||||
|
||||
val hasCustomDefault = !randomPath.isNullOrBlank()
|
||||
var isOnlineCoverLoaded by remember(path) {
|
||||
mutableStateOf(false)
|
||||
var isOnlineCoverLoaded by remember(path, sharedCoverKey, finalPath) {
|
||||
mutableStateOf(sharedCoverKey != null && finalPath != null)
|
||||
}
|
||||
|
||||
Box(
|
||||
|
||||
@@ -1532,4 +1532,63 @@
|
||||
<string name="load_with_web_log">显示WebView日志</string>
|
||||
<string name="cache_first">优先缓存</string>
|
||||
<string name="refresh_limit_unlimited">无限制</string>
|
||||
<string name="homepage_load_more_failed">加载更多失败: %1$s</string>
|
||||
<string name="homepage_module_duplicate_infinite">该分组已存在无限加载模块</string>
|
||||
<string name="homepage_quick_actions">快捷操作</string>
|
||||
<string name="homepage_add_module">添加模块</string>
|
||||
<string name="homepage_module_list">模块列表</string>
|
||||
<string name="homepage_browse_source_modules">浏览书源模块</string>
|
||||
<string name="homepage_set_detail">集详情</string>
|
||||
<string name="homepage_module_manage">首页模块管理</string>
|
||||
<string name="homepage_all_groups">全部分组</string>
|
||||
<string name="homepage_tab_joined">已加入</string>
|
||||
<string name="homepage_tab_source_modules">书源模块</string>
|
||||
<string name="homepage_tab_discover">发现</string>
|
||||
<string name="homepage_no_joined_modules">暂无已加入的模块</string>
|
||||
<string name="homepage_standard_modules_sortable">标准模块 (可拖拽排序)</string>
|
||||
<string name="homepage_infinite_module_bottom">底栏无限模块</string>
|
||||
<string name="homepage_in_effect">当前生效</string>
|
||||
<string name="homepage_blocked">已被屏蔽</string>
|
||||
<string name="homepage_source_json_empty">该书源的 homepageModules JSON 为空</string>
|
||||
<string name="homepage_already_joined">已加入</string>
|
||||
<string name="homepage_module_type">模块类型</string>
|
||||
<string name="homepage_source_no_discover">该书源暂无发现项</string>
|
||||
<string name="homepage_select_items">选择项</string>
|
||||
<string name="homepage_manual_add">+ 手动添加</string>
|
||||
<string name="homepage_n_modules">%1$d 个模块</string>
|
||||
<string name="homepage_no_modules">暂无模块</string>
|
||||
<string name="homepage_browse_to_add">浏览书源模块添加</string>
|
||||
<string name="homepage_standard_module">标准模块</string>
|
||||
<string name="homepage_infinite_module_slot">无限模块槽位</string>
|
||||
<string name="homepage_new_custom_set">+ 新建自定义集</string>
|
||||
<string name="homepage_rename_custom_set">重命名自定义集</string>
|
||||
<string name="homepage_name_label">名称</string>
|
||||
<string name="homepage_new_custom_set_title">新建自定义集</string>
|
||||
<string name="homepage_delete_custom_set">删除自定义集</string>
|
||||
<string name="homepage_delete_custom_set_confirm">确定要删除该集及其包含的所有模块副本吗?</string>
|
||||
<string name="homepage_remove_module">移除模块</string>
|
||||
<string name="homepage_remove_module_confirm">确定要从当前集中移除该模块吗?</string>
|
||||
<string name="homepage_add_button_group">添加按钮组</string>
|
||||
<string name="homepage_module_title_label">模块标题</string>
|
||||
<string name="homepage_custom_title">自定义标题</string>
|
||||
<string name="homepage_title_label">标题</string>
|
||||
<string name="homepage_edit_module">编辑模块</string>
|
||||
<string name="homepage_type_label">类型</string>
|
||||
<string name="homepage_layout_config_label">布局配置</string>
|
||||
<string name="homepage_edit_raw_json">编辑原始 JSON (LayoutConfig)</string>
|
||||
<string name="homepage_layout_settings">布局设置</string>
|
||||
<string name="homepage_layout_mode">首页布局模式</string>
|
||||
<string name="homepage_layout_mixed">混合列表</string>
|
||||
<string name="homepage_layout_tabs">分源Tab</string>
|
||||
<string name="homepage_filter_sources">筛选书源</string>
|
||||
<string name="homepage_no_source_sets_selected">未选择任何书源集</string>
|
||||
<string name="homepage_module_error">模块错误</string>
|
||||
<string name="homepage_add_module_definition">请在书源中添加首页模块定义</string>
|
||||
<string name="homepage_collapse">收起</string>
|
||||
<string name="homepage_show_all">显示全部</string>
|
||||
<string name="homepage_default">默认</string>
|
||||
<string name="remove">移除</string>
|
||||
<string name="homepage_status_in_effect"> · 当前生效</string>
|
||||
<string name="homepage_status_blocked"> · 已被屏蔽</string>
|
||||
<string name="homepage_status_joined"> · 已加入</string>
|
||||
</resources>
|
||||
|
||||
@@ -1312,4 +1312,63 @@
|
||||
<string name="cache_first">优先缓存</string>
|
||||
<string name="refresh_limit_unlimited">无限制</string>
|
||||
<string name="refresh_limit_books">%1$d 本</string>
|
||||
<string name="homepage_load_more_failed">載入更多失敗: %1$s</string>
|
||||
<string name="homepage_module_duplicate_infinite">該分組已存在無限載入模塊</string>
|
||||
<string name="homepage_quick_actions">快捷操作</string>
|
||||
<string name="homepage_add_module">添加模塊</string>
|
||||
<string name="homepage_module_list">模塊列表</string>
|
||||
<string name="homepage_browse_source_modules">瀏覽書源模塊</string>
|
||||
<string name="homepage_set_detail">集詳情</string>
|
||||
<string name="homepage_module_manage">首頁模塊管理</string>
|
||||
<string name="homepage_all_groups">全部分組</string>
|
||||
<string name="homepage_tab_joined">已加入</string>
|
||||
<string name="homepage_tab_source_modules">書源模塊</string>
|
||||
<string name="homepage_tab_discover">發現</string>
|
||||
<string name="homepage_no_joined_modules">暫無已加入的模塊</string>
|
||||
<string name="homepage_standard_modules_sortable">標準模塊 (可拖拽排序)</string>
|
||||
<string name="homepage_infinite_module_bottom">底欄無限模塊</string>
|
||||
<string name="homepage_in_effect">當前生效</string>
|
||||
<string name="homepage_blocked">已被屏蔽</string>
|
||||
<string name="homepage_source_json_empty">該書源的 homepageModules JSON 為空</string>
|
||||
<string name="homepage_already_joined">已加入</string>
|
||||
<string name="homepage_module_type">模塊類型</string>
|
||||
<string name="homepage_source_no_discover">該書源暫無發現項</string>
|
||||
<string name="homepage_select_items">選擇項</string>
|
||||
<string name="homepage_manual_add">+ 手動添加</string>
|
||||
<string name="homepage_n_modules">%1$d 個模塊</string>
|
||||
<string name="homepage_no_modules">暫無模塊</string>
|
||||
<string name="homepage_browse_to_add">瀏覽書源模塊添加</string>
|
||||
<string name="homepage_standard_module">標準模塊</string>
|
||||
<string name="homepage_infinite_module_slot">無限模塊槽位</string>
|
||||
<string name="homepage_new_custom_set">+ 新建自定義集</string>
|
||||
<string name="homepage_rename_custom_set">重命名自定義集</string>
|
||||
<string name="homepage_name_label">名稱</string>
|
||||
<string name="homepage_new_custom_set_title">新建自定義集</string>
|
||||
<string name="homepage_delete_custom_set">刪除自定義集</string>
|
||||
<string name="homepage_delete_custom_set_confirm">確定要刪除該集及其包含的所有模塊副本嗎?</string>
|
||||
<string name="homepage_remove_module">移除模塊</string>
|
||||
<string name="homepage_remove_module_confirm">確定要從當前集中移除該模塊嗎?</string>
|
||||
<string name="homepage_add_button_group">添加按鈕組</string>
|
||||
<string name="homepage_module_title_label">模塊標題</string>
|
||||
<string name="homepage_custom_title">自定義標題</string>
|
||||
<string name="homepage_title_label">標題</string>
|
||||
<string name="homepage_edit_module">編輯模塊</string>
|
||||
<string name="homepage_type_label">類型</string>
|
||||
<string name="homepage_layout_config_label">佈局配置</string>
|
||||
<string name="homepage_edit_raw_json">編輯原始 JSON (LayoutConfig)</string>
|
||||
<string name="homepage_layout_settings">佈局設置</string>
|
||||
<string name="homepage_layout_mode">首頁佈局模式</string>
|
||||
<string name="homepage_layout_mixed">混合列表</string>
|
||||
<string name="homepage_layout_tabs">分源Tab</string>
|
||||
<string name="homepage_filter_sources">篩選書源</string>
|
||||
<string name="homepage_no_source_sets_selected">未選擇任何書源集</string>
|
||||
<string name="homepage_module_error">模塊錯誤</string>
|
||||
<string name="homepage_add_module_definition">請在書源中添加首頁模塊定義</string>
|
||||
<string name="homepage_collapse">收起</string>
|
||||
<string name="homepage_show_all">顯示全部</string>
|
||||
<string name="homepage_default">預設</string>
|
||||
<string name="remove">移除</string>
|
||||
<string name="homepage_status_in_effect"> · 當前生效</string>
|
||||
<string name="homepage_status_blocked"> · 已被屏蔽</string>
|
||||
<string name="homepage_status_joined"> · 已加入</string>
|
||||
</resources>
|
||||
|
||||
@@ -1315,4 +1315,63 @@
|
||||
<string name="cache_first">优先缓存</string>
|
||||
<string name="refresh_limit_unlimited">无限制</string>
|
||||
<string name="refresh_limit_books">%1$d 本</string>
|
||||
<string name="homepage_load_more_failed">載入更多失敗: %1$s</string>
|
||||
<string name="homepage_module_duplicate_infinite">該分組已存在無限載入模組</string>
|
||||
<string name="homepage_quick_actions">快捷操作</string>
|
||||
<string name="homepage_add_module">添加模組</string>
|
||||
<string name="homepage_module_list">模組列表</string>
|
||||
<string name="homepage_browse_source_modules">瀏覽書源模組</string>
|
||||
<string name="homepage_set_detail">集詳情</string>
|
||||
<string name="homepage_module_manage">首頁模組管理</string>
|
||||
<string name="homepage_all_groups">全部分組</string>
|
||||
<string name="homepage_tab_joined">已加入</string>
|
||||
<string name="homepage_tab_source_modules">書源模組</string>
|
||||
<string name="homepage_tab_discover">發現</string>
|
||||
<string name="homepage_no_joined_modules">暫無已加入的模組</string>
|
||||
<string name="homepage_standard_modules_sortable">標準模組 (可拖拽排序)</string>
|
||||
<string name="homepage_infinite_module_bottom">底欄無限模組</string>
|
||||
<string name="homepage_in_effect">當前生效</string>
|
||||
<string name="homepage_blocked">已被屏蔽</string>
|
||||
<string name="homepage_source_json_empty">該書源的 homepageModules JSON 為空</string>
|
||||
<string name="homepage_already_joined">已加入</string>
|
||||
<string name="homepage_module_type">模組類型</string>
|
||||
<string name="homepage_source_no_discover">該書源暫無發現項</string>
|
||||
<string name="homepage_select_items">選擇項</string>
|
||||
<string name="homepage_manual_add">+ 手動添加</string>
|
||||
<string name="homepage_n_modules">%1$d 個模組</string>
|
||||
<string name="homepage_no_modules">暫無模組</string>
|
||||
<string name="homepage_browse_to_add">瀏覽書源模組添加</string>
|
||||
<string name="homepage_standard_module">標準模組</string>
|
||||
<string name="homepage_infinite_module_slot">無限模組槽位</string>
|
||||
<string name="homepage_new_custom_set">+ 新建自定義集</string>
|
||||
<string name="homepage_rename_custom_set">重命名自定義集</string>
|
||||
<string name="homepage_name_label">名稱</string>
|
||||
<string name="homepage_new_custom_set_title">新建自定義集</string>
|
||||
<string name="homepage_delete_custom_set">刪除自定義集</string>
|
||||
<string name="homepage_delete_custom_set_confirm">確定要刪除該集及其包含的所有模組副本嗎?</string>
|
||||
<string name="homepage_remove_module">移除模組</string>
|
||||
<string name="homepage_remove_module_confirm">確定要從當前集中移除該模組嗎?</string>
|
||||
<string name="homepage_add_button_group">添加按鈕組</string>
|
||||
<string name="homepage_module_title_label">模組標題</string>
|
||||
<string name="homepage_custom_title">自定義標題</string>
|
||||
<string name="homepage_title_label">標題</string>
|
||||
<string name="homepage_edit_module">編輯模組</string>
|
||||
<string name="homepage_type_label">類型</string>
|
||||
<string name="homepage_layout_config_label">佈局配置</string>
|
||||
<string name="homepage_edit_raw_json">編輯原始 JSON (LayoutConfig)</string>
|
||||
<string name="homepage_layout_settings">佈局設置</string>
|
||||
<string name="homepage_layout_mode">首頁佈局模式</string>
|
||||
<string name="homepage_layout_mixed">混合列表</string>
|
||||
<string name="homepage_layout_tabs">分源Tab</string>
|
||||
<string name="homepage_filter_sources">篩選書源</string>
|
||||
<string name="homepage_no_source_sets_selected">未選擇任何書源集</string>
|
||||
<string name="homepage_module_error">模組錯誤</string>
|
||||
<string name="homepage_add_module_definition">請在書源中添加首頁模組定義</string>
|
||||
<string name="homepage_collapse">收起</string>
|
||||
<string name="homepage_show_all">顯示全部</string>
|
||||
<string name="homepage_default">預設</string>
|
||||
<string name="remove">移除</string>
|
||||
<string name="homepage_status_in_effect"> · 目前生效</string>
|
||||
<string name="homepage_status_blocked"> · 已被封鎖</string>
|
||||
<string name="homepage_status_joined"> · 已加入</string>
|
||||
</resources>
|
||||
|
||||
@@ -1538,4 +1538,63 @@
|
||||
<string name="download_setting">Download</string>
|
||||
<string name="image_cache">Image Cache</string>
|
||||
<string name="network">Network</string>
|
||||
<string name="homepage_load_more_failed">Failed to load more: %1$s</string>
|
||||
<string name="homepage_module_duplicate_infinite">This group already has an infinite loading module</string>
|
||||
<string name="homepage_quick_actions">Quick Actions</string>
|
||||
<string name="homepage_add_module">Add Module</string>
|
||||
<string name="homepage_module_list">Module List</string>
|
||||
<string name="homepage_browse_source_modules">Browse Source Modules</string>
|
||||
<string name="homepage_set_detail">Set Detail</string>
|
||||
<string name="homepage_module_manage">Homepage Module Manager</string>
|
||||
<string name="homepage_all_groups">All Groups</string>
|
||||
<string name="homepage_tab_joined">Joined</string>
|
||||
<string name="homepage_tab_source_modules">Source Modules</string>
|
||||
<string name="homepage_tab_discover">Discover</string>
|
||||
<string name="homepage_no_joined_modules">No joined modules</string>
|
||||
<string name="homepage_standard_modules_sortable">Standard Modules (Drag to reorder)</string>
|
||||
<string name="homepage_infinite_module_bottom">Bottom Infinite Module</string>
|
||||
<string name="homepage_in_effect">In effect</string>
|
||||
<string name="homepage_blocked">Blocked</string>
|
||||
<string name="homepage_source_json_empty">The source\'s homepageModules JSON is empty</string>
|
||||
<string name="homepage_already_joined">Already joined</string>
|
||||
<string name="homepage_module_type">Module Type</string>
|
||||
<string name="homepage_source_no_discover">This source has no discover items</string>
|
||||
<string name="homepage_select_items">Select Items</string>
|
||||
<string name="homepage_manual_add">+ Manual Add</string>
|
||||
<string name="homepage_n_modules">%1$d modules</string>
|
||||
<string name="homepage_no_modules">No modules</string>
|
||||
<string name="homepage_browse_to_add">Browse Source Modules to Add</string>
|
||||
<string name="homepage_standard_module">Standard Module</string>
|
||||
<string name="homepage_infinite_module_slot">Infinite Module Slot</string>
|
||||
<string name="homepage_new_custom_set">+ New Custom Set</string>
|
||||
<string name="homepage_rename_custom_set">Rename Custom Set</string>
|
||||
<string name="homepage_name_label">Name</string>
|
||||
<string name="homepage_new_custom_set_title">New Custom Set</string>
|
||||
<string name="homepage_delete_custom_set">Delete Custom Set</string>
|
||||
<string name="homepage_delete_custom_set_confirm">Are you sure you want to delete this set and all its module copies?</string>
|
||||
<string name="homepage_remove_module">Remove Module</string>
|
||||
<string name="homepage_remove_module_confirm">Are you sure you want to remove this module from the current set?</string>
|
||||
<string name="homepage_add_button_group">Add Button Group</string>
|
||||
<string name="homepage_module_title_label">Module Title</string>
|
||||
<string name="homepage_custom_title">Custom Title</string>
|
||||
<string name="homepage_title_label">Title</string>
|
||||
<string name="homepage_edit_module">Edit Module</string>
|
||||
<string name="homepage_type_label">Type</string>
|
||||
<string name="homepage_layout_config_label">Layout Config</string>
|
||||
<string name="homepage_edit_raw_json">Edit Raw JSON (LayoutConfig)</string>
|
||||
<string name="homepage_layout_settings">Layout Settings</string>
|
||||
<string name="homepage_layout_mode">Homepage Layout Mode</string>
|
||||
<string name="homepage_layout_mixed">Mixed List</string>
|
||||
<string name="homepage_layout_tabs">Tabs by Source</string>
|
||||
<string name="homepage_filter_sources">Filter Sources</string>
|
||||
<string name="homepage_no_source_sets_selected">No source sets selected</string>
|
||||
<string name="homepage_module_error">Module Error</string>
|
||||
<string name="homepage_add_module_definition">Please add homepage module definitions in the book source</string>
|
||||
<string name="homepage_collapse">Collapse</string>
|
||||
<string name="homepage_show_all">Show All</string>
|
||||
<string name="homepage_default">Default</string>
|
||||
<string name="remove">Remove</string>
|
||||
<string name="homepage_status_in_effect"> · In effect</string>
|
||||
<string name="homepage_status_blocked"> · Blocked</string>
|
||||
<string name="homepage_status_joined"> · Already joined</string>
|
||||
</resources>
|
||||
|
||||
Reference in New Issue
Block a user