Merge remote-tracking branch 'upstream/master'

pull/1251/head
Jason Yao 3 years ago
commit b0f367f8aa
  1. 99
      .github/workflows/release.yml
  2. 49
      .github/workflows/test.yml
  3. 6
      CHANGELOG.md
  4. 40
      README.md
  5. BIN
      app/cronetlib/cronet_api.jar
  6. BIN
      app/cronetlib/cronet_impl_common_java.jar
  7. BIN
      app/cronetlib/src/cronet_api-src.jar
  8. BIN
      app/cronetlib/src/cronet_impl_common_java-src.jar
  9. 2
      app/src/main/assets/epub/chapter.html
  10. 16
      app/src/main/assets/updateLog.md
  11. 1
      app/src/main/java/io/legado/app/constant/EventBus.kt
  12. 10
      app/src/main/java/io/legado/app/data/AppDatabase.kt
  13. 3
      app/src/main/java/io/legado/app/data/dao/BookDao.kt
  14. 2
      app/src/main/java/io/legado/app/data/entities/BookGroup.kt
  15. 7
      app/src/main/java/io/legado/app/data/entities/rule/LogInRule.kt
  16. 1
      app/src/main/java/io/legado/app/help/ImageLoader.kt
  17. 7
      app/src/main/java/io/legado/app/help/http/cronet/CronetInterceptor.kt
  18. 2
      app/src/main/java/io/legado/app/help/http/cronet/CronetUrlRequestCallback.kt
  19. 25
      app/src/main/java/io/legado/app/service/AudioPlayService.kt
  20. 2
      app/src/main/java/io/legado/app/service/BaseReadAloudService.kt
  21. 66
      app/src/main/java/io/legado/app/ui/association/ImportBookSourceDialog.kt
  22. 45
      app/src/main/java/io/legado/app/ui/association/ImportBookSourceViewModel.kt
  23. 78
      app/src/main/java/io/legado/app/ui/association/ImportReplaceRuleDialog.kt
  24. 87
      app/src/main/java/io/legado/app/ui/association/ImportReplaceRuleViewModel.kt
  25. 67
      app/src/main/java/io/legado/app/ui/association/ImportRssSourceDialog.kt
  26. 44
      app/src/main/java/io/legado/app/ui/association/ImportRssSourceViewModel.kt
  27. 4
      app/src/main/java/io/legado/app/ui/book/audio/AudioPlayViewModel.kt
  28. 15
      app/src/main/java/io/legado/app/ui/book/cache/CacheActivity.kt
  29. 4
      app/src/main/java/io/legado/app/ui/book/cache/CacheViewModel.kt
  30. 5
      app/src/main/java/io/legado/app/ui/book/changesource/ChangeSourceAdapter.kt
  31. 14
      app/src/main/java/io/legado/app/ui/book/changesource/ChangeSourceDialog.kt
  32. 55
      app/src/main/java/io/legado/app/ui/book/group/GroupEdit.kt
  33. 121
      app/src/main/java/io/legado/app/ui/book/group/GroupEditDialog.kt
  34. 26
      app/src/main/java/io/legado/app/ui/book/group/GroupManageDialog.kt
  35. 45
      app/src/main/java/io/legado/app/ui/book/group/GroupSelectDialog.kt
  36. 23
      app/src/main/java/io/legado/app/ui/book/group/GroupViewModel.kt
  37. 10
      app/src/main/java/io/legado/app/ui/book/info/BookInfoActivity.kt
  38. 14
      app/src/main/java/io/legado/app/ui/book/info/BookInfoViewModel.kt
  39. 12
      app/src/main/java/io/legado/app/ui/book/info/edit/BookInfoEditActivity.kt
  40. 12
      app/src/main/java/io/legado/app/ui/book/read/ReadBookActivity.kt
  41. 4
      app/src/main/java/io/legado/app/ui/book/read/ReadBookViewModel.kt
  42. 10
      app/src/main/java/io/legado/app/ui/book/read/config/BgTextConfigDialog.kt
  43. 4
      app/src/main/java/io/legado/app/ui/book/read/page/ContentTextView.kt
  44. 4
      app/src/main/java/io/legado/app/ui/book/read/page/PageView.kt
  45. 6
      app/src/main/java/io/legado/app/ui/book/read/page/ReadView.kt
  46. 6
      app/src/main/java/io/legado/app/ui/book/read/page/provider/ChapterProvider.kt
  47. 68
      app/src/main/java/io/legado/app/ui/config/ThemeConfigFragment.kt
  48. 1
      app/src/main/java/io/legado/app/ui/dict/DictDialog.kt
  49. 52
      app/src/main/java/io/legado/app/ui/dict/DictViewModel.kt
  50. 2
      app/src/main/java/io/legado/app/ui/main/bookshelf/style2/BooksAdapterGrid.kt
  51. 2
      app/src/main/java/io/legado/app/ui/main/bookshelf/style2/BooksAdapterList.kt
  52. 6
      app/src/main/java/io/legado/app/ui/main/bookshelf/style2/BookshelfFragment2.kt
  53. 5
      app/src/main/java/io/legado/app/ui/widget/image/CoverImageView.kt
  54. 30
      app/src/main/java/io/legado/app/utils/ActivityExtensions.kt
  55. 32
      app/src/main/java/io/legado/app/utils/ActivityResultContractUtils.kt
  56. 27
      app/src/main/java/io/legado/app/utils/ActivityResultContracts.kt
  57. 29
      app/src/main/java/io/legado/app/utils/SystemUtils.kt
  58. 90
      app/src/main/res/layout/dialog_book_group_edit.xml
  59. 72
      app/src/main/res/layout/dialog_custom_group.xml
  60. 3
      app/src/main/res/layout/item_bookshelf_grid.xml
  61. 3
      app/src/main/res/layout/item_bookshelf_grid_group.xml
  62. 8
      app/src/main/res/layout/item_bookshelf_list.xml
  63. 8
      app/src/main/res/layout/item_bookshelf_list_group.xml
  64. 8
      app/src/main/res/layout/item_download.xml
  65. 5
      app/src/main/res/menu/book_info.xml
  66. 18
      app/src/main/res/menu/import_replace.xml
  67. 1
      app/src/main/res/values-es-rES/strings.xml
  68. 1
      app/src/main/res/values-ja-rJP/strings.xml
  69. 1
      app/src/main/res/values-pt-rBR/strings.xml
  70. 1
      app/src/main/res/values-zh-rHK/strings.xml
  71. 1
      app/src/main/res/values-zh-rTW/strings.xml
  72. 1
      app/src/main/res/values-zh/strings.xml
  73. 2
      app/src/main/res/values/pref_key_value.xml
  74. 1
      app/src/main/res/values/strings.xml
  75. 36
      app/src/main/res/xml/pref_config_theme.xml
  76. 6
      epublib/src/main/java/me/ag2s/epublib/util/ResourceUtil.java

@ -0,0 +1,99 @@
name: Build and Release
on:
push:
branches:
- master
paths:
- 'CHANGELOG.md'
# schedule:
# - cron: '0 4 * * *'
jobs:
build:
runs-on: ubuntu-latest
env:
# 登录蓝奏云后在控制台运行document.cookie
ylogin: ${{ secrets.LANZOU_ID }}
phpdisk_info: ${{ secrets.LANZOU_PSD }}
# 蓝奏云里的文件夹ID(阅读3测试版:2670621)
LANZOU_FOLDER_ID: 'b0f7pt4ja'
# 是否上传到artifact
UPLOAD_ARTIFACT: 'true'
steps:
- uses: actions/checkout@v2
- uses: actions/cache@v2
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: ${{ runner.os }}-legado-${{ hashFiles('**/updateLog.md') }}-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
restore-keys: |
${{ runner.os }}-legado-${{ hashFiles('**/updateLog.md') }}-
- name: Release Apk Sign
run: |
echo "给apk增加签名"
cp $GITHUB_WORKSPACE/.github/workflows/legado.jks $GITHUB_WORKSPACE/app/legado.jks
sed '$a\RELEASE_STORE_FILE=./legado.jks' $GITHUB_WORKSPACE/gradle.properties -i
sed '$a\RELEASE_KEY_ALIAS=legado' $GITHUB_WORKSPACE/gradle.properties -i
sed '$a\RELEASE_STORE_PASSWORD=gedoor_legado' $GITHUB_WORKSPACE/gradle.properties -i
sed '$a\RELEASE_KEY_PASSWORD=gedoor_legado' $GITHUB_WORKSPACE/gradle.properties -i
- name: Unify Version Name
run: |
echo "统一版本号"
VERSION=$(date -d "8 hour" -u +3.%y.%m%d%H)
echo "RELEASE_VERSION=$VERSION" >> $GITHUB_ENV
sed "/def version/c def version = \"$VERSION\"" $GITHUB_WORKSPACE/app/build.gradle -i
- name: Build With Gradle
run: |
echo "开始进行release构建"
chmod +x gradlew
./gradlew assembleAppRelease --build-cache --parallel
echo "开始进行cronet构建"
./gradlew assembleCronetRelease --build-cache --parallel
- name: Organize the Files
run: |
mkdir -p ${{ github.workspace }}/apk/
cp -rf ${{ github.workspace }}/app/build/outputs/apk/*/*/*.apk ${{ github.workspace }}/apk/
- name: Upload App To Artifact
if: ${{ env.UPLOAD_ARTIFACT != 'false' }}
uses: actions/upload-artifact@v2
with:
name: legado apk
path: ${{ github.workspace }}/apk/*.apk
- name: Upload App To Lanzou
if: ${{ env.ylogin }}
run: |
path="$GITHUB_WORKSPACE/apk/"
python3 $GITHUB_WORKSPACE/.github/scripts/lzy_web.py "$path" "$LANZOU_FOLDER_ID"
echo "[$(date -u -d '+8 hour' '+%Y.%m.%d %H:%M:%S')] 分享链接: https://kunfei.lanzoux.com/b0f7pt4ja"
- name: Release
uses: softprops/action-gh-release@59c3b4891632ff9a897f99a91d7bc557467a3a22
with:
name: legado_app_${{ env.RELEASE_VERSION }}
tag_name: ${{ env.RELEASE_VERSION }}
body_path: ${{ github.workspace }}/CHANGELOG.md
draft: false
prerelease: false
files: ${{ github.workspace }}/apk/*.apk
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Push Assets To "release" Branch
run: |
cd $GITHUB_WORKSPACE/apk || exit 1
git init
git config --local user.name "github-actions[bot]"
git config --local user.email "41898282+github-actions[bot]@users.noreply.github.com"
git checkout -b release
git add *.apk
git commit -m "${{ env.RELEASE_VERSION }}"
git remote add origin "https://${{ github.actor }}:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}"
git push -f -u origin release
- name: Purge Jsdelivr Cache
run: |
result=$(curl -s https://purge.jsdelivr.net/gh/${{ github.repository }}@release/)
if echo $result |grep -q 'success.*true'; then
echo "jsdelivr缓存更新成功"
else
echo $result
fi

@ -1,8 +1,6 @@
name: Android CI
name: Test Build
on:
release:
types: [published]
push:
branches:
- master
@ -32,7 +30,6 @@ jobs:
key: ${{ runner.os }}-legado-${{ hashFiles('**/updateLog.md') }}-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
restore-keys: |
${{ runner.os }}-legado-${{ hashFiles('**/updateLog.md') }}-
- name: Clear 18PlusList.txt
run: |
echo "清空18PlusList.txt"
@ -45,36 +42,52 @@ jobs:
sed '$a\RELEASE_KEY_ALIAS=legado' $GITHUB_WORKSPACE/gradle.properties -i
sed '$a\RELEASE_STORE_PASSWORD=gedoor_legado' $GITHUB_WORKSPACE/gradle.properties -i
sed '$a\RELEASE_KEY_PASSWORD=gedoor_legado' $GITHUB_WORKSPACE/gradle.properties -i
- name: Unify Version Name
run: |
echo "统一版本号"
VERSION=$(date -d "8 hour" -u +3.%y.%m%d%H)
VERSIONL=$(date -d "8 hour" -u +3.%y.%m%d%H%M)
echo "VERSION=$VERSIONL" >> $GITHUB_ENV
sed "/def version/c def version = \"$VERSION\"" $GITHUB_WORKSPACE/app/build.gradle -i
- name: Build With Gradle
run: |
echo "开始进行release构建"
chmod +x gradlew
./gradlew assembleAppRelease --build-cache --parallel
echo "开始进行cronet构建"
./gradlew assembleCronetRelease --build-cache --parallel
echo "修改文件名"
mkdir -p ${{ github.workspace }}/apk/
for file in `ls ${{ github.workspace }}/app/build/outputs/apk/*/*/*.apk`; do
mv "$file" ${{ github.workspace }}/apk/`echo "$file"|sed 's/.*\/\(.*\)\(\.apk\)$/\1_原包名\2/'`
done
- name: Apk Live Together
run: |
echo "设置apk共存"
sed "s/'.release'/'.releaseA'/" $GITHUB_WORKSPACE/app/build.gradle -i
sed 's/.release/.releaseA/' $GITHUB_WORKSPACE/app/google-services.json -i
echo "统一版本号"
sed "/def version/c def version = \"${{ env.VERSION }}\"" $GITHUB_WORKSPACE/app/build.gradle -i
- name: Build With Gradle
run: |
echo "开始进行release构建"
chmod +x gradlew
./gradlew assembleAppRelease --build-cache --parallel
echo "开始进行cronet构建"
./gradlew assembleCronetRelease --build-cache --parallel
echo "修改文件名"
for file in `ls ${{ github.workspace }}/app/build/outputs/apk/*/*/*.apk`; do
mv "$file" ${{ github.workspace }}/apk/`echo "$file"|sed 's/.*\/\(.*\)\(\.apk\)$/\1_共存\2/'`
done
- name: Upload App To Artifact
if: ${{ env.UPLOAD_ARTIFACT != 'false' }}
uses: actions/upload-artifact@v2
with:
name: legado apk
path: ${{ github.workspace }}/app/build/outputs/apk/app/release/*.apk
path: ${{ github.workspace }}/apk/*.apk
- name: Upload App To Lanzou
if: ${{ env.ylogin }}
run: |
path="$GITHUB_WORKSPACE/app/build/outputs/apk/app/release"
files=$(ls $path)
for f in $files
do
if [[ $f == *"apk" ]]; then
file=$f
echo "[$(date -u -d '+8 hour' '+%Y.%m.%d %H:%M:%S')] 文件:$file"
break
fi
done
python3 $GITHUB_WORKSPACE/.github/scripts/lzy_web.py "$path/$file" "$LANZOU_FOLDER_ID"
path="$GITHUB_WORKSPACE/apk/"
python3 $GITHUB_WORKSPACE/.github/scripts/lzy_web.py "$path" "$LANZOU_FOLDER_ID"
echo "[$(date -u -d '+8 hour' '+%Y.%m.%d %H:%M:%S')] 分享链接: https://kunfei.lanzoux.com/b0f810h4b"

@ -1,4 +1,4 @@
# 1.0.0 (2020-02-09)
**2021/08/09**
1. 修复选择文字不能选择单个文字的bug
2.

@ -18,7 +18,12 @@
* [web端](https://github.com/celetor/web-yuedu3)
## 下载
<a href="https://play.google.com/store/apps/details?id=io.legado.play.release"><img width="200px" alt="Google Play" src="https://play.google.com/intl/en_us/badges/static/images/badges/en_badge_web_generic.png"/></a> or <a href="https://www.coolapk.com/apk/io.legado.app.release"><img width="100px" height="100px" alt="CoolApk" src="https://github.com/Celeter/build/raw/master/.github/scripts/coolapk.png"/></a> or [Releases](https://github.com/gedoor/legado/releases/latest)
* [google play](https://play.google.com/store/apps/details?id=io.legado.play.release)
* [酷安](https://www.coolapk.com/apk/io.legado.app.release)
* [release](https://github.com/gedoor/legado/releases/latest)
* [jsdelivr](https://cdn.jsdelivr.net/gh/gedoor/legado@release/)
* [网盘](https://kunfei.lanzoui.com/b0f7pt4ja)
* [测试版](https://kunfei.lanzoui.com/b0f810h4b)
## 阅读API
* 阅读3.0 提供了2种方式的API:`Web方式`和`Content Provider方式`。您可以在[这里](api.md)根据需要自行调用。
@ -27,24 +32,21 @@
* path类型解释: 书源,订阅源,替换规则,本地txt小说目录规则,在线朗读引擎,主题,阅读排版
## 感谢
```
org.jsoup:jsoup
cn.wanghaomiao:JsoupXpath
com.jayway.jsonpath:json-path
com.github.gedoor:rhino-android
com.squareup.okhttp3:okhttp
com.ljx.rxhttp:rxhttp
com.github.bumptech.glide:glide
org.nanohttpd:nanohttpd
org.nanohttpd:nanohttpd-websocket
cn.bingoogolapple:bga-qrcode-zxing
com.jaredrummler:colorpicker
org.apache.commons:commons-text
io.noties.markwon:core
io.noties.markwon:image-glide
com.hankcs:hanlp
com.positiondev.epublib:epublib-core
```
> * org.jsoup:jsoup
> * cn.wanghaomiao:JsoupXpath
> * com.jayway.jsonpath:json-path
> * com.github.gedoor:rhino-android
> * com.squareup.okhttp3:okhttp
> * com.github.bumptech.glide:glide
> * org.nanohttpd:nanohttpd
> * org.nanohttpd:nanohttpd-websocket
> * cn.bingoogolapple:bga-qrcode-zxing
> * com.jaredrummler:colorpicker
> * org.apache.commons:commons-text
> * io.noties.markwon:core
> * io.noties.markwon:image-glide
> * com.hankcs:hanlp
> * com.positiondev.epublib:epublib-core
## 免责声明
https://gedoor.github.io/MyBookshelf/disclaimer.html

Binary file not shown.

@ -10,6 +10,8 @@
<!--<div class="logo">
<img alt="" class="logo" src="../Images/logo.png"/>
</div>-->
<br/>
<br/>
<h2 class="head">{title}</h2>
{content}
</body>

@ -10,10 +10,22 @@
* 阅读只是一个转码工具,不提供内容,第一次安装app,需要自己手动导入书源,可以从公众号 **[开源阅读]**、QQ群、酷安评论里获取由书友制作分享的书源。
* 正文出现缺字漏字、内容缺失、排版错乱等情况,有可能是净化规则出现问题, 出现简体变化问题检查一下简繁转换是否关闭。
* 漫画源看书显示乱码,**阅读与其他软件的源并不通用**,请导入阅读的支持的漫画源!
* 关于最近版本有时候界面没有数据的问题是因为把LiveData组件换成了谷歌推荐的Flow组件导致的问题,正在查找解决办法
**2021/08/02**
**2021/08/09**
* 关于最近版本有时候界面没有数据的问题是因为把LiveData组件换成了谷歌推荐的Flow组件导致的问题,正在查找解决办法
1. 修复选择文字不能选择单个文字的bug
2. 分组可选择封面
**2021/08/08**
1. 背景图片添加模糊设置
2. 书籍信息界面添加置顶操作
3. 自动翻页时屏幕常亮
4. 字典:中文使用百度汉语字典,英文使用海词字典。 by ag2s20150909
5. 导入规则时可以选择添加分组还是替换分组
**2021/08/02**
1. 换源界面功能添加:置顶,置底,删除 by h11128
2. Cronet:优化 by ag2s20150909

@ -24,4 +24,5 @@ object EventBus {
const val CHECK_SOURCE = "checkSource"
const val CHECK_SOURCE_DONE = "checkSourceDone"
const val TIP_COLOR = "tipColor"
const val SOURCE_CHANGED = "sourceChanged"
}

@ -24,7 +24,7 @@ val appDb by lazy {
RssSource::class, Bookmark::class, RssArticle::class, RssReadRecord::class,
RssStar::class, TxtTocRule::class, ReadRecord::class, HttpTTS::class, Cache::class,
RuleSub::class],
version = 33,
version = 34,
exportSchema = true
)
abstract class AppDatabase : RoomDatabase() {
@ -60,7 +60,7 @@ abstract class AppDatabase : RoomDatabase() {
migration_19_20, migration_20_21, migration_21_22, migration_22_23,
migration_23_24, migration_24_25, migration_25_26, migration_26_27,
migration_27_28, migration_28_29, migration_29_30, migration_30_31,
migration_31_32, migration_32_33
migration_31_32, migration_32_33, migration_33_34
)
.allowMainThreadQueries()
.addCallback(dbCallback)
@ -331,6 +331,12 @@ abstract class AppDatabase : RoomDatabase() {
)
}
}
private val migration_33_34 = object : Migration(33, 34) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE `book_groups` ADD `cover` TEXT")
}
}
}
}

@ -62,6 +62,9 @@ interface BookDao {
@get:Query("SELECT COUNT(*) FROM books")
val allBookCount: Int
@get:Query("select min(`order`) from books")
val minOrder: Int
@get:Query("select max(`order`) from books")
val maxOrder: Int

@ -14,6 +14,7 @@ data class BookGroup(
@PrimaryKey
val groupId: Long = 0b1,
var groupName: String,
var cover: String? = null,
var order: Int = 0,
var show: Boolean = true
) : Parcelable {
@ -36,6 +37,7 @@ data class BookGroup(
if (other is BookGroup) {
return other.groupId == groupId
&& other.groupName == groupName
&& other.cover == cover
&& other.order == order
&& other.show == show
}

@ -0,0 +1,7 @@
package io.legado.app.data.entities.rule
data class LogInRule(
val ui: HashMap<String, String>,
val logInUrl: String,
val checkJs: String
)

@ -12,6 +12,7 @@ import io.legado.app.utils.isAbsUrl
import io.legado.app.utils.isContentScheme
import java.io.File
@Suppress("unused")
object ImageLoader {
/**

@ -27,7 +27,12 @@ class CronetInterceptor(private val cookieJar: CookieJar?) : Interceptor {
response
} catch (e: Exception) {
//遇到Cronet处理有问题时的情况,如证书过期等等,回退到okhttp处理
chain.proceed(original)
if (e.message.toString().contains("ERR_CERT_DATE_INVALID", true)) {
chain.proceed(original)
} else {
throw e
}
}

@ -147,7 +147,6 @@ class CronetUrlRequestCallback @JvmOverloads internal constructor(
private const val TAG = "Callback"
private const val MAX_FOLLOW_COUNT = 20
@Suppress("DEPRECATION")
private fun protocolFromNegotiatedProtocol(responseInfo: UrlResponseInfo): Protocol {
val negotiatedProtocol = responseInfo.negotiatedProtocol.lowercase(Locale.getDefault())
// Log.e("Cronet", responseInfo.url)
@ -160,6 +159,7 @@ class CronetUrlRequestCallback @JvmOverloads internal constructor(
Protocol.QUIC
}
negotiatedProtocol.contains("spdy") -> {
@Suppress("DEPRECATION")
Protocol.SPDY_3
}
negotiatedProtocol.contains("h2") -> {

@ -161,17 +161,22 @@ class AudioPlayService : BaseService(),
}
private fun resume() {
pause = false
if (!mediaPlayer.isPlaying) {
mediaPlayer.start()
mediaPlayer.seekTo(position)
try {
pause = false
if (!mediaPlayer.isPlaying) {
mediaPlayer.start()
mediaPlayer.seekTo(position)
}
handler.removeCallbacks(mpRunnable)
handler.postDelayed(mpRunnable, 1000)
upMediaSessionPlaybackState(PlaybackStateCompat.STATE_PLAYING)
AudioPlay.status = Status.PLAY
postEvent(EventBus.AUDIO_STATE, Status.PLAY)
upNotification()
} catch (e: Exception) {
e.printStackTrace()
stopSelf()
}
handler.removeCallbacks(mpRunnable)
handler.postDelayed(mpRunnable, 1000)
upMediaSessionPlaybackState(PlaybackStateCompat.STATE_PLAYING)
AudioPlay.status = Status.PLAY
postEvent(EventBus.AUDIO_STATE, Status.PLAY)
upNotification()
}
private fun adjustProgress(position: Int) {

@ -1,5 +1,6 @@
package io.legado.app.service
import android.annotation.SuppressLint
import android.app.PendingIntent
import android.content.BroadcastReceiver
import android.content.Context
@ -225,6 +226,7 @@ abstract class BaseReadAloudService : BaseService(),
/**
* 初始化MediaSession, 注册多媒体按钮
*/
@SuppressLint("UnspecifiedImmutableFlag")
private fun initMediaSession() {
mediaSessionCompat.setCallback(object : MediaSessionCompat.Callback() {
override fun onMediaButtonEvent(mediaButtonEvent: Intent): Boolean {

@ -20,7 +20,7 @@ import io.legado.app.constant.AppPattern
import io.legado.app.constant.PreferKey
import io.legado.app.data.appDb
import io.legado.app.data.entities.BookSource
import io.legado.app.databinding.DialogEditTextBinding
import io.legado.app.databinding.DialogCustomGroupBinding
import io.legado.app.databinding.DialogRecyclerViewBinding
import io.legado.app.databinding.ItemSourceImportBinding
import io.legado.app.help.AppConfig
@ -107,7 +107,7 @@ class ImportBookSourceDialog : BaseDialogFragment(), Toolbar.OnMenuItemClickList
}
binding.tvFooterLeft.visible()
binding.tvFooterLeft.setOnClickListener {
val selectAll = viewModel.isSelectAll()
val selectAll = viewModel.isSelectAll
viewModel.selectStatus.forEachIndexed { index, b ->
if (b != !selectAll) {
viewModel.selectStatus[index] = !selectAll
@ -144,16 +144,16 @@ class ImportBookSourceDialog : BaseDialogFragment(), Toolbar.OnMenuItemClickList
}
private fun upSelectText() {
if (viewModel.isSelectAll()) {
if (viewModel.isSelectAll) {
binding.tvFooterLeft.text = getString(
R.string.select_cancel_count,
viewModel.selectCount(),
viewModel.selectCount,
viewModel.allSources.size
)
} else {
binding.tvFooterLeft.text = getString(
R.string.select_all_count,
viewModel.selectCount(),
viewModel.selectCount,
viewModel.allSources.size
)
}
@ -169,29 +169,7 @@ class ImportBookSourceDialog : BaseDialogFragment(), Toolbar.OnMenuItemClickList
@SuppressLint("InflateParams")
override fun onMenuItemClick(item: MenuItem): Boolean {
when (item.itemId) {
R.id.menu_new_group -> {
alert(R.string.diy_edit_source_group) {
val alertBinding = DialogEditTextBinding.inflate(layoutInflater).apply {
val groups = linkedSetOf<String>()
appDb.bookSourceDao.allGroup.forEach { group ->
groups.addAll(group.splitNotBlank(AppPattern.splitGroupRegex))
}
textInputLayout.setHint(R.string.group_name)
editView.setFilterValues(groups.toList())
editView.dropDownHeight = 180.dp
}
customView {
alertBinding.root
}
okButton {
alertBinding.editView.text?.toString()?.let { group ->
viewModel.groupName = group
item.title = getString(R.string.diy_edit_source_group_title, group)
}
}
noButton()
}.show()
}
R.id.menu_new_group -> alertCustomGroup(item)
R.id.menu_Keep_original_name -> {
item.isChecked = !item.isChecked
putPrefBoolean(PreferKey.importKeepName, item.isChecked)
@ -200,6 +178,38 @@ class ImportBookSourceDialog : BaseDialogFragment(), Toolbar.OnMenuItemClickList
return false
}
private fun alertCustomGroup(item: MenuItem) {
alert(R.string.diy_edit_source_group) {
val alertBinding = DialogCustomGroupBinding.inflate(layoutInflater).apply {
val groups = linkedSetOf<String>()
appDb.bookSourceDao.allGroup.forEach { group ->
groups.addAll(group.splitNotBlank(AppPattern.splitGroupRegex))
}
textInputLayout.setHint(R.string.group_name)
editView.setFilterValues(groups.toList())
editView.dropDownHeight = 180.dp
}
customView {
alertBinding.root
}
okButton {
viewModel.isAddGroup = alertBinding.swAddGroup.isChecked
viewModel.groupName = alertBinding.editView.text?.toString()
if (viewModel.groupName.isNullOrBlank()) {
item.title = getString(R.string.diy_source_group)
} else {
val group = getString(R.string.diy_edit_source_group_title, viewModel.groupName)
if (viewModel.isAddGroup) {
item.title = "+$group"
} else {
item.title = group
}
}
}
noButton()
}.show()
}
inner class SourcesAdapter(context: Context) :
RecyclerAdapter<BookSource, ItemSourceImportBinding>(context) {

@ -5,6 +5,7 @@ import androidx.lifecycle.MutableLiveData
import com.jayway.jsonpath.JsonPath
import io.legado.app.R
import io.legado.app.base.BaseViewModel
import io.legado.app.constant.AppPattern
import io.legado.app.data.appDb
import io.legado.app.data.entities.BookSource
import io.legado.app.help.AppConfig
@ -18,8 +19,10 @@ import io.legado.app.help.storage.Restore
import io.legado.app.utils.isAbsUrl
import io.legado.app.utils.isJsonArray
import io.legado.app.utils.isJsonObject
import io.legado.app.utils.splitNotBlank
class ImportBookSourceViewModel(app: Application) : BaseViewModel(app) {
var isAddGroup = false
var groupName: String? = null
val errorLiveData = MutableLiveData<String>()
val successLiveData = MutableLiveData<Int>()
@ -28,27 +31,30 @@ class ImportBookSourceViewModel(app: Application) : BaseViewModel(app) {
val checkSources = arrayListOf<BookSource?>()
val selectStatus = arrayListOf<Boolean>()
fun isSelectAll(): Boolean {
selectStatus.forEach {
if (!it) {
return false
val isSelectAll: Boolean
get() {
selectStatus.forEach {
if (!it) {
return false
}
}
return true
}
return true
}
fun selectCount(): Int {
var count = 0
selectStatus.forEach {
if (it) {
count++
val selectCount: Int
get() {
var count = 0
selectStatus.forEach {
if (it) {
count++
}
}
return count
}
return count
}
fun importSelect(finally: () -> Unit) {
execute {
val group = groupName?.trim()
val keepName = AppConfig.importKeepName
val selectSource = arrayListOf<BookSource>()
selectStatus.forEachIndexed { index, b ->
@ -61,8 +67,17 @@ class ImportBookSourceViewModel(app: Application) : BaseViewModel(app) {
source.customOrder = it.customOrder
}
}
if (groupName != null) {
source.bookSourceGroup = groupName
if (!group.isNullOrEmpty()) {
if (isAddGroup) {
val groups = linkedSetOf<String>()
source.bookSourceGroup?.splitNotBlank(AppPattern.splitGroupRegex)?.let {
groups.addAll(it)
}
groups.add(group)
source.bookSourceGroup = groups.joinToString(",")
} else {
source.bookSourceGroup = group
}
}
selectSource.add(source)
}

@ -5,8 +5,10 @@ import android.content.Context
import android.content.DialogInterface
import android.os.Bundle
import android.view.LayoutInflater
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.widget.Toolbar
import androidx.fragment.app.FragmentManager
import androidx.fragment.app.viewModels
import androidx.recyclerview.widget.LinearLayoutManager
@ -14,15 +16,23 @@ import io.legado.app.R
import io.legado.app.base.BaseDialogFragment
import io.legado.app.base.adapter.ItemViewHolder
import io.legado.app.base.adapter.RecyclerAdapter
import io.legado.app.constant.AppPattern
import io.legado.app.constant.PreferKey
import io.legado.app.data.appDb
import io.legado.app.data.entities.ReplaceRule
import io.legado.app.databinding.DialogCustomGroupBinding
import io.legado.app.databinding.DialogRecyclerViewBinding
import io.legado.app.databinding.ItemSourceImportBinding
import io.legado.app.lib.dialogs.alert
import io.legado.app.lib.theme.primaryColor
import io.legado.app.ui.widget.dialog.WaitDialog
import io.legado.app.utils.dp
import io.legado.app.utils.putPrefBoolean
import io.legado.app.utils.splitNotBlank
import io.legado.app.utils.viewbindingdelegate.viewBinding
import io.legado.app.utils.visible
class ImportReplaceRuleDialog : BaseDialogFragment() {
class ImportReplaceRuleDialog : BaseDialogFragment(), Toolbar.OnMenuItemClickListener {
companion object {
fun start(
@ -71,6 +81,7 @@ class ImportReplaceRuleDialog : BaseDialogFragment() {
binding.toolBar.setBackgroundColor(primaryColor)
binding.toolBar.setTitle(R.string.import_replace_rule)
binding.rotateLoading.show()
initMenu()
adapter = SourcesAdapter(requireContext())
binding.recyclerView.layoutManager = LinearLayoutManager(requireContext())
binding.recyclerView.adapter = adapter
@ -89,7 +100,7 @@ class ImportReplaceRuleDialog : BaseDialogFragment() {
}
binding.tvFooterLeft.visible()
binding.tvFooterLeft.setOnClickListener {
val selectAll = viewModel.isSelectAll()
val selectAll = viewModel.isSelectAll
viewModel.selectStatus.forEachIndexed { index, b ->
if (b != !selectAll) {
viewModel.selectStatus[index] = !selectAll
@ -125,17 +136,65 @@ class ImportReplaceRuleDialog : BaseDialogFragment() {
viewModel.import(source)
}
private fun initMenu() {
binding.toolBar.setOnMenuItemClickListener(this)
binding.toolBar.inflateMenu(R.menu.import_replace)
}
override fun onMenuItemClick(item: MenuItem?): Boolean {
when (item?.itemId) {
R.id.menu_new_group -> alertCustomGroup(item)
R.id.menu_Keep_original_name -> {
item.isChecked = !item.isChecked
putPrefBoolean(PreferKey.importKeepName, item.isChecked)
}
}
return true
}
private fun alertCustomGroup(item: MenuItem) {
alert(R.string.diy_edit_source_group) {
val alertBinding = DialogCustomGroupBinding.inflate(layoutInflater).apply {
val groups = linkedSetOf<String>()
appDb.bookSourceDao.allGroup.forEach { group ->
groups.addAll(group.splitNotBlank(AppPattern.splitGroupRegex))
}
textInputLayout.setHint(R.string.group_name)
editView.setFilterValues(groups.toList())
editView.dropDownHeight = 180.dp
}
customView {
alertBinding.root
}
okButton {
viewModel.isAddGroup = alertBinding.swAddGroup.isChecked
viewModel.groupName = alertBinding.editView.text?.toString()
if (viewModel.groupName.isNullOrBlank()) {
item.title = getString(R.string.diy_source_group)
} else {
val group = getString(R.string.diy_edit_source_group_title, viewModel.groupName)
if (viewModel.isAddGroup) {
item.title = "+$group"
} else {
item.title = group
}
}
}
noButton()
}.show()
}
private fun upSelectText() {
if (viewModel.isSelectAll()) {
if (viewModel.isSelectAll) {
binding.tvFooterLeft.text = getString(
R.string.select_cancel_count,
viewModel.selectCount(),
viewModel.selectCount,
viewModel.allRules.size
)
} else {
binding.tvFooterLeft.text = getString(
R.string.select_all_count,
viewModel.selectCount(),
viewModel.selectCount,
viewModel.allRules.size
)
}
@ -157,6 +216,15 @@ class ImportReplaceRuleDialog : BaseDialogFragment() {
binding.run {
cbSourceName.isChecked = viewModel.selectStatus[holder.layoutPosition]
cbSourceName.text = item.name
val localRule = viewModel.checkRules[holder.layoutPosition]
tvSourceState.text = when {
localRule == null -> "新规则"
item.pattern != localRule.pattern
|| item.replacement != localRule.replacement
|| item.isRegex != localRule.isRegex
|| item.scope != localRule.scope -> "更新"
else -> "已存在"
}
}
}

@ -3,6 +3,7 @@ package io.legado.app.ui.association
import android.app.Application
import androidx.lifecycle.MutableLiveData
import io.legado.app.base.BaseViewModel
import io.legado.app.constant.AppPattern
import io.legado.app.data.appDb
import io.legado.app.data.entities.ReplaceRule
import io.legado.app.help.AppConfig
@ -11,8 +12,11 @@ import io.legado.app.help.http.okHttpClient
import io.legado.app.help.http.text
import io.legado.app.help.storage.OldReplace
import io.legado.app.utils.isAbsUrl
import io.legado.app.utils.splitNotBlank
class ImportReplaceRuleViewModel(app: Application) : BaseViewModel(app) {
var isAddGroup = false
var groupName: String? = null
val errorLiveData = MutableLiveData<String>()
val successLiveData = MutableLiveData<Int>()
@ -20,23 +24,61 @@ class ImportReplaceRuleViewModel(app: Application) : BaseViewModel(app) {
val checkRules = arrayListOf<ReplaceRule?>()
val selectStatus = arrayListOf<Boolean>()
fun isSelectAll(): Boolean {
selectStatus.forEach {
if (!it) {
return false
val isSelectAll: Boolean
get() {
selectStatus.forEach {
if (!it) {
return false
}
}
return true
}
return true
}
fun selectCount(): Int {
var count = 0
selectStatus.forEach {
if (it) {
count++
val selectCount: Int
get() {
var count = 0
selectStatus.forEach {
if (it) {
count++
}
}
return count
}
fun importSelect(finally: () -> Unit) {
execute {
val group = groupName?.trim()
val keepName = AppConfig.importKeepName
val selectRules = arrayListOf<ReplaceRule>()
selectStatus.forEachIndexed { index, b ->
if (b) {
val rule = allRules[index]
if (keepName) {
checkRules[index]?.let {
rule.name = it.name
rule.group = it.group
rule.order = it.order
}
}
if (!group.isNullOrEmpty()) {
if (isAddGroup) {
val groups = linkedSetOf<String>()
rule.group?.splitNotBlank(AppPattern.splitGroupRegex)?.let {
groups.addAll(it)
}
groups.add(group)
rule.group = groups.joinToString(",")
} else {
rule.group = group
}
}
selectRules.add(rule)
}
}
appDb.replaceRuleDao.insert(*selectRules.toTypedArray())
}.onFinally {
finally.invoke()
}
return count
}
fun import(text: String) {
@ -59,27 +101,12 @@ class ImportReplaceRuleViewModel(app: Application) : BaseViewModel(app) {
}
}
fun importSelect(finally: () -> Unit) {
execute {
val keepName = AppConfig.importKeepName
val selectRules = arrayListOf<ReplaceRule>()
selectStatus.forEachIndexed { index, b ->
if (b) {
val rule = allRules[index]
selectRules.add(rule)
}
}
appDb.replaceRuleDao.insert(*selectRules.toTypedArray())
}.onFinally {
finally.invoke()
}
}
private fun comparisonSource() {
execute {
allRules.forEach {
checkRules.add(null)
selectStatus.add(false)
val rule = appDb.replaceRuleDao.findById(it.id)
checkRules.add(rule)
selectStatus.add(rule == null)
}
}.onSuccess {
successLiveData.postValue(allRules.size)

@ -20,7 +20,7 @@ import io.legado.app.constant.AppPattern
import io.legado.app.constant.PreferKey
import io.legado.app.data.appDb
import io.legado.app.data.entities.RssSource
import io.legado.app.databinding.DialogEditTextBinding
import io.legado.app.databinding.DialogCustomGroupBinding
import io.legado.app.databinding.DialogRecyclerViewBinding
import io.legado.app.databinding.ItemSourceImportBinding
import io.legado.app.help.AppConfig
@ -80,6 +80,7 @@ class ImportRssSourceDialog : BaseDialogFragment(), Toolbar.OnMenuItemClickListe
return inflater.inflate(R.layout.dialog_recycler_view, container)
}
@SuppressLint("NotifyDataSetChanged")
override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) {
binding.toolBar.setBackgroundColor(primaryColor)
binding.toolBar.setTitle(R.string.import_rss_source)
@ -103,7 +104,7 @@ class ImportRssSourceDialog : BaseDialogFragment(), Toolbar.OnMenuItemClickListe
}
binding.tvFooterLeft.visible()
binding.tvFooterLeft.setOnClickListener {
val selectAll = viewModel.isSelectAll()
val selectAll = viewModel.isSelectAll
viewModel.selectStatus.forEachIndexed { index, b ->
if (b != !selectAll) {
viewModel.selectStatus[index] = !selectAll
@ -140,16 +141,16 @@ class ImportRssSourceDialog : BaseDialogFragment(), Toolbar.OnMenuItemClickListe
}
private fun upSelectText() {
if (viewModel.isSelectAll()) {
if (viewModel.isSelectAll) {
binding.tvFooterLeft.text = getString(
R.string.select_cancel_count,
viewModel.selectCount(),
viewModel.selectCount,
viewModel.allSources.size
)
} else {
binding.tvFooterLeft.text = getString(
R.string.select_all_count,
viewModel.selectCount(),
viewModel.selectCount,
viewModel.allSources.size
)
}
@ -165,29 +166,7 @@ class ImportRssSourceDialog : BaseDialogFragment(), Toolbar.OnMenuItemClickListe
@SuppressLint("InflateParams")
override fun onMenuItemClick(item: MenuItem): Boolean {
when (item.itemId) {
R.id.menu_new_group -> {
alert(R.string.diy_edit_source_group) {
val alertBinding = DialogEditTextBinding.inflate(layoutInflater).apply {
val groups = linkedSetOf<String>()
appDb.rssSourceDao.allGroup.forEach { group ->
groups.addAll(group.splitNotBlank(AppPattern.splitGroupRegex))
}
textInputLayout.setHint(R.string.group_name)
editView.setFilterValues(groups.toList())
editView.dropDownHeight = 180.dp
}
customView {
alertBinding.root
}
okButton {
alertBinding.editView.text?.toString()?.let { group ->
viewModel.groupName = group
item.title = getString(R.string.diy_edit_source_group_title, group)
}
}
noButton()
}.show()
}
R.id.menu_new_group -> alertCustomGroup(item)
R.id.menu_Keep_original_name -> {
item.isChecked = !item.isChecked
putPrefBoolean(PreferKey.importKeepName, item.isChecked)
@ -196,6 +175,38 @@ class ImportRssSourceDialog : BaseDialogFragment(), Toolbar.OnMenuItemClickListe
return false
}
private fun alertCustomGroup(item: MenuItem) {
alert(R.string.diy_edit_source_group) {
val alertBinding = DialogCustomGroupBinding.inflate(layoutInflater).apply {
val groups = linkedSetOf<String>()
appDb.rssSourceDao.allGroup.forEach { group ->
groups.addAll(group.splitNotBlank(AppPattern.splitGroupRegex))
}
textInputLayout.setHint(R.string.group_name)
editView.setFilterValues(groups.toList())
editView.dropDownHeight = 180.dp
}
customView {
alertBinding.root
}
okButton {
viewModel.isAddGroup = alertBinding.swAddGroup.isChecked
viewModel.groupName = alertBinding.editView.text?.toString()
if (viewModel.groupName.isNullOrBlank()) {
item.title = getString(R.string.diy_source_group)
} else {
val group = getString(R.string.diy_edit_source_group_title, viewModel.groupName)
if (viewModel.isAddGroup) {
item.title = "+$group"
} else {
item.title = group
}
}
}
noButton()
}.show()
}
inner class SourcesAdapter(context: Context) :
RecyclerAdapter<RssSource, ItemSourceImportBinding>(context) {

@ -5,6 +5,7 @@ import androidx.lifecycle.MutableLiveData
import com.jayway.jsonpath.JsonPath
import io.legado.app.R
import io.legado.app.base.BaseViewModel
import io.legado.app.constant.AppPattern
import io.legado.app.data.appDb
import io.legado.app.data.entities.RssSource
import io.legado.app.help.AppConfig
@ -16,6 +17,7 @@ import io.legado.app.help.storage.Restore
import io.legado.app.utils.*
class ImportRssSourceViewModel(app: Application) : BaseViewModel(app) {
var isAddGroup = false
var groupName: String? = null
val errorLiveData = MutableLiveData<String>()
val successLiveData = MutableLiveData<Int>()
@ -24,27 +26,30 @@ class ImportRssSourceViewModel(app: Application) : BaseViewModel(app) {
val checkSources = arrayListOf<RssSource?>()
val selectStatus = arrayListOf<Boolean>()
fun isSelectAll(): Boolean {
selectStatus.forEach {
if (!it) {
return false
val isSelectAll: Boolean
get() {
selectStatus.forEach {
if (!it) {
return false
}
}
return true
}
return true
}
fun selectCount(): Int {
var count = 0
selectStatus.forEach {
if (it) {
count++
val selectCount: Int
get() {
var count = 0
selectStatus.forEach {
if (it) {
count++
}
}
return count
}
return count
}
fun importSelect(finally: () -> Unit) {
execute {
val group = groupName?.trim()
val keepName = AppConfig.importKeepName
val selectSource = arrayListOf<RssSource>()
selectStatus.forEachIndexed { index, b ->
@ -57,8 +62,17 @@ class ImportRssSourceViewModel(app: Application) : BaseViewModel(app) {
source.customOrder = it.customOrder
}
}
if (groupName != null) {
source.sourceGroup = groupName
if (!group.isNullOrEmpty()) {
if (isAddGroup) {
val groups = linkedSetOf<String>()
source.sourceGroup?.splitNotBlank(AppPattern.splitGroupRegex)?.let {
groups.addAll(it)
}
groups.add(group)
source.sourceGroup = groups.joinToString(",")
} else {
source.sourceGroup = group
}
}
selectSource.add(source)
}

@ -4,12 +4,14 @@ import android.app.Application
import android.content.Intent
import io.legado.app.R
import io.legado.app.base.BaseViewModel
import io.legado.app.constant.EventBus
import io.legado.app.data.appDb
import io.legado.app.data.entities.Book
import io.legado.app.data.entities.BookChapter
import io.legado.app.help.BookHelp
import io.legado.app.model.webBook.WebBook
import io.legado.app.service.help.AudioPlay
import io.legado.app.utils.postEvent
import io.legado.app.utils.toastOnUi
import kotlinx.coroutines.Dispatchers
@ -98,6 +100,8 @@ class AudioPlayViewModel(application: Application) : BaseViewModel(application)
} else {
loadChapterList(book1) { upChangeDurChapterIndex(book1, oldTocSize, it) }
}
}.onFinally {
postEvent(EventBus.SOURCE_CHANGED, book1.bookUrl)
}
}

@ -1,5 +1,6 @@
package io.legado.app.ui.book.cache
import android.annotation.SuppressLint
import android.os.Bundle
import android.view.Menu
import android.view.MenuItem
@ -44,6 +45,7 @@ class CacheActivity : VMBaseActivity<ActivityCacheBookBinding, CacheViewModel>()
override val viewModel by viewModels<CacheViewModel>()
private val exportBookPathKey = "exportBookPath"
private val exportTypes = arrayListOf("txt", "epub")
lateinit var adapter: CacheAdapter
private var booksFlowJob: Job? = null
private var menu: Menu? = null
@ -91,6 +93,10 @@ class CacheActivity : VMBaseActivity<ActivityCacheBookBinding, CacheViewModel>()
override fun onMenuOpened(featureId: Int, menu: Menu): Boolean {
menu.findItem(R.id.menu_enable_replace)?.isChecked = AppConfig.exportUseReplace
menu.findItem(R.id.menu_export_web_dav)?.isChecked = AppConfig.exportToWebDav
menu.findItem(R.id.menu_export_type)?.title =
"${getString(R.string.export_type)}(${getTypeName()})"
menu.findItem(R.id.menu_export_charset)?.title =
"${getString(R.string.export_charset)}(${AppConfig.exportCharset})"
return super.onMenuOpened(featureId, menu)
}
@ -173,6 +179,7 @@ class CacheActivity : VMBaseActivity<ActivityCacheBookBinding, CacheViewModel>()
}
}
@SuppressLint("NotifyDataSetChanged")
private fun initGroupData() {
launch {
appDb.bookGroupDao.flowAll().collect {
@ -309,8 +316,14 @@ class CacheActivity : VMBaseActivity<ActivityCacheBookBinding, CacheViewModel>()
}.show()
}
private fun getTypeName(): String {
return exportTypes.getOrElse(AppConfig.exportType) {
exportTypes[0]
}
}
private fun showExportTypeConfig() {
selector(R.string.export_type, arrayListOf("txt", "epub")) { _, i ->
selector(R.string.export_type, exportTypes) { _, i ->
AppConfig.exportType = i
}
}

@ -33,7 +33,7 @@ import javax.script.SimpleBindings
class CacheViewModel(application: Application) : BaseViewModel(application) {
fun getExportFileName(book: Book): String {
private fun getExportFileName(book: Book): String {
val jsStr = AppConfig.bookExportFileName
if (jsStr.isNullOrBlank()) {
return "${book.name} 作者:${book.getRealAuthor()}"
@ -56,6 +56,7 @@ class CacheViewModel(application: Application) : BaseViewModel(application) {
}
}.onError {
finally(it.localizedMessage ?: "ERROR")
it.printStackTrace()
}.onSuccess {
finally(context.getString(R.string.success))
}
@ -185,6 +186,7 @@ class CacheViewModel(application: Application) : BaseViewModel(application) {
}
}.onError {
finally(it.localizedMessage ?: "ERROR")
it.printStackTrace()
}.onSuccess {
finally(context.getString(R.string.success))
}

@ -62,6 +62,11 @@ class ChangeSourceAdapter(
when (it) {
"name" -> tvOrigin.text = item.originName
"latest" -> tvLast.text = item.getDisplayLastChapterTitle()
"upCurSource" -> if (callBack.bookUrl == item.bookUrl) {
ivChecked.visible()
} else {
ivChecked.invisible()
}
}
}
}

@ -4,6 +4,7 @@ import android.os.Bundle
import android.view.*
import androidx.appcompat.widget.SearchView
import androidx.appcompat.widget.Toolbar
import androidx.core.os.bundleOf
import androidx.fragment.app.FragmentManager
import androidx.fragment.app.viewModels
import androidx.recyclerview.widget.LinearLayoutManager
@ -11,6 +12,7 @@ import androidx.recyclerview.widget.RecyclerView
import io.legado.app.R
import io.legado.app.base.BaseDialogFragment
import io.legado.app.constant.AppPattern
import io.legado.app.constant.EventBus
import io.legado.app.constant.PreferKey
import io.legado.app.data.appDb
import io.legado.app.data.entities.Book
@ -218,12 +220,12 @@ class ChangeSourceDialog : BaseDialogFragment(),
}
override fun deleteSource(searchBook: SearchBook) {
viewModel.del(searchBook)
if (bookUrl == searchBook.bookUrl) {
viewModel.firstSourceOrNull(searchBook)?.let {
changeSource(it)
}
}
viewModel.del(searchBook)
}
private fun changeSource(searchBook: SearchBook) {
@ -259,6 +261,16 @@ class ChangeSourceDialog : BaseDialogFragment(),
}
}
override fun observeLiveBus() {
observeEvent<String>(EventBus.SOURCE_CHANGED) {
adapter.notifyItemRangeChanged(
0,
adapter.itemCount,
bundleOf(Pair("upCurSource", bookUrl))
)
}
}
interface CallBack {
val oldBook: Book?
fun changeTo(book: Book)

@ -1,55 +0,0 @@
package io.legado.app.ui.book.group
import android.content.Context
import android.view.LayoutInflater
import io.legado.app.R
import io.legado.app.data.appDb
import io.legado.app.data.entities.BookGroup
import io.legado.app.databinding.DialogEditTextBinding
import io.legado.app.help.coroutine.Coroutine
import io.legado.app.lib.dialogs.alert
import io.legado.app.utils.requestInputMethod
object GroupEdit {
fun show(context: Context, layoutInflater: LayoutInflater, bookGroup: BookGroup) = context.run {
alert(title = getString(R.string.group_edit)) {
val alertBinding = DialogEditTextBinding.inflate(layoutInflater).apply {
textInputLayout.setHint(R.string.group_name)
editView.setText(bookGroup.groupName)
}
if (bookGroup.groupId >= 0) {
neutralButton(R.string.delete) {
deleteGroup(context, bookGroup)
}
}
customView { alertBinding.root }
yesButton {
alertBinding.editView.text?.toString()?.let {
bookGroup.groupName = it
Coroutine.async {
appDb.bookGroupDao.update(bookGroup)
}
}
}
noButton()
}.show().requestInputMethod()
}
private fun deleteGroup(context: Context, bookGroup: BookGroup) = context.run {
alert(R.string.delete, R.string.sure_del) {
okButton {
Coroutine.async {
appDb.bookGroupDao.delete(bookGroup)
val books = appDb.bookDao.getBooksByGroup(bookGroup.groupId)
books.forEach {
it.group = it.group - bookGroup.groupId
}
appDb.bookDao.update(*books.toTypedArray())
}
}
noButton()
}.show()
}
}

@ -0,0 +1,121 @@
package io.legado.app.ui.book.group
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.FragmentManager
import androidx.fragment.app.viewModels
import io.legado.app.R
import io.legado.app.base.BaseDialogFragment
import io.legado.app.data.entities.BookGroup
import io.legado.app.databinding.DialogBookGroupEditBinding
import io.legado.app.lib.dialogs.alert
import io.legado.app.lib.theme.primaryColor
import io.legado.app.utils.*
import io.legado.app.utils.viewbindingdelegate.viewBinding
import splitties.views.onClick
class GroupEditDialog : BaseDialogFragment() {
companion object {
fun start(fragmentManager: FragmentManager, bookGroup: BookGroup? = null) {
GroupEditDialog().apply {
arguments = Bundle().apply {
putParcelable("group", bookGroup)
}
}.show(fragmentManager, "bookGroupEdit")
}
}
private val binding by viewBinding(DialogBookGroupEditBinding::bind)
private val viewModel by viewModels<GroupViewModel>()
private var bookGroup: BookGroup? = null
val selectImage = registerForActivityResult(SelectImageContract()) {
it?.second?.let { uri ->
if (uri.isContentScheme()) {
binding.ivCover.load(uri.toString())
} else {
binding.ivCover.load(uri.path)
}
}
}
override fun onStart() {
super.onStart()
val dm = requireActivity().windowSize
dialog?.window?.setLayout(
(dm.widthPixels * 0.9).toInt(),
ViewGroup.LayoutParams.WRAP_CONTENT
)
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.dialog_book_group_edit, container)
}
override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) {
binding.toolBar.setBackgroundColor(primaryColor)
bookGroup = arguments?.getParcelable("group")
bookGroup?.let {
binding.tieGroupName.setText(it.groupName)
binding.ivCover.load(it.cover)
} ?: let {
binding.toolBar.title = getString(R.string.add_group)
binding.btnDelete.gone()
binding.ivCover.load()
}
binding.run {
ivCover.onClick {
selectImage.launch(null)
}
btnCancel.onClick {
dismiss()
}
btnOk.onClick {
val groupName = tieGroupName.text?.toString()
if (groupName.isNullOrEmpty()) {
toastOnUi("分组名称不能为空")
} else {
bookGroup?.let {
it.groupName = groupName
it.cover = binding.ivCover.path
viewModel.upGroup(it) {
dismiss()
}
} ?: let {
viewModel.addGroup(groupName, binding.ivCover.path) {
dismiss()
}
}
}
}
btnDelete.onClick {
deleteGroup {
bookGroup?.let {
viewModel.delGroup(it) {
dismiss()
}
}
}
}
}
}
private fun deleteGroup(ok: () -> Unit) {
alert(R.string.delete, R.string.sure_del) {
okButton {
ok.invoke()
}
noButton()
}.show()
}
}

@ -1,6 +1,5 @@
package io.legado.app.ui.book.group
import android.annotation.SuppressLint
import android.content.Context
import android.os.Bundle
import android.view.LayoutInflater
@ -18,17 +17,14 @@ import io.legado.app.base.adapter.ItemViewHolder
import io.legado.app.base.adapter.RecyclerAdapter
import io.legado.app.data.appDb
import io.legado.app.data.entities.BookGroup
import io.legado.app.databinding.DialogEditTextBinding
import io.legado.app.databinding.DialogRecyclerViewBinding
import io.legado.app.databinding.ItemBookGroupManageBinding
import io.legado.app.lib.dialogs.alert
import io.legado.app.lib.theme.accentColor
import io.legado.app.lib.theme.backgroundColor
import io.legado.app.lib.theme.primaryColor
import io.legado.app.ui.widget.recycler.ItemTouchCallback
import io.legado.app.ui.widget.recycler.VerticalDivider
import io.legado.app.utils.applyTint
import io.legado.app.utils.requestInputMethod
import io.legado.app.utils.viewbindingdelegate.viewBinding
import io.legado.app.utils.visible
import io.legado.app.utils.windowSize
@ -94,29 +90,11 @@ class GroupManageDialog : BaseDialogFragment(), Toolbar.OnMenuItemClickListener
override fun onMenuItemClick(item: MenuItem?): Boolean {
when (item?.itemId) {
R.id.menu_add -> addGroup()
R.id.menu_add -> GroupEditDialog.start(childFragmentManager)
}
return true
}
@SuppressLint("InflateParams")
private fun addGroup() {
alert(title = getString(R.string.add_group)) {
val alertBinding = DialogEditTextBinding.inflate(layoutInflater).apply {
textInputLayout.setHint(R.string.group_name)
}
customView { alertBinding.root }
yesButton {
alertBinding.editView.text?.toString()?.let {
if (it.isNotBlank()) {
viewModel.addGroup(it)
}
}
}
noButton()
}.show().requestInputMethod()
}
private inner class GroupAdapter(context: Context) :
RecyclerAdapter<BookGroup, ItemBookGroupManageBinding>(context),
ItemTouchCallback.Callback {
@ -144,7 +122,7 @@ class GroupManageDialog : BaseDialogFragment(), Toolbar.OnMenuItemClickListener
binding.run {
tvEdit.setOnClickListener {
getItem(holder.layoutPosition)?.let { bookGroup ->
GroupEdit.show(context, layoutInflater, bookGroup)
GroupEditDialog.start(childFragmentManager, bookGroup)
}
}
swShow.setOnCheckedChangeListener { buttonView, isChecked ->

@ -1,6 +1,5 @@
package io.legado.app.ui.book.group
import android.annotation.SuppressLint
import android.content.Context
import android.os.Bundle
import android.view.LayoutInflater
@ -20,16 +19,13 @@ import io.legado.app.base.adapter.RecyclerAdapter
import io.legado.app.data.appDb
import io.legado.app.data.entities.BookGroup
import io.legado.app.databinding.DialogBookGroupPickerBinding
import io.legado.app.databinding.DialogEditTextBinding
import io.legado.app.databinding.ItemGroupSelectBinding
import io.legado.app.lib.dialogs.alert
import io.legado.app.lib.theme.accentColor
import io.legado.app.lib.theme.backgroundColor
import io.legado.app.lib.theme.primaryColor
import io.legado.app.ui.widget.recycler.ItemTouchCallback
import io.legado.app.ui.widget.recycler.VerticalDivider
import io.legado.app.utils.applyTint
import io.legado.app.utils.requestInputMethod
import io.legado.app.utils.viewbindingdelegate.viewBinding
import io.legado.app.utils.windowSize
import kotlinx.coroutines.flow.collect
@ -116,46 +112,11 @@ class GroupSelectDialog : BaseDialogFragment(), Toolbar.OnMenuItemClickListener
override fun onMenuItemClick(item: MenuItem?): Boolean {
when (item?.itemId) {
R.id.menu_add -> addGroup()
R.id.menu_add -> GroupEditDialog.start(childFragmentManager)
}
return true
}
@SuppressLint("InflateParams")
private fun addGroup() {
alert(title = getString(R.string.add_group)) {
val alertBinding = DialogEditTextBinding.inflate(layoutInflater).apply {
textInputLayout.setHint(R.string.group_name)
}
customView { alertBinding.root }
yesButton {
alertBinding.editView.text?.toString()?.let {
if (it.isNotBlank()) {
viewModel.addGroup(it)
}
}
}
noButton()
}.show().requestInputMethod()
}
@SuppressLint("InflateParams")
private fun editGroup(bookGroup: BookGroup) {
alert(title = getString(R.string.group_edit)) {
val alertBinding = DialogEditTextBinding.inflate(layoutInflater).apply {
textInputLayout.setHint(R.string.group_name)
editView.setText(bookGroup.groupName)
}
customView { alertBinding.root }
yesButton {
alertBinding.editView.text?.toString()?.let {
viewModel.upGroup(bookGroup.copy(groupName = it))
}
}
noButton()
}.show().requestInputMethod()
}
private inner class GroupAdapter(context: Context) :
RecyclerAdapter<BookGroup, ItemGroupSelectBinding>(context),
ItemTouchCallback.Callback {
@ -192,7 +153,9 @@ class GroupSelectDialog : BaseDialogFragment(), Toolbar.OnMenuItemClickListener
}
}
}
tvEdit.setOnClickListener { getItem(holder.layoutPosition)?.let { editGroup(it) } }
tvEdit.setOnClickListener {
GroupEditDialog.start(childFragmentManager, getItem(holder.layoutPosition))
}
}
}

@ -7,7 +7,15 @@ import io.legado.app.data.entities.BookGroup
class GroupViewModel(application: Application) : BaseViewModel(application) {
fun addGroup(groupName: String) {
fun upGroup(vararg bookGroup: BookGroup, finally: (() -> Unit)? = null) {
execute {
appDb.bookGroupDao.update(*bookGroup)
}.onFinally {
finally?.invoke()
}
}
fun addGroup(groupName: String, cover: String?, finally: () -> Unit) {
execute {
var id = 1L
val idsSum = appDb.bookGroupDao.idsSum
@ -17,19 +25,16 @@ class GroupViewModel(application: Application) : BaseViewModel(application) {
val bookGroup = BookGroup(
groupId = id,
groupName = groupName,
cover = cover,
order = appDb.bookGroupDao.maxOrder.plus(1)
)
appDb.bookGroupDao.insert(bookGroup)
}.onFinally {
finally()
}
}
fun upGroup(vararg bookGroup: BookGroup) {
execute {
appDb.bookGroupDao.update(*bookGroup)
}
}
fun delGroup(vararg bookGroup: BookGroup) {
fun delGroup(vararg bookGroup: BookGroup, finally: () -> Unit) {
execute {
appDb.bookGroupDao.delete(*bookGroup)
bookGroup.forEach { group ->
@ -39,6 +44,8 @@ class GroupViewModel(application: Application) : BaseViewModel(application) {
}
appDb.bookDao.update(*books.toTypedArray())
}
}.onFinally {
finally()
}
}

@ -15,6 +15,7 @@ import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions
import com.bumptech.glide.request.RequestOptions.bitmapTransform
import io.legado.app.R
import io.legado.app.base.VMBaseActivity
import io.legado.app.constant.AppConst
import io.legado.app.constant.BookType
import io.legado.app.constant.Theme
import io.legado.app.data.appDb
@ -36,6 +37,7 @@ import io.legado.app.ui.book.read.ReadBookActivity
import io.legado.app.ui.book.search.SearchActivity
import io.legado.app.ui.book.source.edit.BookSourceEditActivity
import io.legado.app.ui.book.toc.TocActivityResult
import io.legado.app.ui.login.SourceLoginActivity
import io.legado.app.ui.widget.image.CoverImageView
import io.legado.app.utils.*
import io.legado.app.utils.viewbindingdelegate.viewBinding
@ -151,6 +153,14 @@ class BookInfoActivity :
viewModel.loadBookInfo(it, false)
}
}
R.id.menu_login -> viewModel.bookSource?.let {
startActivity<SourceLoginActivity> {
putExtra("sourceUrl", it.bookSourceUrl)
putExtra("loginUrl", it.loginUrl)
putExtra("userAgent", it.getHeaderMap()[AppConst.UA_NAME])
}
}
R.id.menu_top -> viewModel.topBook()
R.id.menu_copy_book_url -> viewModel.bookData.value?.bookUrl?.let {
sendToClip(it)
} ?: toastOnUi(R.string.no_book)

@ -5,6 +5,7 @@ import android.content.Intent
import androidx.lifecycle.MutableLiveData
import io.legado.app.R
import io.legado.app.base.BaseViewModel
import io.legado.app.constant.EventBus
import io.legado.app.data.appDb
import io.legado.app.data.entities.Book
import io.legado.app.data.entities.BookChapter
@ -13,6 +14,7 @@ import io.legado.app.help.BookHelp
import io.legado.app.model.localBook.LocalBook
import io.legado.app.model.webBook.WebBook
import io.legado.app.service.help.ReadBook
import io.legado.app.utils.postEvent
import io.legado.app.utils.toastOnUi
import kotlinx.coroutines.Dispatchers.IO
@ -167,6 +169,8 @@ class BookInfoViewModel(application: Application) : BaseViewModel(application) {
upChangeDurChapterIndex(newBook, oldTocSize, it)
}
}
}.onFinally {
postEvent(EventBus.SOURCE_CHANGED, newBook.bookUrl)
}
}
@ -192,6 +196,16 @@ class BookInfoViewModel(application: Application) : BaseViewModel(application) {
}
}
fun topBook() {
execute {
bookData.value?.let { book ->
val minOrder = appDb.bookDao.minOrder
book.order = minOrder - 1
appDb.bookDao.update(book)
}
}
}
fun saveBook(success: (() -> Unit)? = null) {
execute {
bookData.value?.let { book ->

@ -5,7 +5,6 @@ import android.net.Uri
import android.os.Bundle
import android.view.Menu
import android.view.MenuItem
import androidx.activity.result.contract.ActivityResultContracts
import androidx.activity.viewModels
import androidx.documentfile.provider.DocumentFile
import io.legado.app.R
@ -23,12 +22,11 @@ class BookInfoEditActivity :
VMBaseActivity<ActivityBookInfoEditBinding, BookInfoEditViewModel>(),
ChangeCoverDialog.CallBack {
private val selectCoverResult =
registerForActivityResult(ActivityResultContracts.GetContent()) {
it?.let { uri ->
coverChangeTo(uri)
}
private val selectCover = registerForActivityResult(SelectImageContract()) {
it?.second?.let { uri ->
coverChangeTo(uri)
}
}
override val binding by viewBinding(ActivityBookInfoEditBinding::inflate)
override val viewModel by viewModels<BookInfoEditViewModel>()
@ -62,7 +60,7 @@ class BookInfoEditActivity :
}
}
tvSelectCover.setOnClickListener {
selectCoverResult.launch("image/*")
selectCover.launch(null)
}
tvRefreshCover.setOnClickListener {
viewModel.book?.customCoverUrl = tieCoverUrl.text?.toString()

@ -686,6 +686,8 @@ class ReadBookActivity : ReadBookBaseActivity(),
binding.readView.upContent(1)
autoPagePlus()
binding.readMenu.setAutoPage(true)
screenTimeOut = -1L
screenOffTimerStart()
}
}
@ -694,14 +696,15 @@ class ReadBookActivity : ReadBookBaseActivity(),
mHandler.removeCallbacks(autoPageRunnable)
binding.readView.upContent()
binding.readMenu.setAutoPage(false)
upScreenTimeOut()
}
private fun autoPagePlus() {
var delayMillis = ReadBookConfig.autoReadSpeed * 1000L / binding.readView.height
var scrollOffset = 1
if (delayMillis < 20) {
var delayInt=delayMillis.toInt()
if(delayInt==0)delayInt =1
var delayInt = delayMillis.toInt()
if (delayInt == 0) delayInt = 1
scrollOffset = 20 / delayInt
delayMillis = 20
}
@ -970,9 +973,8 @@ class ReadBookActivity : ReadBookBaseActivity(),
}
private fun upScreenTimeOut() {
getPrefString(PreferKey.keepLight)?.let {
screenTimeOut = it.toLong() * 1000
}
val keepLightPrefer = getPrefString(PreferKey.keepLight)?.toInt() ?: 0
screenTimeOut = keepLightPrefer * 1000L
screenOffTimerStart()
}

@ -5,6 +5,7 @@ import android.content.Intent
import androidx.lifecycle.viewModelScope
import io.legado.app.R
import io.legado.app.base.BaseViewModel
import io.legado.app.constant.EventBus
import io.legado.app.data.appDb
import io.legado.app.data.entities.Book
import io.legado.app.data.entities.BookChapter
@ -20,6 +21,7 @@ import io.legado.app.service.BaseReadAloudService
import io.legado.app.service.help.ReadAloud
import io.legado.app.service.help.ReadBook
import io.legado.app.utils.msg
import io.legado.app.utils.postEvent
import io.legado.app.utils.toastOnUi
import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.Dispatchers.Main
@ -207,6 +209,8 @@ class ReadBookViewModel(application: Application) : BaseViewModel(application) {
upChangeDurChapterIndex(newBook, oldTocSize, it)
}
}
}.onFinally {
postEvent(EventBus.SOURCE_CHANGED, newBook.bookUrl)
}
}

@ -6,7 +6,6 @@ import android.graphics.Color
import android.net.Uri
import android.os.Bundle
import android.view.*
import androidx.activity.result.contract.ActivityResultContracts
import androidx.documentfile.provider.DocumentFile
import com.jaredrummler.android.colorpicker.ColorPickerDialog
import io.legado.app.R
@ -46,9 +45,10 @@ class BgTextConfigDialog : BaseDialogFragment() {
private var primaryTextColor = 0
private var secondaryTextColor = 0
private val importFormNet = "网络导入"
private val selectBgImage = registerForActivityResult(ActivityResultContracts.GetContent()) {
it ?: return@registerForActivityResult
setBgFromUri(it)
private val selectBgImage = registerForActivityResult(SelectImageContract()) {
it?.second?.let { uri ->
setBgFromUri(uri)
}
}
private val selectExportDir = registerForActivityResult(FilePicker()) {
it ?: return@registerForActivityResult
@ -122,7 +122,7 @@ class BgTextConfigDialog : BaseDialogFragment() {
ivBg.setImageResource(R.drawable.ic_image)
ivBg.setColorFilter(primaryTextColor)
root.setOnClickListener {
selectBgImage.launch("image/*")
selectBgImage.launch(null)
}
}
}

@ -250,7 +250,7 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at
selectStart[2] != charIndex
) {
if (selectToInt(relativePos, lineIndex, charIndex)
< selectToInt(selectEnd)
<= selectToInt(selectEnd)
) {
selectStart[0] = relativePos
selectStart[1] = lineIndex
@ -276,7 +276,7 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at
|| selectEnd[2] != charIndex
) {
if (selectToInt(relativePos, lineIndex, charIndex)
> selectToInt(selectStart)
>= selectToInt(selectStart)
) {
selectEnd[0] = relativePos
selectEnd[1] = lineIndex

@ -85,7 +85,9 @@ class PageView(context: Context) : FrameLayout(context) {
vwTopDivider.visible(it.showHeaderLine)
vwBottomDivider.visible(it.showFooterLine)
pageNvBar.layoutParams = pageNvBar.layoutParams.apply {
height = if (it.hideNavigationBar) 0 else App.navigationBarHeight
height =
if (it.hideNavigationBar) 0
else App.navigationBarHeight
}
}
contentTextView.upVisibleRect()

@ -43,9 +43,9 @@ class ReadView(context: Context, attrs: AttributeSet) :
upContent()
}
var isScroll = false
var prevPage: PageView = PageView(context)
var curPage: PageView = PageView(context)
var nextPage: PageView = PageView(context)
val prevPage by lazy { PageView(context) }
val curPage by lazy { PageView(context) }
val nextPage by lazy { PageView(context) }
val defaultAnimationSpeed = 300
private var pressDown = false
private var isMove = false

@ -343,8 +343,10 @@ object ChapterProvider {
}
x = x1
}
val words1 = words.copyOfRange(bodyIndent.length, words.size)
addCharsToLineMiddle(textLine, words1, textPaint, desiredWidth, x, srcList)
if (words.size > bodyIndent.length) {
val words1 = words.copyOfRange(bodyIndent.length, words.size)
addCharsToLineMiddle(textLine, words1, textPaint, desiredWidth, x, srcList)
}
}
/**

@ -42,7 +42,7 @@ class ThemeConfigFragment : BasePreferenceFragment(),
private val requestCodeCoverDark = 112
private val requestCodeBgLight = 121
private val requestCodeBgDark = 122
private val selectImage = registerForActivityResult(ActivityResultContractUtils.SelectImage()) {
private val selectImage = registerForActivityResult(SelectImageContract()) {
val uri = it?.second ?: return@registerForActivityResult
when (it.first) {
requestCodeCover -> setCoverFromUri(PreferKey.defaultCover, uri)
@ -164,37 +164,9 @@ class ThemeConfigFragment : BasePreferenceFragment(),
recreateActivities()
}
"themeList" -> ThemeListDialog().show(childFragmentManager, "themeList")
"saveDayTheme", "saveNightTheme" -> saveThemeAlert(key)
PreferKey.bgImage -> if (getPrefString(PreferKey.bgImage).isNullOrEmpty()) {
selectImage.launch(requestCodeBgLight)
} else {
selector(items = arrayListOf("删除图片", "选择图片")) { _, i ->
if (i == 0) {
removePref(PreferKey.bgImage)
upTheme(false)
} else {
selectImage.launch(requestCodeBgLight)
}
}
}
PreferKey.bgImageN -> if (getPrefString(PreferKey.bgImageN).isNullOrEmpty()) {
selectImage.launch(requestCodeBgDark)
} else {
selector(items = arrayListOf("删除图片", "选择图片")) { _, i ->
if (i == 0) {
removePref(PreferKey.bgImageN)
upTheme(true)
} else {
selectImage.launch(requestCodeBgDark)
}
}
}
PreferKey.bgImageBlurring -> alertImageBlurring(PreferKey.bgImageBlurring) {
upTheme(false)
}
PreferKey.bgImageNBlurring -> alertImageBlurring(PreferKey.bgImageNBlurring) {
upTheme(true)
}
"saveDayTheme", "saveNightTheme" -> alertSaveTheme(key)
PreferKey.bgImage -> selectBgAction(false)
PreferKey.bgImageN -> selectBgAction(true)
PreferKey.defaultCover -> if (getPrefString(PreferKey.defaultCover).isNullOrEmpty()) {
selectImage.launch(requestCodeCover)
} else {
@ -223,7 +195,7 @@ class ThemeConfigFragment : BasePreferenceFragment(),
}
@SuppressLint("InflateParams")
private fun saveThemeAlert(key: String) {
private fun alertSaveTheme(key: String) {
alert(R.string.theme_name) {
val alertBinding = DialogEditTextBinding.inflate(layoutInflater).apply {
textInputLayout.hint = "name"
@ -245,6 +217,36 @@ class ThemeConfigFragment : BasePreferenceFragment(),
}.show()
}
private fun selectBgAction(isNight: Boolean) {
val bgKey = if (isNight) PreferKey.bgImageN else PreferKey.bgImage
val blurringKey = if (isNight) PreferKey.bgImageNBlurring else PreferKey.bgImageBlurring
val actions = arrayListOf(
getString(R.string.background_image_blurring),
getString(R.string.select_image)
)
if (!getPrefString(bgKey).isNullOrEmpty()) {
actions.add(getString(R.string.delete))
}
selector(items = actions) { _, i ->
when (i) {
0 -> alertImageBlurring(blurringKey) {
upTheme(isNight)
}
1 -> {
if (isNight) {
selectImage.launch(requestCodeBgDark)
} else {
selectImage.launch(requestCodeBgLight)
}
}
2 -> {
removePref(bgKey)
upTheme(isNight)
}
}
}
}
private fun alertImageBlurring(preferKey: String, success: () -> Unit) {
alert(R.string.background_image_blurring) {
val alertBinding = DialogImageBlurringBinding.inflate(layoutInflater).apply {

@ -63,6 +63,7 @@ class DictDialog : BaseDialogFragment() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
binding.tvDict.text = Html.fromHtml(it, Html.FROM_HTML_MODE_LEGACY)
} else {
@Suppress("DEPRECATION")
binding.tvDict.text = Html.fromHtml(it)
}
}

@ -8,12 +8,27 @@ import io.legado.app.help.http.newCallStrResponse
import io.legado.app.help.http.okHttpClient
import io.legado.app.utils.toastOnUi
import org.jsoup.Jsoup
import java.util.regex.Pattern
class DictViewModel(application: Application) : BaseViewModel(application) {
var dictHtmlData: MutableLiveData<String> = MutableLiveData()
fun dict(word: String) {
if(isChinese(word)){
baiduDict(word)
}else{
haiciDict(word)
}
}
/**
* 海词英文词典
*
* @param word
*/
private fun haiciDict(word: String) {
execute {
val body = okHttpClient.newCallStrResponse {
get("https://apii.dict.cn/mini.php", mapOf(Pair("q", word)))
@ -25,7 +40,44 @@ class DictViewModel(application: Application) : BaseViewModel(application) {
}.onError {
context.toastOnUi(it.localizedMessage)
}
}
/**
* 百度汉语词典
*
* @param word
*/
private fun baiduDict(word: String) {
execute {
val body = okHttpClient.newCallStrResponse {
get("https://dict.baidu.com/s", mapOf(Pair("wd", word)))
}.body
val jsoup = Jsoup.parse(body!!)
jsoup.select("script").remove()//移除script
jsoup.select("#word-header").remove()//移除单字的header
jsoup.select("#term-header").remove()//移除词语的header
jsoup.select(".more-button").remove()//移除展示更多
jsoup.select(".disactive").remove()
jsoup.select("#download-wrapper").remove()//移除下载广告
jsoup.select("#right-panel").remove()//移除右侧广告
jsoup.select("#content-panel")
}.onSuccess {
dictHtmlData.postValue(it.html())
}.onError {
context.toastOnUi(it.localizedMessage)
}
}
/**
* 判断是否包含汉字
* @param str
* @return
*/
fun isChinese(str: String): Boolean {
val p = Pattern.compile("[\u4e00-\u9fa5]")
val m = p.matcher(str)
return m.find()
}
}

@ -56,6 +56,7 @@ class BooksAdapterGrid(context: Context, callBack: CallBack) :
binding.run {
val item = callBack.getItem(position) as BookGroup
tvName.text = item.groupName
ivCover.load(item.cover)
}
}
@ -88,6 +89,7 @@ class BooksAdapterGrid(context: Context, callBack: CallBack) :
val item = callBack.getItem(position)
if (item is BookGroup) {
tvName.text = item.groupName
ivCover.load(item.cover)
}
root.setOnClickListener {
callBack.onItemClick(position)

@ -55,6 +55,7 @@ class BooksAdapterList(context: Context, callBack: CallBack) :
binding.run {
val item = callBack.getItem(position) as BookGroup
tvName.text = item.groupName
ivCover.load(item.cover)
}
}
@ -90,6 +91,7 @@ class BooksAdapterList(context: Context, callBack: CallBack) :
val item = callBack.getItem(position)
if (item is BookGroup) {
tvName.text = item.groupName
ivCover.load(item.cover)
flHasNew.gone()
ivAuthor.gone()
ivLast.gone()

@ -22,7 +22,7 @@ import io.legado.app.help.AppConfig
import io.legado.app.lib.theme.ATH
import io.legado.app.lib.theme.accentColor
import io.legado.app.ui.book.audio.AudioPlayActivity
import io.legado.app.ui.book.group.GroupEdit
import io.legado.app.ui.book.group.GroupEditDialog
import io.legado.app.ui.book.info.BookInfoActivity
import io.legado.app.ui.book.read.ReadBookActivity
import io.legado.app.ui.book.search.SearchActivity
@ -120,7 +120,6 @@ class BookshelfFragment2 : BaseBookshelfFragment(R.layout.fragment_bookshelf1),
AppConst.bookGroupNoneId -> appDb.bookDao.flowNoGroup()
else -> appDb.bookDao.flowByGroup(groupId)
}.collect { list ->
binding.tvEmptyMsg.isGone = list.isNotEmpty()
books = when (getPrefInt(PreferKey.bookshelfSort)) {
1 -> list.sortedByDescending {
it.latestChapterTime
@ -136,6 +135,7 @@ class BookshelfFragment2 : BaseBookshelfFragment(R.layout.fragment_bookshelf1),
}
}
booksAdapter.notifyDataSetChanged()
binding.tvEmptyMsg.isGone = getItemCount() > 0
}
}
}
@ -190,7 +190,7 @@ class BookshelfFragment2 : BaseBookshelfFragment(R.layout.fragment_bookshelf1),
putExtra("name", item.name)
putExtra("author", item.author)
}
is BookGroup -> GroupEdit.show(requireContext(), layoutInflater, item)
is BookGroup -> GroupEditDialog.start(childFragmentManager, item)
}
}

@ -30,6 +30,8 @@ class CoverImageView @JvmOverloads constructor(
) {
internal var width: Float = 0.toFloat()
internal var height: Float = 0.toFloat()
var path: String? = null
private set
private var nameHeight = 0f
private var authorHeight = 0f
private val namePaint by lazy {
@ -131,7 +133,8 @@ class CoverImageView @JvmOverloads constructor(
}
}
fun load(path: String?, name: String?, author: String?) {
fun load(path: String? = null, name: String? = null, author: String? = null) {
this.path = path
setText(name, author)
if (AppConfig.useDefaultCover) {
ImageLoader.load(context, defaultDrawable)

@ -3,6 +3,8 @@ package io.legado.app.utils
import android.app.Activity
import android.os.Build
import android.util.DisplayMetrics
import android.view.View
import android.view.ViewGroup
import android.view.WindowInsets
import android.view.WindowMetrics
@ -22,13 +24,39 @@ val Activity.windowSize: DisplayMetrics
return displayMetrics
}
/**
* 返回NavigationBar是否存在
* 该方法需要在View完全被绘制出来之后调用否则判断不了
* 在比如 onWindowFocusChanged方法中可以得到正确的结果
*/
val Activity.isNavigationBarExist: Boolean
get() {
val viewGroup = window.decorView as? ViewGroup
if (viewGroup != null) {
for (i in 0 until viewGroup.childCount) {
viewGroup.getChildAt(i).context.packageName
if (viewGroup.getChildAt(i).id != View.NO_ID
&& "navigationBarBackground" == resources.getResourceEntryName(
viewGroup.getChildAt(
i
).id
)
) {
return true
}
}
}
return false
}
/**
* 该方法需要在View完全被绘制出来之后调用否则判断不了
* 在比如 onWindowFocusChanged方法中可以得到正确的结果
*/
val Activity.navigationBarHeight: Int
get() {
if (SystemUtils.isNavigationBarExist(this)) {
if (isNavigationBarExist) {
val resourceId = resources.getIdentifier("navigation_bar_height", "dimen", "android")
return resources.getDimensionPixelSize(resourceId)
}

@ -1,32 +0,0 @@
package io.legado.app.utils
import android.app.Activity.RESULT_OK
import android.content.Context
import android.content.Intent
import android.net.Uri
import androidx.activity.result.contract.ActivityResultContract
object ActivityResultContractUtils {
class SelectImage : ActivityResultContract<Int, Pair<Int?, Uri?>?>() {
var requestCode: Int? = null
override fun createIntent(context: Context, input: Int?): Intent {
requestCode = input
return Intent(Intent.ACTION_GET_CONTENT)
.addCategory(Intent.CATEGORY_OPENABLE)
.setType("image/*")
}
override fun parseResult(resultCode: Int, intent: Intent?): Pair<Int?, Uri?>? {
if (resultCode == RESULT_OK) {
return Pair(requestCode, intent?.data)
}
return null
}
}
}

@ -0,0 +1,27 @@
package io.legado.app.utils
import android.app.Activity.RESULT_OK
import android.content.Context
import android.content.Intent
import android.net.Uri
import androidx.activity.result.contract.ActivityResultContract
class SelectImageContract : ActivityResultContract<Int, Pair<Int?, Uri?>?>() {
var requestCode: Int? = null
override fun createIntent(context: Context, input: Int?): Intent {
requestCode = input
return Intent(Intent.ACTION_OPEN_DOCUMENT)
.addCategory(Intent.CATEGORY_OPENABLE)
.setType("image/*")
}
override fun parseResult(resultCode: Int, intent: Intent?): Pair<Int?, Uri?>? {
if (resultCode == RESULT_OK) {
return Pair(requestCode, intent?.data)
}
return null
}
}

@ -8,15 +8,11 @@ import android.content.Intent
import android.net.Uri
import android.os.PowerManager
import android.provider.Settings
import android.view.View
import android.view.ViewGroup
@Suppress("unused")
object SystemUtils {
private const val NAVIGATION = "navigationBarBackground"
fun getScreenOffTime(context: Context): Int {
var screenOffTime = 0
kotlin.runCatching {
@ -49,29 +45,4 @@ object SystemUtils {
}
}
/**
* 返回NavigationBar是否存在
* 该方法需要在View完全被绘制出来之后调用否则判断不了
* 在比如 onWindowFocusChanged方法中可以得到正确的结果
*/
fun isNavigationBarExist(activity: Activity?): Boolean {
activity?.let {
val viewGroup = it.window.decorView as? ViewGroup
if (viewGroup != null) {
for (i in 0 until viewGroup.childCount) {
viewGroup.getChildAt(i).context.packageName
if (viewGroup.getChildAt(i).id != View.NO_ID
&& NAVIGATION == activity.resources.getResourceEntryName(
viewGroup.getChildAt(
i
).id
)
) {
return true
}
}
}
}
return false
}
}

@ -0,0 +1,90 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/background_menu">
<androidx.appcompat.widget.Toolbar
android:id="@+id/tool_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/background_menu"
android:elevation="5dp"
android:theme="?attr/actionBarStyle"
app:displayHomeAsUp="false"
app:fitStatusBar="false"
app:layout_constraintTop_toTopOf="parent"
app:popupTheme="@style/AppTheme.PopupOverlay"
app:title="@string/group_edit"
app:titleTextAppearance="@style/ToolbarTitle" />
<io.legado.app.ui.widget.image.CoverImageView
android:id="@+id/iv_cover"
android:layout_width="90dp"
android:layout_height="126dp"
android:layout_margin="6dp"
android:contentDescription="@string/img_cover"
android:scaleType="centerCrop"
android:src="@drawable/image_cover_default"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toBottomOf="@+id/tool_bar"
tools:ignore="ImageContrastCheck" />
<io.legado.app.ui.widget.text.TextInputLayout
android:id="@+id/til_group_name"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_margin="6dp"
android:hint="@string/group_name"
app:layout_constraintLeft_toRightOf="@+id/iv_cover"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/tool_bar"
app:layout_constraintBottom_toTopOf="@+id/btn_ok">
<io.legado.app.ui.widget.text.EditText
android:id="@+id/tie_group_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:singleLine="true"
tools:ignore="TouchTargetSizeCheck,SpeakableTextPresentCheck" />
</io.legado.app.ui.widget.text.TextInputLayout>
<io.legado.app.ui.widget.text.AccentTextView
android:id="@+id/btn_delete"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="12dp"
android:layout_marginLeft="12dp"
android:text="@string/delete"
app:layout_constraintBottom_toBottomOf="@id/btn_ok"
app:layout_constraintLeft_toRightOf="@+id/iv_cover"
app:layout_constraintTop_toTopOf="@+id/btn_ok"
tools:ignore="RtlHardcoded" />
<io.legado.app.ui.widget.text.AccentTextView
android:id="@+id/btn_ok"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="12dp"
android:layout_marginRight="12dp"
android:text="@string/ok"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/til_group_name"
tools:ignore="RtlHardcoded" />
<io.legado.app.ui.widget.text.AccentTextView
android:id="@+id/btn_cancel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="12dp"
android:text="@string/cancel"
app:layout_constraintBottom_toBottomOf="@id/btn_ok"
app:layout_constraintRight_toLeftOf="@+id/btn_ok"
app:layout_constraintTop_toTopOf="@+id/btn_ok" />
</androidx.constraintlayout.widget.ConstraintLayout>

@ -0,0 +1,72 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.core.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:paddingLeft="20dp"
android:paddingRight="20dp"
android:paddingTop="12dp"
android:overScrollMode="ifContentScrolls">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="6dp"
android:paddingBottom="6dp">
<TextView
android:id="@+id/tv_add_group_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@string/add_group"
android:textColor="@color/primaryText"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toLeftOf="@+id/sw_add_group" />
<TextView
android:id="@+id/tv_add_group_s"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@string/custom_group_summary"
android:textColor="@color/secondaryText"
app:layout_constraintTop_toBottomOf="@+id/tv_add_group_title"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toLeftOf="@+id/sw_add_group" />
<io.legado.app.lib.theme.view.ATESwitch
android:id="@+id/sw_add_group"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textOff="@string/replace"
android:textOn="@string/add"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:ignore="SpeakableTextPresentCheck" />
</androidx.constraintlayout.widget.ConstraintLayout>
<io.legado.app.ui.widget.text.TextInputLayout
android:id="@+id/text_input_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="6dp">
<io.legado.app.ui.widget.text.AutoCompleteTextView
android:id="@+id/edit_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:ignore="SpeakableTextPresentCheck,TouchTargetSizeCheck" />
</io.legado.app.ui.widget.text.TextInputLayout>
</LinearLayout>
</androidx.core.widget.NestedScrollView>

@ -32,7 +32,8 @@
android:src="@drawable/image_cover_default"
android:transitionName="img_cover"
app:layout_constraintTop_toTopOf="parent"
tools:ignore="UnusedAttribute" />
tools:ignore="UnusedAttribute,ImageContrastCheck"
tools:layout_editor_absoluteX="8dp" />
<io.legado.app.ui.widget.text.BadgeView
android:id="@+id/bv_unread"

@ -32,7 +32,8 @@
android:src="@drawable/image_cover_default"
android:transitionName="img_cover"
app:layout_constraintTop_toTopOf="parent"
tools:ignore="UnusedAttribute" />
tools:ignore="UnusedAttribute,ImageContrastCheck"
tools:layout_editor_absoluteX="8dp" />
<io.legado.app.ui.widget.text.BadgeView
android:id="@+id/bv_unread"

@ -99,7 +99,7 @@
app:layout_constraintLeft_toRightOf="@+id/iv_author"
app:layout_constraintRight_toRightOf="@id/fl_has_new"
app:layout_constraintTop_toBottomOf="@+id/tv_name"
tools:ignore="RtlSymmetry" />
tools:ignore="RtlSymmetry,TextContrastCheck" />
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/iv_read"
@ -127,7 +127,8 @@
app:layout_constraintBottom_toTopOf="@id/tv_last"
app:layout_constraintLeft_toRightOf="@+id/iv_read"
app:layout_constraintRight_toRightOf="@+id/fl_has_new"
app:layout_constraintTop_toBottomOf="@+id/tv_author" />
app:layout_constraintTop_toBottomOf="@+id/tv_author"
tools:ignore="TextContrastCheck" />
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/iv_last"
@ -155,7 +156,8 @@
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toRightOf="@+id/iv_last"
app:layout_constraintRight_toRightOf="@id/fl_has_new"
app:layout_constraintTop_toBottomOf="@+id/tv_read" />
app:layout_constraintTop_toBottomOf="@+id/tv_read"
tools:ignore="TextContrastCheck" />
<View
android:id="@+id/vw_foreground"

@ -99,7 +99,7 @@
app:layout_constraintLeft_toRightOf="@+id/iv_author"
app:layout_constraintRight_toRightOf="@id/fl_has_new"
app:layout_constraintTop_toBottomOf="@+id/tv_name"
tools:ignore="RtlSymmetry" />
tools:ignore="RtlSymmetry,TextContrastCheck" />
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/iv_read"
@ -127,7 +127,8 @@
app:layout_constraintBottom_toTopOf="@id/tv_last"
app:layout_constraintLeft_toRightOf="@+id/iv_read"
app:layout_constraintRight_toRightOf="@+id/fl_has_new"
app:layout_constraintTop_toBottomOf="@+id/tv_author" />
app:layout_constraintTop_toBottomOf="@+id/tv_author"
tools:ignore="TextContrastCheck" />
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/iv_last"
@ -155,7 +156,8 @@
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toRightOf="@+id/iv_last"
app:layout_constraintRight_toRightOf="@id/fl_has_new"
app:layout_constraintTop_toBottomOf="@+id/tv_read" />
app:layout_constraintTop_toBottomOf="@+id/tv_read"
tools:ignore="TextContrastCheck" />
<View
android:id="@+id/vw_foreground"

@ -3,6 +3,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:padding="16dp">
<TextView
@ -10,6 +11,8 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:singleLine="true"
android:textColor="@color/primaryText"
tools:text="@string/name"
app:layout_constraintRight_toLeftOf="@+id/iv_download"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintLeft_toLeftOf="parent" />
@ -19,6 +22,8 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:singleLine="true"
android:textColor="@color/primaryText"
tools:text="@string/author"
app:layout_constraintRight_toLeftOf="@+id/iv_download"
app:layout_constraintTop_toBottomOf="@+id/tv_name"
app:layout_constraintLeft_toLeftOf="parent" />
@ -28,6 +33,8 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:singleLine="true"
android:textColor="@color/secondaryText"
tools:text="@string/action_download"
app:layout_constraintRight_toLeftOf="@+id/iv_download"
app:layout_constraintTop_toBottomOf="@id/tv_author"
app:layout_constraintLeft_toLeftOf="parent" />
@ -52,6 +59,7 @@
android:background="?attr/selectableItemBackground"
android:padding="10dp"
android:text="@string/export"
android:textColor="@color/primaryText"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintRight_toRightOf="parent" />

@ -24,6 +24,11 @@
android:title="@string/login"
app:showAsAction="never" />
<item
android:id="@+id/menu_top"
android:title="@string/to_top"
app:showAsAction="never" />
<item
android:id="@+id/menu_copy_book_url"
android:title="@string/copy_book_url"

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:tools="http://schemas.android.com/tools"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/menu_new_group"
android:title="@string/diy_source_group"
app:showAsAction="always"
tools:ignore="AlwaysShowAction" />
<item
android:id="@+id/menu_Keep_original_name"
android:title="@string/keep_original_name"
android:checkable="true"
app:showAsAction="never" />
</menu>

@ -848,4 +848,5 @@
<string name="dict">字典</string>
<string name="unknown_error">未知错误</string>
<string name="end">end</string>
<string name="custom_group_summary">关闭替换分组/开启添加分组</string>
</resources>

@ -848,4 +848,5 @@
<string name="dict">字典</string>
<string name="unknown_error">未知错误</string>
<string name="end">end</string>
<string name="custom_group_summary">关闭替换分组/开启添加分组</string>
</resources>

@ -848,4 +848,5 @@
<string name="dict">Dicionários</string>
<string name="unknown_error">未知错误</string>
<string name="end">end</string>
<string name="custom_group_summary">关闭替换分组/开启添加分组</string>
</resources>

@ -849,5 +849,6 @@
<string name="dict">字典</string>
<string name="unknown_error">未知錯誤</string>
<string name="end">end</string>
<string name="custom_group_summary">关闭替换分组/开启添加分组</string>
</resources>

@ -850,5 +850,6 @@
<string name="dict">字典</string>
<string name="unknown_error">未知錯誤</string>
<string name="end">end</string>
<string name="custom_group_summary">关闭替换分组/开启添加分组</string>
</resources>

@ -851,5 +851,6 @@
<string name="unknown_error">未知错误</string>
<string name="autobackup_fail">自动备份失败</string>
<string name="end">结束</string>
<string name="custom_group_summary">关闭替换分组/开启添加分组</string>
</resources>

@ -10,6 +10,6 @@
<string name="home_page_url" translatable="false">https://gedoor.github.io/MyBookshelf/</string>
<string name="latest_release_url" translatable="false">https://github.com/gedoor/legado/releases/latest</string>
<string name="latest_release_api" translatable="false">https://api.github.com/repos/gedoor/legado/releases/latest</string>
<string name="tg_url" translatable="false">https://t.me/yueduguanfang</string>
<string name="tg_url" translatable="false">https://t.me/legado_channels</string>
<string name="discord_url" translatable="false">https://discord.gg/qDE52P5xGW</string>
</resources>

@ -853,5 +853,6 @@
<string name="unknown_error">未知错误</string>
<string name="autobackup_fail">Autobackup failed</string>
<string name="end">end</string>
<string name="custom_group_summary">关闭替换分组/开启添加分组</string>
</resources>

@ -82,14 +82,6 @@
app:cpv_dialogType="preset"
app:iconSpaceReserved="false" />
<io.legado.app.ui.widget.prefs.Preference
android:key="backgroundImage"
android:title="@string/background_image" />
<io.legado.app.ui.widget.prefs.Preference
android:key="backgroundImageBlurring"
android:title="@string/background_image_blurring" />
<io.legado.app.ui.widget.prefs.ColorPreference
android:defaultValue="@color/md_grey_200"
android:key="colorBottomBackground"
@ -100,12 +92,13 @@
app:cpv_dialogType="preset"
app:iconSpaceReserved="false" />
<io.legado.app.ui.widget.prefs.Preference
android:key="backgroundImage"
android:title="@string/background_image" />
<io.legado.app.ui.widget.prefs.Preference
android:key="defaultCover"
android:title="@string/default_cover"
app:allowDividerAbove="false"
app:allowDividerBelow="false"
app:iconSpaceReserved="false" />
android:title="@string/default_cover" />
<io.legado.app.ui.widget.prefs.Preference
android:key="saveDayTheme"
@ -149,14 +142,6 @@
app:cpv_dialogType="preset"
app:iconSpaceReserved="false" />
<io.legado.app.ui.widget.prefs.Preference
android:key="backgroundImageNight"
android:title="@string/background_image" />
<io.legado.app.ui.widget.prefs.Preference
android:key="backgroundImageNightBlurring"
android:title="@string/background_image_blurring" />
<io.legado.app.ui.widget.prefs.ColorPreference
android:defaultValue="@color/md_grey_800"
android:key="colorBottomBackgroundNight"
@ -165,17 +150,20 @@
app:cpv_dialogType="preset"
app:iconSpaceReserved="false" />
<io.legado.app.ui.widget.prefs.Preference
android:key="backgroundImageNight"
android:title="@string/background_image" />
<io.legado.app.ui.widget.prefs.Preference
android:key="defaultCoverDark"
android:title="@string/default_cover"
app:allowDividerAbove="false"
app:allowDividerBelow="false"
app:iconSpaceReserved="false" />
android:title="@string/default_cover" />
<io.legado.app.ui.widget.prefs.Preference
android:key="saveNightTheme"
android:summary="@string/save_night_theme_summary"
android:title="@string/save_theme_config"
app:allowDividerAbove="false"
app:allowDividerBelow="false"
app:iconSpaceReserved="false" />
</io.legado.app.ui.widget.prefs.PreferenceCategory>

@ -51,9 +51,9 @@ public class ResourceUtil {
public static Resource createPublicResource(String name, String author, String intro, String kind, String wordCount, String model, String href) {
String html = model.replace("{name}", name)
.replace("{author}", author)
.replace("{kind}", kind)
.replace("{wordCount}", wordCount)
.replace("{intro}", StringUtil.formatHtml(intro));
.replace("{kind}", kind == null ? "" : kind)
.replace("{wordCount}", wordCount == null ? "" : wordCount)
.replace("{intro}", StringUtil.formatHtml(intro == null ? "" : intro));
return new Resource(html.getBytes(), href);
}

Loading…
Cancel
Save