Răsfoiți Sursa

Merge branch 'gedoor:master' into master

Xwite 4 ani în urmă
părinte
comite
d026cf3382
28 a modificat fișierele cu 201 adăugiri și 109 ștergeri
  1. 2 1
      app/build.gradle
  2. BIN
      app/cronetlib/cronet_api.jar
  3. BIN
      app/cronetlib/cronet_impl_common_java.jar
  4. BIN
      app/cronetlib/cronet_impl_native_java.jar
  5. 1 1
      app/src/main/assets/cronet.json
  6. 7 19
      app/src/main/assets/updateLog.md
  7. 1 1
      app/src/main/java/io/legado/app/App.kt
  8. 2 3
      app/src/main/java/io/legado/app/constant/EventBus.kt
  9. 0 2
      app/src/main/java/io/legado/app/help/config/AppConfig.kt
  10. 3 1
      app/src/main/java/io/legado/app/help/storage/AppWebDav.kt
  11. 11 3
      app/src/main/java/io/legado/app/model/AudioPlay.kt
  12. 32 34
      app/src/main/java/io/legado/app/model/localBook/LocalBook.kt
  13. 23 18
      app/src/main/java/io/legado/app/service/AudioPlayService.kt
  14. 2 2
      app/src/main/java/io/legado/app/service/BaseReadAloudService.kt
  15. 22 1
      app/src/main/java/io/legado/app/ui/book/audio/AudioPlayActivity.kt
  16. 40 0
      app/src/main/java/io/legado/app/ui/book/audio/ComposeView.kt
  17. 2 2
      app/src/main/java/io/legado/app/ui/book/local/ImportBookViewModel.kt
  18. 16 12
      app/src/main/java/io/legado/app/ui/book/read/ReadBookViewModel.kt
  19. 1 1
      app/src/main/java/io/legado/app/ui/book/read/config/ReadAloudDialog.kt
  20. 28 7
      app/src/main/res/layout/activity_audio_play.xml
  21. 1 0
      app/src/main/res/values-es-rES/strings.xml
  22. 1 0
      app/src/main/res/values-ja-rJP/strings.xml
  23. 1 0
      app/src/main/res/values-pt-rBR/strings.xml
  24. 1 0
      app/src/main/res/values-zh-rHK/strings.xml
  25. 1 0
      app/src/main/res/values-zh-rTW/strings.xml
  26. 1 0
      app/src/main/res/values-zh/strings.xml
  27. 1 0
      app/src/main/res/values/strings.xml
  28. 1 1
      gradle.properties

+ 2 - 1
app/build.gradle

@@ -244,8 +244,9 @@ dependencies {
     implementation('com.github.liuyueyi.quick-chinese-transfer:quick-transfer-core:0.2.3')
     implementation('com.github.liuyueyi.quick-chinese-transfer:quick-transfer-core:0.2.3')
 
 
     //加解密类库
     //加解密类库
-    implementation('cn.hutool:hutool-crypto:5.7.22')
+    implementation('cn.hutool:hutool-crypto:5.8.0.M1')
 
 
+    //renderscriptToolkit
     implementation('com.github.android:renderscript-intrinsics-replacement-toolkit:b6363490c3')
     implementation('com.github.android:renderscript-intrinsics-replacement-toolkit:b6363490c3')
 
 
     //代码编辑com.github.AmrDeveloper:CodeView已集成到应用内
     //代码编辑com.github.AmrDeveloper:CodeView已集成到应用内

BIN
app/cronetlib/cronet_api.jar


BIN
app/cronetlib/cronet_impl_common_java.jar


BIN
app/cronetlib/cronet_impl_native_java.jar


+ 1 - 1
app/src/main/assets/cronet.json

@@ -1 +1 @@
-{"x86":"dad15d54e348efc1e692d72e72e9d52e","armeabi-v7a":"8745ce95fc4bc9063743a890d92bf411","x86_64":"35dd2701803d69aa66d8ac8f836e0123","arm64-v8a":"30165de860a5f288417d536d542401c3","version":"99.0.4844.88"}
+{"x86":"ff4b324fca11309c5e93a45f65970540","armeabi-v7a":"65e8bb0095d5be314ada2f523718f477","x86_64":"3dac75809f86b8edd1399a642bd26f59","arm64-v8a":"ac515c2995b4fb831419313056b1d3f0","version":"100.0.4896.58"}

+ 7 - 19
app/src/main/assets/updateLog.md

@@ -11,6 +11,13 @@
 * 正文出现缺字漏字、内容缺失、排版错乱等情况,有可能是净化规则或简繁转换出现问题。
 * 正文出现缺字漏字、内容缺失、排版错乱等情况,有可能是净化规则或简繁转换出现问题。
 * 漫画源看书显示乱码,**阅读与其他软件的源并不通用**,请导入阅读的支持的漫画源!
 * 漫画源看书显示乱码,**阅读与其他软件的源并不通用**,请导入阅读的支持的漫画源!
 
 
+**2022/03/29**
+
+* 更新cronet: 100.0.4896.58
+* tts朗读不在指定为中文,跟随系统设置,因为有些人需要朗读英文,如果朗读不出来可以去tts设置里看看是不是中文
+* 修复详情也封面背景模糊失效的bug
+* 优化音频界面定时设定
+
 **2022/03/25**
 **2022/03/25**
 
 
 * 更新cronet: 99.0.4844.88
 * 更新cronet: 99.0.4844.88
@@ -557,22 +564,6 @@ fun getZipByteArrayContent(url: String, path: String): ByteArray?
 * 样式属性可以搜索 [FleboxLayout子元素支持的属性介绍](https://www.jianshu.com/p/3c471953e36d)
 * 样式属性可以搜索 [FleboxLayout子元素支持的属性介绍](https://www.jianshu.com/p/3c471953e36d)
 * 样式属性可省略,有默认值
 * 样式属性可省略,有默认值
 
 
-```json
-[
-  {
-    "title": "xxx",
-    "url": "",
-    "style": {
-      "layout_flexGrow": 0,
-      "layout_flexShrink": 1,
-      "layout_alignSelf": "auto",
-      "layout_flexBasisPercent": -1,
-      "layout_wrapBefore": false
-    }
-  }
-]
-```
-
 **2021/07/07**
 **2021/07/07**
 
 
 1. 默认规则新增类似`jsonPath`的索引写法 by bushixuanqi
 1. 默认规则新增类似`jsonPath`的索引写法 by bushixuanqi
@@ -770,9 +761,6 @@ chapter.html的关键字有{title}、{content}
 
 
 * 增加三星 S Pen 支持 by [dacer](https://github.com/dacer)
 * 增加三星 S Pen 支持 by [dacer](https://github.com/dacer)
 * 订阅添加阅读下载,可以从多个渠道下载
 * 订阅添加阅读下载,可以从多个渠道下载
-* 修复一些BUG
-  **2020/12/30**
-
 * 解决文件下载异常,在线语音可正常播放 by [Celeter](https://github.com/Celeter)
 * 解决文件下载异常,在线语音可正常播放 by [Celeter](https://github.com/Celeter)
 * 更新默认在线朗读库, 默认id小于0方便下次更新时删除旧数据, 有重复的自己删除
 * 更新默认在线朗读库, 默认id小于0方便下次更新时删除旧数据, 有重复的自己删除
 * 导入导出书单
 * 导入导出书单

+ 1 - 1
app/src/main/java/io/legado/app/App.kt

@@ -58,7 +58,7 @@ class App : MultiDexApplication() {
                 2 -> ChineseUtils.s2t("初始化")
                 2 -> ChineseUtils.s2t("初始化")
             }
             }
             //同步阅读记录
             //同步阅读记录
-            if (AppWebDav.isOk && AppConfig.syncBookProgress) {
+            if (AppWebDav.syncBookProgress) {
                 AppWebDav.downloadAllBookProgress()
                 AppWebDav.downloadAllBookProgress()
             }
             }
         }
         }

+ 2 - 3
app/src/main/java/io/legado/app/constant/EventBus.kt

@@ -7,7 +7,8 @@ object EventBus {
     const val BOOKSHELF_REFRESH = "bookshelfRefresh"
     const val BOOKSHELF_REFRESH = "bookshelfRefresh"
     const val ALOUD_STATE = "aloud_state"
     const val ALOUD_STATE = "aloud_state"
     const val TTS_PROGRESS = "ttsStart"
     const val TTS_PROGRESS = "ttsStart"
-    const val TTS_DS = "ttsDs"
+    const val AUDIO_DS = "audioDs"
+    const val READ_ALOUD_DS = "readAloudDs"
     const val BATTERY_CHANGED = "batteryChanged"
     const val BATTERY_CHANGED = "batteryChanged"
     const val TIME_CHANGED = "timeChanged"
     const val TIME_CHANGED = "timeChanged"
     const val UP_CONFIG = "upConfig"
     const val UP_CONFIG = "upConfig"
@@ -17,13 +18,11 @@ object EventBus {
     const val AUDIO_PROGRESS = "audioProgress"
     const val AUDIO_PROGRESS = "audioProgress"
     const val AUDIO_SIZE = "audioSize"
     const val AUDIO_SIZE = "audioSize"
     const val AUDIO_SPEED = "audioSpeed"
     const val AUDIO_SPEED = "audioSpeed"
-    const val AUDIO_ERROR = "audioError"
     const val NOTIFY_MAIN = "notifyMain"
     const val NOTIFY_MAIN = "notifyMain"
     const val WEB_SERVICE = "webService"
     const val WEB_SERVICE = "webService"
     const val UP_DOWNLOAD = "upDownload"
     const val UP_DOWNLOAD = "upDownload"
     const val SAVE_CONTENT = "saveContent"
     const val SAVE_CONTENT = "saveContent"
     const val CHECK_SOURCE = "checkSource"
     const val CHECK_SOURCE = "checkSource"
-    const val CHECK_SOURCE_MESSAGE = "checkSourceMessage"
     const val CHECK_SOURCE_DONE = "checkSourceDone"
     const val CHECK_SOURCE_DONE = "checkSourceDone"
     const val TIP_COLOR = "tipColor"
     const val TIP_COLOR = "tipColor"
     const val SOURCE_CHANGED = "sourceChanged"
     const val SOURCE_CHANGED = "sourceChanged"

+ 0 - 2
app/src/main/java/io/legado/app/help/config/AppConfig.kt

@@ -282,8 +282,6 @@ object AppConfig : SharedPreferences.OnSharedPreferenceChangeListener {
 
 
     val importKeepName get() = appCtx.getPrefBoolean(PreferKey.importKeepName)
     val importKeepName get() = appCtx.getPrefBoolean(PreferKey.importKeepName)
 
 
-    val syncBookProgress get() = appCtx.getPrefBoolean(PreferKey.syncBookProgress, true)
-
     var preDownloadNum
     var preDownloadNum
         get() = appCtx.getPrefInt(PreferKey.preDownloadNum, 10)
         get() = appCtx.getPrefInt(PreferKey.preDownloadNum, 10)
         set(value) {
         set(value) {

+ 3 - 1
app/src/main/java/io/legado/app/help/storage/AppWebDav.kt

@@ -29,6 +29,8 @@ object AppWebDav {
     private val bookProgressUrl = "${rootWebDavUrl}bookProgress/"
     private val bookProgressUrl = "${rootWebDavUrl}bookProgress/"
     private val zipFilePath = "${appCtx.externalFiles.absolutePath}${File.separator}backup.zip"
     private val zipFilePath = "${appCtx.externalFiles.absolutePath}${File.separator}backup.zip"
 
 
+    val syncBookProgress get() = appCtx.getPrefBoolean(PreferKey.syncBookProgress, true)
+
     var isOk = false
     var isOk = false
 
 
     init {
     init {
@@ -167,7 +169,7 @@ object AppWebDav {
 
 
     fun uploadBookProgress(book: Book) {
     fun uploadBookProgress(book: Book) {
         if (!isOk) return
         if (!isOk) return
-        if (!AppConfig.syncBookProgress) return
+        if (!syncBookProgress) return
         if (!NetworkUtils.isAvailable()) return
         if (!NetworkUtils.isAvailable()) return
         Coroutine.async {
         Coroutine.async {
             val bookProgress = BookProgress(book)
             val bookProgress = BookProgress(book)

+ 11 - 3
app/src/main/java/io/legado/app/model/AudioPlay.kt

@@ -15,6 +15,7 @@ import io.legado.app.help.coroutine.Coroutine
 import io.legado.app.service.AudioPlayService
 import io.legado.app.service.AudioPlayService
 import io.legado.app.utils.postEvent
 import io.legado.app.utils.postEvent
 import io.legado.app.utils.startService
 import io.legado.app.utils.startService
+import splitties.init.appCtx
 
 
 object AudioPlay {
 object AudioPlay {
     var titleData = MutableLiveData<String>()
     var titleData = MutableLiveData<String>()
@@ -138,10 +139,17 @@ object AudioPlay {
         }
         }
     }
     }
 
 
-    fun addTimer(context: Context) {
-        val intent = Intent(context, AudioPlayService::class.java)
+    fun addTimer() {
+        val intent = Intent(appCtx, AudioPlayService::class.java)
         intent.action = IntentAction.addTimer
         intent.action = IntentAction.addTimer
-        context.startService(intent)
+        appCtx.startService(intent)
+    }
+
+    fun setTimer(minute: Int) {
+        val intent = Intent(appCtx, AudioPlayService::class.java)
+        intent.action = IntentAction.setTimer
+        intent.putExtra("minute", minute)
+        appCtx.startService(intent)
     }
     }
 
 
     fun saveRead(book: Book) {
     fun saveRead(book: Book) {

+ 32 - 34
app/src/main/java/io/legado/app/model/localBook/LocalBook.kt

@@ -16,12 +16,17 @@ import java.io.File
 import java.io.FileInputStream
 import java.io.FileInputStream
 import java.io.FileNotFoundException
 import java.io.FileNotFoundException
 import java.io.InputStream
 import java.io.InputStream
-import java.util.regex.Matcher
 import java.util.regex.Pattern
 import java.util.regex.Pattern
 import javax.script.SimpleBindings
 import javax.script.SimpleBindings
 
 
 object LocalBook {
 object LocalBook {
 
 
+    private val nameAuthorPatterns = arrayOf(
+        Pattern.compile("(.*?)《([^《》]+)》(.*)"),
+        Pattern.compile("(^)(.+) 作者:(.+)$"),
+        Pattern.compile("(^)(.+) by (.+)$")
+    )
+
     @Throws(FileNotFoundException::class, SecurityException::class)
     @Throws(FileNotFoundException::class, SecurityException::class)
     fun getBookInputStream(book: Book): InputStream {
     fun getBookInputStream(book: Book): InputStream {
         val uri = Uri.parse(book.bookUrl)
         val uri = Uri.parse(book.bookUrl)
@@ -117,44 +122,37 @@ object LocalBook {
         val tempFileName = fileName.substringBeforeLast(".")
         val tempFileName = fileName.substringBeforeLast(".")
         var name: String
         var name: String
         var author: String
         var author: String
-        //匹配(知轩藏书常用格式) 《书名》其它信息作者:作者名.txt
-        val m1 = Pattern
-            .compile("(.*?)《([^《》]+)》(.*)")
-            .matcher(tempFileName)
-        //匹配 书名 作者:作者名.txt
-        val m2 = Pattern
-            .compile("(^)(.+) 作者:(.+)$")
-            .matcher(tempFileName)
-
-        (m1.takeIf { m1.find() } ?: m2.takeIf { m2.find() }).run {
-            if (this is Matcher) {
-                //按默认格式将文件名分解成书名、作者名
+        for (pattern in nameAuthorPatterns) {
+            pattern.matcher(tempFileName).takeIf { it.find() }?.run {
                 name = group(2)!!
                 name = group(2)!!
-                author = BookHelp.formatBookAuthor((group(1) ?: "") + (group(3) ?: ""))
-            } else if (!AppConfig.bookImportFileName.isNullOrBlank()) {
-                try {
-                    //在脚本中定义如何分解文件名成书名、作者名
-                    val jsonStr = AppConst.SCRIPT_ENGINE.eval(
-                        //在用户脚本后添加捕获author、name的代码,只要脚本中author、name有值就会被捕获
-                        AppConfig.bookImportFileName + "\nJSON.stringify({author:author,name:name})",
-                        //将文件名注入到脚步的src变量中
-                        SimpleBindings().also { it["src"] = tempFileName }
-                    ).toString()
-                    val bookMess = GSON.fromJsonObject<HashMap<String, String>>(jsonStr)
-                        .getOrThrow() ?: HashMap()
-                    name = bookMess["name"] ?: tempFileName
-                    author = bookMess["author"]?.takeIf { it.length != tempFileName.length } ?: ""
-                } catch (e: Exception) {
-                    name = BookHelp.formatBookName(tempFileName)
-                    author = BookHelp.formatBookAuthor(tempFileName.replace(name, ""))
-                        .takeIf { it.length != tempFileName.length } ?: ""
-                }
-            } else {
+                val group1 = group(1) ?: ""
+                val group3 = group(3) ?: ""
+                author = BookHelp.formatBookAuthor(group1 + group3)
+                return Pair(name, author)
+            }
+        }
+        if (!AppConfig.bookImportFileName.isNullOrBlank()) {
+            try {
+                //在脚本中定义如何分解文件名成书名、作者名
+                val jsonStr = AppConst.SCRIPT_ENGINE.eval(
+                    //在用户脚本后添加捕获author、name的代码,只要脚本中author、name有值就会被捕获
+                    AppConfig.bookImportFileName + "\nJSON.stringify({author:author,name:name})",
+                    //将文件名注入到脚步的src变量中
+                    SimpleBindings().also { it["src"] = tempFileName }
+                ).toString()
+                val bookMess = GSON.fromJsonObject<HashMap<String, String>>(jsonStr)
+                    .getOrThrow() ?: HashMap()
+                name = bookMess["name"] ?: tempFileName
+                author = bookMess["author"]?.takeIf { it.length != tempFileName.length } ?: ""
+            } catch (e: Exception) {
                 name = BookHelp.formatBookName(tempFileName)
                 name = BookHelp.formatBookName(tempFileName)
                 author = BookHelp.formatBookAuthor(tempFileName.replace(name, ""))
                 author = BookHelp.formatBookAuthor(tempFileName.replace(name, ""))
                     .takeIf { it.length != tempFileName.length } ?: ""
                     .takeIf { it.length != tempFileName.length } ?: ""
             }
             }
-
+        } else {
+            name = BookHelp.formatBookName(tempFileName)
+            author = BookHelp.formatBookAuthor(tempFileName.replace(name, ""))
+                .takeIf { it.length != tempFileName.length } ?: ""
         }
         }
         return Pair(name, author)
         return Pair(name, author)
     }
     }

+ 23 - 18
app/src/main/java/io/legado/app/service/AudioPlayService.kt

@@ -45,7 +45,7 @@ class AudioPlayService : BaseService(),
     companion object {
     companion object {
         var isRun = false
         var isRun = false
             private set
             private set
-        var pause = false
+        var pause = true
             private set
             private set
         var timeMinute: Int = 0
         var timeMinute: Int = 0
             private set
             private set
@@ -59,11 +59,10 @@ class AudioPlayService : BaseService(),
     private val exoPlayer: ExoPlayer by lazy {
     private val exoPlayer: ExoPlayer by lazy {
         ExoPlayer.Builder(this).build()
         ExoPlayer.Builder(this).build()
     }
     }
-    private var title: String = ""
-    private var subtitle: String = ""
     private var mediaSessionCompat: MediaSessionCompat? = null
     private var mediaSessionCompat: MediaSessionCompat? = null
     private var broadcastReceiver: BroadcastReceiver? = null
     private var broadcastReceiver: BroadcastReceiver? = null
-    private var position = 0
+    private var audioFocusLossTransient = false
+    private var position = AudioPlay.book?.durChapterPos ?: 0
     private var dsJob: Job? = null
     private var dsJob: Job? = null
     private var upPlayProgressJob: Job? = null
     private var upPlayProgressJob: Job? = null
     private var playSpeed: Float = 1f
     private var playSpeed: Float = 1f
@@ -76,18 +75,16 @@ class AudioPlayService : BaseService(),
         initMediaSession()
         initMediaSession()
         initBroadcastReceiver()
         initBroadcastReceiver()
         upMediaSessionPlaybackState(PlaybackStateCompat.STATE_PLAYING)
         upMediaSessionPlaybackState(PlaybackStateCompat.STATE_PLAYING)
+        doDs()
     }
     }
 
 
     override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
     override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
         intent?.action?.let { action ->
         intent?.action?.let { action ->
             when (action) {
             when (action) {
                 IntentAction.play -> {
                 IntentAction.play -> {
-                    AudioPlay.book?.let {
-                        title = it.name
-                        subtitle = AudioPlay.durChapter?.title ?: ""
-                        position = it.durChapterPos
-                        loadContent()
-                    }
+                    pause = false
+                    position = AudioPlay.book?.durChapterPos ?: 0
+                    loadContent()
                 }
                 }
                 IntentAction.pause -> pause(true)
                 IntentAction.pause -> pause(true)
                 IntentAction.resume -> resume()
                 IntentAction.resume -> resume()
@@ -171,6 +168,10 @@ class AudioPlayService : BaseService(),
     private fun resume() {
     private fun resume() {
         try {
         try {
             pause = false
             pause = false
+            if (url.isEmpty()) {
+                loadContent()
+                return
+            }
             if (!exoPlayer.isPlaying) {
             if (!exoPlayer.isPlaying) {
                 exoPlayer.play()
                 exoPlayer.play()
             }
             }
@@ -260,11 +261,11 @@ class AudioPlayService : BaseService(),
     }
     }
 
 
     private fun addTimer() {
     private fun addTimer() {
-        if (timeMinute == 60) {
+        if (timeMinute == 180) {
             timeMinute = 0
             timeMinute = 0
         } else {
         } else {
             timeMinute += 10
             timeMinute += 10
-            if (timeMinute > 60) timeMinute = 60
+            if (timeMinute > 180) timeMinute = 180
         }
         }
         doDs()
         doDs()
     }
     }
@@ -273,7 +274,7 @@ class AudioPlayService : BaseService(),
      * 定时
      * 定时
      */
      */
     private fun doDs() {
     private fun doDs() {
-        postEvent(EventBus.TTS_DS, timeMinute)
+        postEvent(EventBus.AUDIO_DS, timeMinute)
         upNotification()
         upNotification()
         dsJob?.cancel()
         dsJob?.cancel()
         dsJob = launch {
         dsJob = launch {
@@ -287,7 +288,7 @@ class AudioPlayService : BaseService(),
                         AudioPlay.stop(this@AudioPlayService)
                         AudioPlay.stop(this@AudioPlayService)
                     }
                     }
                 }
                 }
-                postEvent(EventBus.TTS_DS, timeMinute)
+                postEvent(EventBus.AUDIO_DS, timeMinute)
                 upNotification()
                 upNotification()
             }
             }
         }
         }
@@ -360,7 +361,6 @@ class AudioPlayService : BaseService(),
      */
      */
     private fun contentLoadFinish(chapter: BookChapter, content: String) {
     private fun contentLoadFinish(chapter: BookChapter, content: String) {
         if (chapter.index == AudioPlay.book?.durChapterIndex) {
         if (chapter.index == AudioPlay.book?.durChapterIndex) {
-            subtitle = chapter.title
             url = content
             url = content
             play()
             play()
         }
         }
@@ -426,13 +426,18 @@ class AudioPlayService : BaseService(),
         when (focusChange) {
         when (focusChange) {
             AudioManager.AUDIOFOCUS_GAIN -> {
             AudioManager.AUDIOFOCUS_GAIN -> {
                 // 重新获得焦点,  可做恢复播放,恢复后台音量的操作
                 // 重新获得焦点,  可做恢复播放,恢复后台音量的操作
+                audioFocusLossTransient = false
                 if (!pause) resume()
                 if (!pause) resume()
             }
             }
             AudioManager.AUDIOFOCUS_LOSS -> {
             AudioManager.AUDIOFOCUS_LOSS -> {
                 // 永久丢失焦点除非重新主动获取,这种情况是被其他播放器抢去了焦点,  为避免与其他播放器混音,可将音乐暂停
                 // 永久丢失焦点除非重新主动获取,这种情况是被其他播放器抢去了焦点,  为避免与其他播放器混音,可将音乐暂停
+                if (audioFocusLossTransient) {
+                    pause(true)
+                }
             }
             }
             AudioManager.AUDIOFOCUS_LOSS_TRANSIENT -> {
             AudioManager.AUDIOFOCUS_LOSS_TRANSIENT -> {
                 // 暂时丢失焦点,这种情况是被其他应用申请了短暂的焦点,可压低后台音量
                 // 暂时丢失焦点,这种情况是被其他应用申请了短暂的焦点,可压低后台音量
+                audioFocusLossTransient = true
                 if (!pause) pause(false)
                 if (!pause) pause(false)
             }
             }
             AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK -> {
             AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK -> {
@@ -454,9 +459,9 @@ class AudioPlayService : BaseService(),
                 )
                 )
                 else -> getString(R.string.audio_play_t)
                 else -> getString(R.string.audio_play_t)
             }
             }
-            nTitle += ": $title"
-            var nSubtitle = subtitle
-            if (subtitle.isEmpty()) {
+            nTitle += ": ${AudioPlay.book?.name}"
+            var nSubtitle = AudioPlay.durChapter?.title
+            if (nSubtitle.isNullOrEmpty()) {
                 nSubtitle = getString(R.string.audio_play_s)
                 nSubtitle = getString(R.string.audio_play_s)
             }
             }
             val builder = NotificationCompat
             val builder = NotificationCompat

+ 2 - 2
app/src/main/java/io/legado/app/service/BaseReadAloudService.kt

@@ -206,7 +206,7 @@ abstract class BaseReadAloudService : BaseService(),
      */
      */
     @Synchronized
     @Synchronized
     private fun doDs() {
     private fun doDs() {
-        postEvent(EventBus.TTS_DS, timeMinute)
+        postEvent(EventBus.READ_ALOUD_DS, timeMinute)
         upNotification()
         upNotification()
         dsJob?.cancel()
         dsJob?.cancel()
         dsJob = launch {
         dsJob = launch {
@@ -220,7 +220,7 @@ abstract class BaseReadAloudService : BaseService(),
                         ReadAloud.stop(this@BaseReadAloudService)
                         ReadAloud.stop(this@BaseReadAloudService)
                     }
                     }
                 }
                 }
-                postEvent(EventBus.TTS_DS, timeMinute)
+                postEvent(EventBus.READ_ALOUD_DS, timeMinute)
                 upNotification()
                 upNotification()
             }
             }
         }
         }

+ 22 - 1
app/src/main/java/io/legado/app/ui/book/audio/AudioPlayActivity.kt

@@ -1,5 +1,6 @@
 package io.legado.app.ui.book.audio
 package io.legado.app.ui.book.audio
 
 
+import android.annotation.SuppressLint
 import android.app.Activity
 import android.app.Activity
 import android.icu.text.SimpleDateFormat
 import android.icu.text.SimpleDateFormat
 import android.os.Build
 import android.os.Build
@@ -8,6 +9,7 @@ import android.view.Menu
 import android.view.MenuItem
 import android.view.MenuItem
 import android.widget.SeekBar
 import android.widget.SeekBar
 import androidx.activity.viewModels
 import androidx.activity.viewModels
+import androidx.compose.runtime.mutableStateOf
 import io.legado.app.R
 import io.legado.app.R
 import io.legado.app.base.VMBaseActivity
 import io.legado.app.base.VMBaseActivity
 import io.legado.app.constant.EventBus
 import io.legado.app.constant.EventBus
@@ -25,6 +27,7 @@ import io.legado.app.ui.book.changesource.ChangeBookSourceDialog
 import io.legado.app.ui.book.source.edit.BookSourceEditActivity
 import io.legado.app.ui.book.source.edit.BookSourceEditActivity
 import io.legado.app.ui.book.toc.TocActivityResult
 import io.legado.app.ui.book.toc.TocActivityResult
 import io.legado.app.ui.login.SourceLoginActivity
 import io.legado.app.ui.login.SourceLoginActivity
+import io.legado.app.ui.theme.AppTheme
 import io.legado.app.ui.widget.seekbar.SeekBarChangeListener
 import io.legado.app.ui.widget.seekbar.SeekBarChangeListener
 import io.legado.app.utils.*
 import io.legado.app.utils.*
 import io.legado.app.utils.viewbindingdelegate.viewBinding
 import io.legado.app.utils.viewbindingdelegate.viewBinding
@@ -42,6 +45,7 @@ class AudioPlayActivity :
     override val viewModel by viewModels<AudioPlayViewModel>()
     override val viewModel by viewModels<AudioPlayViewModel>()
     private var menu: Menu? = null
     private var menu: Menu? = null
     private var adjustProgress = false
     private var adjustProgress = false
+    private val timerViewState = mutableStateOf(false)
     private val progressTimeFormat by lazy {
     private val progressTimeFormat by lazy {
         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
             SimpleDateFormat("mm:ss", Locale.getDefault())
             SimpleDateFormat("mm:ss", Locale.getDefault())
@@ -153,7 +157,19 @@ class AudioPlayActivity :
             AudioPlay.adjustSpeed(this@AudioPlayActivity, -0.1f)
             AudioPlay.adjustSpeed(this@AudioPlayActivity, -0.1f)
         }
         }
         binding.ivTimer.setOnClickListener {
         binding.ivTimer.setOnClickListener {
-            AudioPlay.addTimer(this@AudioPlayActivity)
+            if (AudioPlayService.isRun) {
+                timerViewState.value = true
+            } else {
+                toastOnUi(R.string.cannot_timed_non_playback)
+            }
+        }
+        binding.composeView.setContent {
+            AppTheme {
+                TimerDialog(
+                    state = timerViewState,
+                    binding.ivTimer
+                )
+            }
         }
         }
     }
     }
 
 
@@ -210,6 +226,7 @@ class AudioPlayActivity :
         }
         }
     }
     }
 
 
+    @SuppressLint("SetTextI18n")
     override fun observeLiveBus() {
     override fun observeLiveBus() {
         observeEvent<Boolean>(EventBus.MEDIA_BUTTON) {
         observeEvent<Boolean>(EventBus.MEDIA_BUTTON) {
             if (it) {
             if (it) {
@@ -239,6 +256,10 @@ class AudioPlayActivity :
             binding.tvSpeed.text = String.format("%.1fX", it)
             binding.tvSpeed.text = String.format("%.1fX", it)
             binding.tvSpeed.visible()
             binding.tvSpeed.visible()
         }
         }
+        observeEventSticky<Int>(EventBus.AUDIO_DS) {
+            binding.tvTimer.text = "${it}m"
+            binding.tvTimer.visible(it > 0)
+        }
     }
     }
 
 
 }
 }

+ 40 - 0
app/src/main/java/io/legado/app/ui/book/audio/ComposeView.kt

@@ -0,0 +1,40 @@
+package io.legado.app.ui.book.audio
+
+import android.view.View
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material.Card
+import androidx.compose.material.Slider
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.MutableState
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.window.Dialog
+import io.legado.app.model.AudioPlay
+import io.legado.app.service.AudioPlayService
+
+
+@Composable
+fun TimerDialog(state: MutableState<Boolean>, parent: View) {
+    val intOffset = IntArray(2)
+    parent.getLocationInWindow(intOffset)
+    if (state.value) {
+        val timeMinute = remember {
+            mutableStateOf(AudioPlayService.timeMinute)
+        }
+        Dialog(onDismissRequest = { state.value = false }) {
+            Card(Modifier.fillMaxWidth()) {
+                Slider(
+                    modifier = Modifier.padding(horizontal = 16.dp),
+                    value = timeMinute.value.toFloat(), onValueChange = {
+                        timeMinute.value = it.toInt()
+                        AudioPlay.setTimer(it.toInt())
+                    },
+                    valueRange = 0f..180f
+                )
+            }
+        }
+    }
+}

+ 2 - 2
app/src/main/java/io/legado/app/ui/book/local/ImportBookViewModel.kt

@@ -58,10 +58,10 @@ class ImportBookViewModel(application: Application) : BaseViewModel(application)
     }.map { docList ->
     }.map { docList ->
         when (sort) {
         when (sort) {
             2 -> docList.sortedWith(
             2 -> docList.sortedWith(
-                compareBy({ !it.isDir }, { it.lastModified }, { it.name })
+                compareBy({ !it.isDir }, { -it.lastModified }, { it.name })
             )
             )
             1 -> docList.sortedWith(
             1 -> docList.sortedWith(
-                compareBy({ !it.isDir }, { it.size }, { it.name })
+                compareBy({ !it.isDir }, { -it.size }, { it.name })
             )
             )
             else -> docList.sortedWith(
             else -> docList.sortedWith(
                 compareBy({ !it.isDir }, { it.name })
                 compareBy({ !it.isDir }, { it.name })

+ 16 - 12
app/src/main/java/io/legado/app/ui/book/read/ReadBookViewModel.kt

@@ -168,20 +168,24 @@ class ReadBookViewModel(application: Application) : BaseViewModel(application) {
         book: Book,
         book: Book,
         alertSync: ((progress: BookProgress) -> Unit)? = null
         alertSync: ((progress: BookProgress) -> Unit)? = null
     ) {
     ) {
-        if (AppConfig.syncBookProgress)
-            execute {
+        execute {
+            if (AppWebDav.syncBookProgress) {
                 AppWebDav.getBookProgress(book)
                 AppWebDav.getBookProgress(book)
-            }.onSuccess {
-                it?.let { progress ->
-                    if (progress.durChapterIndex < book.durChapterIndex ||
-                        (progress.durChapterIndex == book.durChapterIndex && progress.durChapterPos < book.durChapterPos)
-                    ) {
-                        alertSync?.invoke(progress)
-                    } else {
-                        ReadBook.setProgress(progress)
-                    }
-                }
+                    ?: throw NoStackTraceException("没有进度")
+            } else {
+                throw NoStackTraceException("进度同步未启用")
             }
             }
+        }.onSuccess { progress ->
+            if (progress.durChapterIndex < book.durChapterIndex ||
+                (progress.durChapterIndex == book.durChapterIndex
+                    && progress.durChapterPos < book.durChapterPos)
+            ) {
+                alertSync?.invoke(progress)
+            } else {
+                ReadBook.setProgress(progress)
+            }
+        }
+
     }
     }
 
 
     /**
     /**

+ 1 - 1
app/src/main/java/io/legado/app/ui/book/read/config/ReadAloudDialog.kt

@@ -184,7 +184,7 @@ class ReadAloudDialog : BaseDialogFragment(R.layout.dialog_read_aloud) {
 
 
     override fun observeLiveBus() {
     override fun observeLiveBus() {
         observeEvent<Int>(EventBus.ALOUD_STATE) { upPlayState() }
         observeEvent<Int>(EventBus.ALOUD_STATE) { upPlayState() }
-        observeEvent<Int>(EventBus.TTS_DS) { binding.seekTimer.progress = it }
+        observeEvent<Int>(EventBus.READ_ALOUD_DS) { binding.seekTimer.progress = it }
     }
     }
 
 
     interface CallBack {
     interface CallBack {

+ 28 - 7
app/src/main/res/layout/activity_audio_play.xml

@@ -2,10 +2,10 @@
 <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
 <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto"
     xmlns:app="http://schemas.android.com/apk/res-auto"
     xmlns:tools="http://schemas.android.com/tools"
     xmlns:tools="http://schemas.android.com/tools"
-    android:orientation="vertical"
     android:layout_width="match_parent"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     android:layout_height="match_parent"
     android:background="@color/background"
     android:background="@color/background"
+    android:orientation="vertical"
     tools:context=".ui.book.audio.AudioPlayActivity">
     tools:context=".ui.book.audio.AudioPlayActivity">
 
 
     <ImageView
     <ImageView
@@ -28,15 +28,30 @@
         app:layout_constraintTop_toTopOf="parent"
         app:layout_constraintTop_toTopOf="parent"
         app:themeMode="dark" />
         app:themeMode="dark" />
 
 
+    <TextView
+        android:id="@+id/tv_timer"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_margin="16dp"
+        android:background="@drawable/shape_fillet_btn_press"
+        android:paddingLeft="6dp"
+        android:paddingRight="6dp"
+        android:textColor="@color/md_white_1000"
+        android:visibility="invisible"
+        app:drawableLeftCompat="@drawable/ic_timer_black_24dp"
+        app:drawableTint="@color/md_white_1000"
+        app:layout_constraintLeft_toLeftOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/title_bar" />
+
     <TextView
     <TextView
         android:id="@+id/tv_speed"
         android:id="@+id/tv_speed"
         android:layout_width="wrap_content"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:layout_height="wrap_content"
         android:layout_margin="16dp"
         android:layout_margin="16dp"
         android:background="@drawable/shape_fillet_btn_press"
         android:background="@drawable/shape_fillet_btn_press"
+        android:paddingLeft="6dp"
+        android:paddingRight="6dp"
         android:textColor="@color/md_white_1000"
         android:textColor="@color/md_white_1000"
-        android:paddingStart="3dp"
-        android:paddingEnd="3dp"
         android:visibility="invisible"
         android:visibility="invisible"
         app:layout_constraintRight_toRightOf="parent"
         app:layout_constraintRight_toRightOf="parent"
         app:layout_constraintTop_toBottomOf="@id/title_bar" />
         app:layout_constraintTop_toBottomOf="@id/title_bar" />
@@ -58,12 +73,12 @@
         android:id="@+id/tv_sub_title"
         android:id="@+id/tv_sub_title"
         android:layout_width="match_parent"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:layout_height="wrap_content"
+        android:gravity="center"
         android:paddingStart="16dp"
         android:paddingStart="16dp"
-        android:paddingEnd="16dp"
         android:paddingTop="6dp"
         android:paddingTop="6dp"
+        android:paddingEnd="16dp"
         android:paddingBottom="6dp"
         android:paddingBottom="6dp"
         android:textColor="@color/md_white_1000"
         android:textColor="@color/md_white_1000"
-        android:gravity="center"
         app:layout_constraintBottom_toTopOf="@+id/ll_player_progress" />
         app:layout_constraintBottom_toTopOf="@+id/ll_player_progress" />
 
 
     <LinearLayout
     <LinearLayout
@@ -106,10 +121,10 @@
         android:id="@+id/ll_play_menu"
         android:id="@+id/ll_play_menu"
         android:layout_width="match_parent"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:layout_height="wrap_content"
-        android:paddingLeft="24dp"
-        android:paddingRight="24dp"
         android:gravity="center_vertical"
         android:gravity="center_vertical"
         android:orientation="horizontal"
         android:orientation="horizontal"
+        android:paddingLeft="24dp"
+        android:paddingRight="24dp"
         app:layout_constraintBottom_toBottomOf="parent">
         app:layout_constraintBottom_toBottomOf="parent">
 
 
         <ImageView
         <ImageView
@@ -208,4 +223,10 @@
 
 
     </LinearLayout>
     </LinearLayout>
 
 
+    <androidx.compose.ui.platform.ComposeView
+        android:id="@+id/compose_view"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        app:layout_constraintTop_toTopOf="parent" />
+
 </androidx.constraintlayout.widget.ConstraintLayout>
 </androidx.constraintlayout.widget.ConstraintLayout>

+ 1 - 0
app/src/main/res/values-es-rES/strings.xml

@@ -964,5 +964,6 @@
     <string name="scroll_to_dur_source">定位到当前书源</string>
     <string name="scroll_to_dur_source">定位到当前书源</string>
     <string name="sys_tts_config">系统tts设置</string>
     <string name="sys_tts_config">系统tts设置</string>
     <string name="sys_tts_config_summary">打开系统tts设置界面</string>
     <string name="sys_tts_config_summary">打开系统tts设置界面</string>
+    <string name="cannot_timed_non_playback">非播放状态无法定时</string>
     <!-- string end -->
     <!-- string end -->
 </resources>
 </resources>

+ 1 - 0
app/src/main/res/values-ja-rJP/strings.xml

@@ -967,5 +967,6 @@
     <string name="scroll_to_dur_source">定位到当前书源</string>
     <string name="scroll_to_dur_source">定位到当前书源</string>
     <string name="sys_tts_config">系统tts设置</string>
     <string name="sys_tts_config">系统tts设置</string>
     <string name="sys_tts_config_summary">打开系统tts设置界面</string>
     <string name="sys_tts_config_summary">打开系统tts设置界面</string>
+    <string name="cannot_timed_non_playback">非播放状态无法定时</string>
     <!-- string end -->
     <!-- string end -->
 </resources>
 </resources>

+ 1 - 0
app/src/main/res/values-pt-rBR/strings.xml

@@ -967,5 +967,6 @@
     <string name="scroll_to_dur_source">定位到当前书源</string>
     <string name="scroll_to_dur_source">定位到当前书源</string>
     <string name="sys_tts_config">系统tts设置</string>
     <string name="sys_tts_config">系统tts设置</string>
     <string name="sys_tts_config_summary">打开系统tts设置界面</string>
     <string name="sys_tts_config_summary">打开系统tts设置界面</string>
+    <string name="cannot_timed_non_playback">非播放状态无法定时</string>
     <!-- string end -->
     <!-- string end -->
 </resources>
 </resources>

+ 1 - 0
app/src/main/res/values-zh-rHK/strings.xml

@@ -964,5 +964,6 @@
     <string name="scroll_to_dur_source">定位到当前书源</string>
     <string name="scroll_to_dur_source">定位到当前书源</string>
     <string name="sys_tts_config">系统tts设置</string>
     <string name="sys_tts_config">系统tts设置</string>
     <string name="sys_tts_config_summary">打开系统tts设置界面</string>
     <string name="sys_tts_config_summary">打开系统tts设置界面</string>
+    <string name="cannot_timed_non_playback">非播放状态无法定时</string>
     <!-- string end -->
     <!-- string end -->
 </resources>
 </resources>

+ 1 - 0
app/src/main/res/values-zh-rTW/strings.xml

@@ -966,5 +966,6 @@
     <string name="scroll_to_dur_source">定位到目前書源</string>
     <string name="scroll_to_dur_source">定位到目前書源</string>
     <string name="sys_tts_config">系统tts设置</string>
     <string name="sys_tts_config">系统tts设置</string>
     <string name="sys_tts_config_summary">打开系统tts设置界面</string>
     <string name="sys_tts_config_summary">打开系统tts设置界面</string>
+    <string name="cannot_timed_non_playback">非播放状态无法定时</string>
     <!-- string end -->
     <!-- string end -->
 </resources>
 </resources>

+ 1 - 0
app/src/main/res/values-zh/strings.xml

@@ -966,5 +966,6 @@
     <string name="scroll_to_dur_source">定位到当前书源</string>
     <string name="scroll_to_dur_source">定位到当前书源</string>
     <string name="sys_tts_config">系统tts设置</string>
     <string name="sys_tts_config">系统tts设置</string>
     <string name="sys_tts_config_summary">打开系统tts设置界面</string>
     <string name="sys_tts_config_summary">打开系统tts设置界面</string>
+    <string name="cannot_timed_non_playback">非播放状态无法定时</string>
     <!-- string end -->
     <!-- string end -->
 </resources>
 </resources>

+ 1 - 0
app/src/main/res/values/strings.xml

@@ -967,5 +967,6 @@
     <string name="scroll_to_dur_source">定位到当前书源</string>
     <string name="scroll_to_dur_source">定位到当前书源</string>
     <string name="sys_tts_config">系统tts设置</string>
     <string name="sys_tts_config">系统tts设置</string>
     <string name="sys_tts_config_summary">打开系统tts设置界面</string>
     <string name="sys_tts_config_summary">打开系统tts设置界面</string>
+    <string name="cannot_timed_non_playback">非播放状态无法定时</string>
     <!-- string end -->
     <!-- string end -->
 </resources>
 </resources>

+ 1 - 1
gradle.properties

@@ -23,4 +23,4 @@ kotlin.code.style=official
 android.enableResourceOptimizations=true
 android.enableResourceOptimizations=true
 
 
 #https://chromiumdash.appspot.com/releases?platform=Android
 #https://chromiumdash.appspot.com/releases?platform=Android
-CronetVersion=99.0.4844.88
+CronetVersion=100.0.4896.58