diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 5256b0ebc..67af4688e 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -186,6 +186,7 @@ + 【备份与恢复】,选择【导入旧版本数据】。 * 请关注[开源阅读软件]()支持我,同时关注合作公众号[小说拾遗](),阅读公众号小编。 +**2020/03/08** +* 订阅长按保存图片 +* 订阅全屏播放 +* 书架全部分组可以隐藏了 +* 内置web书架基本能用了 by 六月 +* 书架整理加入未分组 +* 显示总进度 +* 隐藏状态栏时,标题显示在上方 + **2020/03/07** * 添加标题上下间距调整 * 添加标题大小调整 diff --git a/app/src/main/assets/web/bookshelf.html b/app/src/main/assets/web/bookshelf.html index 485622373..86639426f 100644 --- a/app/src/main/assets/web/bookshelf.html +++ b/app/src/main/assets/web/bookshelf.html @@ -1,32 +1,39 @@  + - - 阅读书架 - + + 阅读3.0书架 + + + - - - - + + + + -
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
-
- + + \ No newline at end of file diff --git a/app/src/main/assets/web/bookshelf.js b/app/src/main/assets/web/bookshelf.js index c182f49fa..7aa35df0f 100644 --- a/app/src/main/assets/web/bookshelf.js +++ b/app/src/main/assets/web/bookshelf.js @@ -1,11 +1,14 @@ var $ = document.querySelector.bind(document) , $$ = document.querySelectorAll.bind(document) , $c = document.createElement.bind(document) - , randomImg = "http://acg.bakayun.cn/randbg.php?t=dfzh" + , randomImg = "http://api.mtyqx.cn/api/random.php" , randomImg2 = "http://img.xjh.me/random_img.php" , books ; +var now_chapter = -1; +var sum_chapter = 0; + var formatTime = value => { return new Date(value).toLocaleString('zh-CN', { hour12: false, year: "numeric", month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit", second: "2-digit" @@ -13,13 +16,13 @@ var formatTime = value => { }; var apiMap = { - getBookshelf: "/getBookshelf", - getChapterList: "/getChapterList", - getBookContent: "/getBookContent", - saveBook: "/saveBook" + "getBookshelf": "/getBookshelf", + "getChapterList": "/getChapterList", + "getBookContent": "/getBookContent", + "saveBook": "/saveBook" }; -var apiAddress = (apiName, url) => { +var apiAddress = (apiName, url, index) => { let address = $('#address').value || window.location.host; if (!(/^http|^\/\//).test(address)) { address = "//" + address; @@ -28,6 +31,9 @@ var apiAddress = (apiName, url) => { address += ":1122"; } localStorage.setItem('address', address); + if (apiName == "getBookContent") { + return address + apiMap[apiName] + (url ? "?url=" + encodeURIComponent(url) : "") + "&index=" + index; + } return address + apiMap[apiName] + (url ? "?url=" + encodeURIComponent(url) : ""); }; @@ -41,12 +47,12 @@ var init = () => { alert(getBookshelf.errorMsg); return; } - books = data.data.sort((book1, book2) => book1.serialNumber - book2.serialNumber); - books.forEach(book => { + books = data.data; + books.forEach((book, i) => { let bookDiv = $c("div"); let img = $c("img"); img.src = book.coverUrl || randomImg; - img.setAttribute("data-series-num", book.serialNumber); + img.setAttribute("data-series-num", i); bookDiv.appendChild(img); bookDiv.innerHTML += ` @@ -59,6 +65,8 @@ var init = () => { }); $$('#books img').forEach(bookImg => bookImg.addEventListener("click", () => { + now_chapter = -1; + sum_chapter = 0; $('#allcontent').classList.add("read"); var book = books[bookImg.getAttribute("data-series-num")]; $("#info").innerHTML = ` @@ -85,11 +93,13 @@ var init = () => { data.data.forEach(chapter => { let ch = $c("button"); - ch.setAttribute("data-url", chapter.durChapterUrl); - ch.setAttribute("title", chapter.durChapterName); - ch.innerHTML = chapter.durChapterName.length > 15 ? chapter.durChapterName.substring(0, 14) + "..." : chapter.durChapterName; + ch.setAttribute("data-url", chapter.bookUrl); + ch.setAttribute("data-index", chapter.index); + ch.setAttribute("title", chapter.title); + ch.innerHTML = chapter.title.length > 15 ? chapter.title.substring(0, 14) + "..." : chapter.title; $("#chapter").appendChild(ch); }); + sum_chapter = data.data.length; $('#chapter').scrollTop = 0; $("#content").innerHTML = "章节列表加载完成!"; }); @@ -126,15 +136,47 @@ $('#showchapter').addEventListener("click", () => { window.location.hash = "#chapter"; }); +$('#up').addEventListener('click', e => { + if (now_chapter > 0) { + now_chapter--; + let clickEvent = document.createEvent('MouseEvents'); + clickEvent.initEvent("click", true, false); + $('[data-index="' + now_chapter + '"]').dispatchEvent(clickEvent); + } else if (now_chapter == 0) { + alert("已经是第一章了^_^!") + } else { + + } +}); + +$('#down').addEventListener('click', e => { + if (now_chapter > -1) { + if (now_chapter < sum_chapter - 1) { + now_chapter++; + let clickEvent = document.createEvent('MouseEvents'); + clickEvent.initEvent("click", true, false); + $('[data-index="' + now_chapter + '"]').dispatchEvent(clickEvent); + + } else { + alert("已经是最后一章了^_^!") + } + } +}); + $('#chapter').addEventListener("click", (e) => { if (e.target.tagName === "BUTTON") { var url = e.target.getAttribute("data-url"); + var index = e.target.getAttribute("data-index"); var name = e.target.getAttribute("title"); if (!url) { - alert("未取得章节地址"); + alert("未取得书籍地址"); + } + if (!index && (0 != index)) { + alert("未取得章节索引"); } + now_chapter = parseInt(index); $("#content").innerHTML = "

" + name + " 加载中...

"; - fetch(apiAddress("getBookContent", url), { mode: "cors" }) + fetch(apiAddress("getBookContent", url, index), { mode: "cors" }) .then(res => res.json()) .then(data => { if (!data.isSuccess) { diff --git a/app/src/main/java/io/legado/app/App.kt b/app/src/main/java/io/legado/app/App.kt index d1a3e9464..7cb9eff1f 100644 --- a/app/src/main/java/io/legado/app/App.kt +++ b/app/src/main/java/io/legado/app/App.kt @@ -1,13 +1,11 @@ package io.legado.app -import android.app.Activity import android.app.Application import android.app.NotificationChannel import android.app.NotificationManager import android.content.Context import android.content.res.Configuration import android.os.Build -import android.os.Bundle import androidx.annotation.RequiresApi import androidx.appcompat.app.AppCompatDelegate import com.jeremyliao.liveeventbus.LiveEventBus @@ -56,7 +54,7 @@ class App : Application() { .lifecycleObserverAlwaysActive(true) .autoClear(false) - registerActivityLife() + registerActivityLifecycleCallbacks(ActivityHelp) } override fun onConfigurationChanged(newConfig: Configuration) { @@ -116,9 +114,7 @@ class App : Application() { */ @RequiresApi(Build.VERSION_CODES.O) private fun createChannelId() { - val notificationManager = - getSystemService(Context.NOTIFICATION_SERVICE) as? NotificationManager - notificationManager?.let { + (getSystemService(Context.NOTIFICATION_SERVICE) as? NotificationManager)?.let { //用唯一的ID创建渠道对象 val downloadChannel = NotificationChannel( channelIdDownload, @@ -157,32 +153,4 @@ class App : Application() { } } - private fun registerActivityLife() { - registerActivityLifecycleCallbacks(object : ActivityLifecycleCallbacks { - override fun onActivityPaused(activity: Activity) { - } - - override fun onActivityResumed(activity: Activity) { - } - - override fun onActivityStarted(activity: Activity) { - - } - - override fun onActivityDestroyed(activity: Activity) { - ActivityHelp.remove(activity) - } - - override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle?) { - } - - override fun onActivityStopped(activity: Activity) { - } - - override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) { - ActivityHelp.add(activity) - } - - }) - } } diff --git a/app/src/main/java/io/legado/app/constant/AppConst.kt b/app/src/main/java/io/legado/app/constant/AppConst.kt index 96cc7567f..810e6deb2 100644 --- a/app/src/main/java/io/legado/app/constant/AppConst.kt +++ b/app/src/main/java/io/legado/app/constant/AppConst.kt @@ -4,8 +4,6 @@ import android.annotation.SuppressLint import io.legado.app.App import io.legado.app.R import io.legado.app.data.entities.BookGroup -import io.legado.app.utils.getPrefBoolean -import io.legado.app.utils.putPrefBoolean import java.text.SimpleDateFormat import javax.script.ScriptEngine import javax.script.ScriptEngineManager @@ -29,14 +27,18 @@ object AppConst { ScriptEngineManager().getEngineByName("rhino") } - val TIME_FORMAT: SimpleDateFormat by lazy { + val timeFormat: SimpleDateFormat by lazy { SimpleDateFormat("HH:mm") } - val DATE_FORMAT: SimpleDateFormat by lazy { + val dateFormat: SimpleDateFormat by lazy { SimpleDateFormat("yyyy/MM/dd HH:mm") } + val fileNameFormat: SimpleDateFormat by lazy { + SimpleDateFormat("yy-MM-dd-HH-mm-ss") + } + val keyboardToolChars: List by lazy { arrayListOf( "@", "&", "|", "%", "/", ":", "[", "]", "{", "}", "<", ">", "\\", "$", "#", "!", ".", @@ -48,18 +50,6 @@ object AppConst { val bookGroupLocal = BookGroup(-2, App.INSTANCE.getString(R.string.local)) val bookGroupAudio = BookGroup(-3, App.INSTANCE.getString(R.string.audio)) - var bookGroupLocalShow: Boolean - get() = App.INSTANCE.getPrefBoolean("bookGroupLocal", false) - set(value) { - App.INSTANCE.putPrefBoolean("bookGroupLocal", value) - } - - var bookGroupAudioShow: Boolean - get() = App.INSTANCE.getPrefBoolean("bookGroupAudio", false) - set(value) { - App.INSTANCE.putPrefBoolean("bookGroupAudio", value) - } - const val notificationIdRead = 1144771 const val notificationIdAudio = 1144772 const val notificationIdWeb = 1144773 diff --git a/app/src/main/java/io/legado/app/data/dao/BookDao.kt b/app/src/main/java/io/legado/app/data/dao/BookDao.kt index f25d76be5..b323a2711 100644 --- a/app/src/main/java/io/legado/app/data/dao/BookDao.kt +++ b/app/src/main/java/io/legado/app/data/dao/BookDao.kt @@ -27,6 +27,9 @@ interface BookDao { @Query("SELECT * FROM books WHERE (`group` & :group) > 0") fun observeByGroup(group: Int): LiveData> + @Query("select * from books where (SELECT sum(groupId) FROM book_groups) & `group` = 0") + fun observeNoGroup(): LiveData> + @Query("SELECT * FROM books WHERE name like '%'||:key||'%' or author like '%'||:key||'%'") fun liveDataSearch(key: String): LiveData> diff --git a/app/src/main/java/io/legado/app/help/ActivityHelp.kt b/app/src/main/java/io/legado/app/help/ActivityHelp.kt index 6138c0c7e..b14c76670 100644 --- a/app/src/main/java/io/legado/app/help/ActivityHelp.kt +++ b/app/src/main/java/io/legado/app/help/ActivityHelp.kt @@ -1,13 +1,15 @@ package io.legado.app.help import android.app.Activity +import android.app.Application +import android.os.Bundle import java.lang.ref.WeakReference import java.util.* /** * Activity管理器,管理项目中Activity的状态 */ -object ActivityHelp { +object ActivityHelp : Application.ActivityLifecycleCallbacks { private val activities: MutableList> = arrayListOf() @@ -86,4 +88,27 @@ object ActivityHelp { } } + override fun onActivityPaused(activity: Activity) { + } + + override fun onActivityResumed(activity: Activity) { + } + + override fun onActivityStarted(activity: Activity) { + + } + + override fun onActivityDestroyed(activity: Activity) { + remove(activity) + } + + override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle?) { + } + + override fun onActivityStopped(activity: Activity) { + } + + override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) { + add(activity) + } } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/help/AppConfig.kt b/app/src/main/java/io/legado/app/help/AppConfig.kt index f71652c23..6eef2bfa0 100644 --- a/app/src/main/java/io/legado/app/help/AppConfig.kt +++ b/app/src/main/java/io/legado/app/help/AppConfig.kt @@ -92,4 +92,23 @@ object AppConfig { set(value) { App.INSTANCE.putPrefInt(PreferKey.systemTypefaces, value) } + + var bookGroupAllShow: Boolean + get() = App.INSTANCE.getPrefBoolean("bookGroupAll", true) + set(value) { + App.INSTANCE.putPrefBoolean("bookGroupAll", value) + } + + var bookGroupLocalShow: Boolean + get() = App.INSTANCE.getPrefBoolean("bookGroupLocal", false) + set(value) { + App.INSTANCE.putPrefBoolean("bookGroupLocal", value) + } + + var bookGroupAudioShow: Boolean + get() = App.INSTANCE.getPrefBoolean("bookGroupAudio", false) + set(value) { + App.INSTANCE.putPrefBoolean("bookGroupAudio", value) + } + } diff --git a/app/src/main/java/io/legado/app/help/JsExtensions.kt b/app/src/main/java/io/legado/app/help/JsExtensions.kt index 7cfd763a1..493ede9c3 100644 --- a/app/src/main/java/io/legado/app/help/JsExtensions.kt +++ b/app/src/main/java/io/legado/app/help/JsExtensions.kt @@ -2,7 +2,7 @@ package io.legado.app.help import android.util.Base64 import androidx.annotation.Keep -import io.legado.app.constant.AppConst.DATE_FORMAT +import io.legado.app.constant.AppConst.dateFormat import io.legado.app.model.analyzeRule.AnalyzeUrl import io.legado.app.utils.EncoderUtils import io.legado.app.utils.MD5Utils @@ -50,6 +50,6 @@ interface JsExtensions { } fun timeFormat(time: Long): String { - return DATE_FORMAT.format(Date(time)) + return dateFormat.format(Date(time)) } } diff --git a/app/src/main/java/io/legado/app/help/ReadBookConfig.kt b/app/src/main/java/io/legado/app/help/ReadBookConfig.kt index b1a17905f..d0e67e901 100644 --- a/app/src/main/java/io/legado/app/help/ReadBookConfig.kt +++ b/app/src/main/java/io/legado/app/help/ReadBookConfig.kt @@ -251,6 +251,12 @@ object ReadBookConfig { if (shareLayout) shareConfig.footerPaddingTop = value else durConfig.footerPaddingTop = value + var showHeaderLine: Boolean + get() = if (shareLayout) shareConfig.showHeaderLine else durConfig.showHeaderLine + set(value) = + if (shareLayout) shareConfig.showHeaderLine = value + else durConfig.showHeaderLine = value + var showFooterLine: Boolean get() = if (shareLayout) shareConfig.showFooterLine else durConfig.showFooterLine set(value) = @@ -288,6 +294,7 @@ object ReadBookConfig { var footerPaddingLeft: Int = 16, var footerPaddingRight: Int = 16, var footerPaddingTop: Int = 6, + var showHeaderLine: Boolean = false, var showFooterLine: Boolean = true ) { fun setBg(bgType: Int, bg: String) { diff --git a/app/src/main/java/io/legado/app/help/http/HttpHelper.kt b/app/src/main/java/io/legado/app/help/http/HttpHelper.kt index a37e053fb..df417e2f9 100644 --- a/app/src/main/java/io/legado/app/help/http/HttpHelper.kt +++ b/app/src/main/java/io/legado/app/help/http/HttpHelper.kt @@ -57,6 +57,16 @@ object HttpHelper { return null } + suspend fun simpleGetByteAsync(url: String): ByteArray? { + NetworkUtils.getBaseUrl(url)?.let { baseUrl -> + return getByteRetrofit(baseUrl) + .create(HttpGetApi::class.java) + .getMapByteAsync(url, mapOf(), mapOf()) + .body() + } + return null + } + inline fun getApiService(baseUrl: String, encode: String? = null): T { return getRetrofit(baseUrl, encode).create(T::class.java) } diff --git a/app/src/main/java/io/legado/app/model/WebBook.kt b/app/src/main/java/io/legado/app/model/WebBook.kt index 5a51b486c..69dfb943c 100644 --- a/app/src/main/java/io/legado/app/model/WebBook.kt +++ b/app/src/main/java/io/legado/app/model/WebBook.kt @@ -84,8 +84,8 @@ class WebBook(val bookSource: BookSource) { scope: CoroutineScope = Coroutine.DEFAULT, context: CoroutineContext = Dispatchers.IO ): Coroutine { - book.type = bookSource.bookSourceType return Coroutine.async(scope, context) { + book.type = bookSource.bookSourceType val body = if (!book.infoHtml.isNullOrEmpty()) { book.infoHtml @@ -111,8 +111,8 @@ class WebBook(val bookSource: BookSource) { scope: CoroutineScope = Coroutine.DEFAULT, context: CoroutineContext = Dispatchers.IO ): Coroutine> { - book.type = bookSource.bookSourceType return Coroutine.async(scope, context) { + book.type = bookSource.bookSourceType val body = if (book.bookUrl == book.tocUrl && !book.tocHtml.isNullOrEmpty()) { book.tocHtml @@ -139,36 +139,50 @@ class WebBook(val bookSource: BookSource) { context: CoroutineContext = Dispatchers.IO ): Coroutine { return Coroutine.async(scope, context) { - if (bookSource.getContentRule().content.isNullOrEmpty()) { - Debug.log(sourceUrl, "⇒正文规则为空,使用章节链接:${bookChapter.url}") - return@async bookChapter.url - } - val body = - if (bookChapter.url == book.bookUrl && !book.tocHtml.isNullOrEmpty()) { - book.tocHtml - } else { - val analyzeUrl = - AnalyzeUrl( - book = book, - ruleUrl = bookChapter.url, - baseUrl = book.tocUrl, - headerMapF = bookSource.getHeaderMap() - ) - analyzeUrl.getResponseAwait( - bookSource.bookSourceUrl, - jsStr = bookSource.getContentRule().webJs, - sourceRegex = bookSource.getContentRule().sourceRegex - ).body - } - BookContent.analyzeContent( - this, - body, - book, - bookChapter, - bookSource, - bookChapter.url, - nextChapterUrl + getContentSuspend( + book, bookChapter, nextChapterUrl, scope ) } } + + /** + * 章节内容 + */ + suspend fun getContentSuspend( + book: Book, + bookChapter: BookChapter, + nextChapterUrl: String? = null, + scope: CoroutineScope = Coroutine.DEFAULT + ): String { + if (bookSource.getContentRule().content.isNullOrEmpty()) { + Debug.log(sourceUrl, "⇒正文规则为空,使用章节链接:${bookChapter.url}") + return bookChapter.url + } + val body = + if (bookChapter.url == book.bookUrl && !book.tocHtml.isNullOrEmpty()) { + book.tocHtml + } else { + val analyzeUrl = + AnalyzeUrl( + book = book, + ruleUrl = bookChapter.url, + baseUrl = book.tocUrl, + headerMapF = bookSource.getHeaderMap() + ) + analyzeUrl.getResponseAwait( + bookSource.bookSourceUrl, + jsStr = bookSource.getContentRule().webJs, + sourceRegex = bookSource.getContentRule().sourceRegex + ).body + } + return BookContent.analyzeContent( + scope, + body, + book, + bookChapter, + bookSource, + bookChapter.url, + nextChapterUrl + ) + } } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/service/DownloadService.kt b/app/src/main/java/io/legado/app/service/DownloadService.kt index b5b983476..732506449 100644 --- a/app/src/main/java/io/legado/app/service/DownloadService.kt +++ b/app/src/main/java/io/legado/app/service/DownloadService.kt @@ -29,8 +29,10 @@ class DownloadService : BaseService() { private val handler = Handler() private var runnable: Runnable = Runnable { upDownload() } private val downloadMap = hashMapOf>() + private val downloadCount = hashMapOf(); private val finalMap = hashMapOf>() private var notificationContent = "正在启动下载" + private val notificationBuilder by lazy { val builder = NotificationCompat.Builder(this, AppConst.channelIdDownload) .setSmallIcon(R.drawable.ic_download) @@ -97,6 +99,11 @@ class DownloadService : BaseService() { finalMap.remove(bookUrl) } + private fun updateNotification(downloadCount:DownloadCount, totalCount: Int, content: String){ + notificationContent = + "进度:${downloadCount.downloadFinishedCount}/$totalCount,成功:${downloadCount.successCount},$content" + } + private fun download() { val task = Coroutine.async(this, context = searchPool) { downloadMap.forEach { entry -> @@ -106,6 +113,9 @@ class DownloadService : BaseService() { val bookSource = App.db.bookSourceDao().getBookSource(book.origin) ?: return@async val webBook = WebBook(bookSource) + + downloadCount[entry.key] = DownloadCount() + entry.value.forEach { chapter -> if (!isActive) return@async if (downloadMap.containsKey(book.bookUrl)) { @@ -116,16 +126,19 @@ class DownloadService : BaseService() { scope = this, context = searchPool ) - .onStart { - notificationContent = chapter.title - } + //.onStart { + // notificationContent = "启动:" + chapter.title + //} .onSuccess(IO) { content -> content?.let { + downloadCount[entry.key]?.increaseSuccess() BookHelp.saveContent(book, chapter, content) } } .onFinally(IO) { synchronized(this@DownloadService) { + downloadCount[entry.key]?.increaseFinished() + downloadCount[entry.key]?.let { updateNotification(it, entry.value.size, chapter.title) } val chapterMap = finalMap[book.bookUrl] ?: linkedSetOf().apply { @@ -135,9 +148,14 @@ class DownloadService : BaseService() { if (chapterMap.size == entry.value.size) { downloadMap.remove(book.bookUrl) finalMap.remove(book.bookUrl) + downloadCount.remove(entry.key) } } } + } else{ + //无需下载的,设置为增加成功 + downloadCount[entry.key]?.increaseSuccess() + downloadCount[entry.key]?.increaseFinished() } } } @@ -176,4 +194,17 @@ class DownloadService : BaseService() { val notification = builder.build() startForeground(AppConst.notificationIdDownload, notification) } +} + +class DownloadCount{ + @Volatile public var downloadFinishedCount = 0 // 下载完成的条目数量 + @Volatile public var successCount = 0 //下载成功的条目数量 + + fun increaseSuccess(){ + ++successCount; + } + + fun increaseFinished(){ + ++downloadFinishedCount; + } } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/about/AboutActivity.kt b/app/src/main/java/io/legado/app/ui/about/AboutActivity.kt index 8fc7d1294..d86cb39a1 100644 --- a/app/src/main/java/io/legado/app/ui/about/AboutActivity.kt +++ b/app/src/main/java/io/legado/app/ui/about/AboutActivity.kt @@ -1,13 +1,19 @@ package io.legado.app.ui.about import android.os.Bundle +import android.text.Spannable +import android.text.SpannableString +import android.text.style.ForegroundColorSpan import android.view.Menu import android.view.MenuItem import io.legado.app.R import io.legado.app.base.BaseActivity +import io.legado.app.lib.theme.accentColor import io.legado.app.utils.openUrl +import kotlinx.android.synthetic.main.activity_about.* import org.jetbrains.anko.share + class AboutActivity : BaseActivity(R.layout.activity_about) { override fun onActivityCreated(savedInstanceState: Bundle?) { @@ -17,6 +23,16 @@ class AboutActivity : BaseActivity(R.layout.activity_about) { supportFragmentManager.beginTransaction() .replace(R.id.fl_fragment, aboutFragment, fTag) .commit() + tv_app_summary.post { + val span = ForegroundColorSpan(accentColor) + val spannableString = SpannableString(tv_app_summary.text) + val start = spannableString.indexOf("开源阅读软件") + spannableString.setSpan( + span, start, start + 6, + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE + ) + tv_app_summary.text = spannableString + } } override fun onCompatCreateOptionsMenu(menu: Menu): Boolean { diff --git a/app/src/main/java/io/legado/app/ui/about/DonateFragment.kt b/app/src/main/java/io/legado/app/ui/about/DonateFragment.kt index 0270b3f77..f82fa1b64 100644 --- a/app/src/main/java/io/legado/app/ui/about/DonateFragment.kt +++ b/app/src/main/java/io/legado/app/ui/about/DonateFragment.kt @@ -2,7 +2,6 @@ package io.legado.app.ui.about import android.content.Context import android.content.Intent -import android.net.Uri import android.os.Bundle import android.view.View import androidx.preference.Preference @@ -12,7 +11,6 @@ import io.legado.app.utils.ACache import io.legado.app.utils.openUrl import io.legado.app.utils.sendToClip import org.jetbrains.anko.longToast -import java.net.URLEncoder class DonateFragment : PreferenceFragmentCompat() { @@ -36,7 +34,6 @@ class DonateFragment : PreferenceFragmentCompat() { "zfbHbRwm" -> requireContext().openUrl(zfbHbRwmUrl) "zfbSkRwm" -> requireContext().openUrl(zfbSkRwmUrl) "qqSkRwm" -> requireContext().openUrl(qqSkRwmUrl) - "zfbSk" -> aliDonate(requireContext()) "zfbHbSsm" -> getZfbHb(requireContext()) "gzGzh" -> requireContext().sendToClip("开源阅读软件") } @@ -59,19 +56,4 @@ class DonateFragment : PreferenceFragmentCompat() { } } - private fun aliDonate(context: Context) { - try { - val qrCode = URLEncoder.encode( - "https://qr.alipay.com/tsx06677nwdk3javroq4ef0?_s=Dweb-other", - "utf-8" - ) - val aliPayQr = - "alipayqr://platformapi/startapp?saId=10000007&qrcode=$qrCode&_t=${System.currentTimeMillis()}" - val intent = Intent(Intent.ACTION_VIEW, Uri.parse(aliPayQr)) - context.startActivity(intent) - } catch (e: Exception) { - e.printStackTrace() - } - } - } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/book/arrange/ArrangeBookActivity.kt b/app/src/main/java/io/legado/app/ui/book/arrange/ArrangeBookActivity.kt index f03cc1df5..f3b0d5ee5 100644 --- a/app/src/main/java/io/legado/app/ui/book/arrange/ArrangeBookActivity.kt +++ b/app/src/main/java/io/legado/app/ui/book/arrange/ArrangeBookActivity.kt @@ -100,6 +100,7 @@ class ArrangeBookActivity : VMBaseActivity(R.layout.activi -1 -> App.db.bookDao().observeAll() -2 -> App.db.bookDao().observeLocal() -3 -> App.db.bookDao().observeAudio() + -11 -> App.db.bookDao().observeNoGroup() else -> App.db.bookDao().observeByGroup(groupId) } booksLiveData?.observe(this, Observer { @@ -112,6 +113,11 @@ class ArrangeBookActivity : VMBaseActivity(R.layout.activi when (item.itemId) { R.id.menu_group_manage -> GroupManageDialog() .show(supportFragmentManager, "groupManage") + R.id.menu_no_group -> { + title_bar.subtitle = getString(R.string.no_group) + groupId = -11 + initBookData() + } R.id.menu_all -> { title_bar.subtitle = item.title groupId = -1 diff --git a/app/src/main/java/io/legado/app/ui/book/download/DownloadActivity.kt b/app/src/main/java/io/legado/app/ui/book/download/DownloadActivity.kt index f731806a7..fec1ed537 100644 --- a/app/src/main/java/io/legado/app/ui/book/download/DownloadActivity.kt +++ b/app/src/main/java/io/legado/app/ui/book/download/DownloadActivity.kt @@ -127,7 +127,7 @@ class DownloadActivity : VMBaseActivity(R.layout.activity_dow FilePicker.selectFolder(this, exportRequestCode) { val path = ACache.get(this@DownloadActivity).getAsString(exportBookPathKey) if (path.isNullOrEmpty()) { - toast("没有默认路径") + toast(R.string.no_default_path) } else { startExport(path) } diff --git a/app/src/main/java/io/legado/app/ui/book/group/GroupManageDialog.kt b/app/src/main/java/io/legado/app/ui/book/group/GroupManageDialog.kt index 1a4322da6..ea4d2b61e 100644 --- a/app/src/main/java/io/legado/app/ui/book/group/GroupManageDialog.kt +++ b/app/src/main/java/io/legado/app/ui/book/group/GroupManageDialog.kt @@ -20,9 +20,9 @@ import io.legado.app.App import io.legado.app.R import io.legado.app.base.adapter.ItemViewHolder import io.legado.app.base.adapter.SimpleRecyclerAdapter -import io.legado.app.constant.AppConst import io.legado.app.constant.Theme import io.legado.app.data.entities.BookGroup +import io.legado.app.help.AppConfig import io.legado.app.help.ItemTouchCallback import io.legado.app.lib.dialogs.alert import io.legado.app.lib.dialogs.customView @@ -42,7 +42,7 @@ import kotlin.collections.ArrayList class GroupManageDialog : DialogFragment(), Toolbar.OnMenuItemClickListener { private lateinit var viewModel: GroupViewModel private lateinit var adapter: GroupAdapter - private var callBack: CallBack? = null + private val callBack: CallBack? get() = parentFragment as? CallBack override fun onStart() { super.onStart() @@ -62,19 +62,12 @@ class GroupManageDialog : DialogFragment(), Toolbar.OnMenuItemClickListener { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - callBack = parentFragment as? CallBack + tool_bar.title = getString(R.string.group_manage) initData() + initMenu() } private fun initData() { - tool_bar.title = getString(R.string.group_manage) - tool_bar.inflateMenu(R.menu.book_group_manage) - tool_bar.menu.applyTint(requireContext(), Theme.getTheme()) - tool_bar.setOnMenuItemClickListener(this) - tool_bar.menu.findItem(R.id.menu_group_local) - .isChecked = AppConst.bookGroupLocalShow - tool_bar.menu.findItem(R.id.menu_group_audio) - .isChecked = AppConst.bookGroupAudioShow adapter = GroupAdapter(requireContext()) recycler_view.layoutManager = LinearLayoutManager(requireContext()) recycler_view.addItemDecoration(VerticalDivider(requireContext())) @@ -90,17 +83,36 @@ class GroupManageDialog : DialogFragment(), Toolbar.OnMenuItemClickListener { ItemTouchHelper(itemTouchCallback).attachToRecyclerView(recycler_view) } + private fun initMenu() { + tool_bar.setOnMenuItemClickListener(this) + tool_bar.inflateMenu(R.menu.book_group_manage) + tool_bar.menu.let { + it.applyTint(requireContext(), Theme.getTheme()) + it.findItem(R.id.menu_group_all) + .isChecked = AppConfig.bookGroupAllShow + it.findItem(R.id.menu_group_local) + .isChecked = AppConfig.bookGroupLocalShow + it.findItem(R.id.menu_group_audio) + .isChecked = AppConfig.bookGroupAudioShow + } + } + override fun onMenuItemClick(item: MenuItem?): Boolean { when (item?.itemId) { R.id.menu_add -> addGroup() + R.id.menu_group_all -> { + item.isChecked = !item.isChecked + AppConfig.bookGroupAllShow = item.isChecked + callBack?.upGroup() + } R.id.menu_group_local -> { item.isChecked = !item.isChecked - AppConst.bookGroupLocalShow = item.isChecked + AppConfig.bookGroupLocalShow = item.isChecked callBack?.upGroup() } R.id.menu_group_audio -> { item.isChecked = !item.isChecked - AppConst.bookGroupAudioShow = item.isChecked + AppConfig.bookGroupAudioShow = item.isChecked callBack?.upGroup() } } diff --git a/app/src/main/java/io/legado/app/ui/book/local/ImportBookAdapter.kt b/app/src/main/java/io/legado/app/ui/book/local/ImportBookAdapter.kt index acc3b7265..153356754 100644 --- a/app/src/main/java/io/legado/app/ui/book/local/ImportBookAdapter.kt +++ b/app/src/main/java/io/legado/app/ui/book/local/ImportBookAdapter.kt @@ -87,7 +87,7 @@ class ImportBookAdapter(context: Context, val callBack: CallBack) : ll_brief.visible() tv_tag.text = item.name.substringAfterLast(".") tv_size.text = StringUtils.toSize(item.size) - tv_date.text = AppConst.DATE_FORMAT.format(item.date) + tv_date.text = AppConst.dateFormat.format(item.date) cb_select.isChecked = selectedUris.contains(item.uri.toString()) } tv_name.text = item.name diff --git a/app/src/main/java/io/legado/app/ui/book/read/ReadBookActivity.kt b/app/src/main/java/io/legado/app/ui/book/read/ReadBookActivity.kt index de1f1d728..2ca4555a3 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/ReadBookActivity.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/ReadBookActivity.kt @@ -479,6 +479,8 @@ class ReadBookActivity : VMBaseActivity(R.layout.activity_boo if (!ReadBook.isLocalBook) { tv_chapter_url.text = it.url tv_chapter_url.visible() + } else { + tv_chapter_url.gone() } seek_read_page.max = it.pageSize().minus(1) seek_read_page.progress = ReadBook.durPageIndex diff --git a/app/src/main/java/io/legado/app/ui/book/read/config/PaddingConfigDialog.kt b/app/src/main/java/io/legado/app/ui/book/read/config/PaddingConfigDialog.kt index a19789fd3..ed1c296de 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/config/PaddingConfigDialog.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/config/PaddingConfigDialog.kt @@ -76,7 +76,8 @@ class PaddingConfigDialog : DialogFragment() { dsb_footer_padding_bottom.progress = footerPaddingBottom dsb_footer_padding_left.progress = footerPaddingLeft dsb_footer_padding_right.progress = footerPaddingRight - cb_show_line.isChecked = showFooterLine + cb_show_top_line.isChecked = showHeaderLine + cb_show_bottom_line.isChecked = showFooterLine } private fun initView() = with(ReadBookConfig) { @@ -131,7 +132,13 @@ class PaddingConfigDialog : DialogFragment() { footerPaddingRight = it postEvent(EventBus.UP_CONFIG, true) } - cb_show_line.onCheckedChangeListener = { cb, isChecked -> + cb_show_top_line.onCheckedChangeListener = { cb, isChecked -> + if (cb.isPressed) { + showHeaderLine = isChecked + postEvent(EventBus.UP_CONFIG, true) + } + } + cb_show_bottom_line.onCheckedChangeListener = { cb, isChecked -> if (cb.isPressed) { showFooterLine = isChecked postEvent(EventBus.UP_CONFIG, true) diff --git a/app/src/main/java/io/legado/app/ui/book/read/config/ReadStyleDialog.kt b/app/src/main/java/io/legado/app/ui/book/read/config/ReadStyleDialog.kt index 68a1d11aa..f29c4ddba 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/config/ReadStyleDialog.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/config/ReadStyleDialog.kt @@ -108,7 +108,7 @@ class ReadStyleDialog : DialogFragment(), FontSelectDialog.CallBack { textBold = !textBold tv_text_bold.isSelected = textBold } - postEvent(EventBus.UP_CONFIG, false) + postEvent(EventBus.UP_CONFIG, true) } tv_text_font.onClick { FontSelectDialog().show(childFragmentManager, "fontSelectDialog") diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/ChapterProvider.kt b/app/src/main/java/io/legado/app/ui/book/read/page/ChapterProvider.kt index 81e483c75..65121906c 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/ChapterProvider.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/ChapterProvider.kt @@ -270,8 +270,8 @@ object ChapterProvider { contentPaint.isAntiAlias = true contentPaint.color = ReadBookConfig.durConfig.textColor() contentPaint.letterSpacing = ReadBookConfig.letterSpacing - val bold = if (ReadBookConfig.textBold) Typeface.BOLD else Typeface.NORMAL - contentPaint.typeface = Typeface.create(typeface, bold) + val style = if (ReadBookConfig.textBold) Typeface.BOLD else Typeface.NORMAL + contentPaint.typeface = Typeface.create(typeface, style) contentPaint.textSize = ReadBookConfig.textSize.sp.toFloat() //间距 lineSpacingExtra = ReadBookConfig.lineSpacingExtra @@ -294,5 +294,5 @@ object ChapterProvider { } val TextPaint.textHeight: Float - get() = this.fontMetrics.descent - fontMetrics.ascent + fontMetrics.leading + get() = fontMetrics.descent - fontMetrics.ascent + fontMetrics.leading } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/ContentView.kt b/app/src/main/java/io/legado/app/ui/book/read/page/ContentView.kt index 8e0bf1d55..a9a752303 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/ContentView.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/ContentView.kt @@ -7,7 +7,7 @@ import android.view.MotionEvent import android.widget.FrameLayout import com.github.houbb.opencc4j.core.impl.ZhConvertBootstrap import io.legado.app.R -import io.legado.app.constant.AppConst.TIME_FORMAT +import io.legado.app.constant.AppConst.timeFormat import io.legado.app.help.AppConfig import io.legado.app.help.ReadBookConfig import io.legado.app.ui.book.read.page.entities.TextPage @@ -18,6 +18,8 @@ import java.util.* class ContentView(context: Context) : FrameLayout(context) { + private var battery = 100 + init { //设置背景防止切换背景时文字重叠 setBackgroundColor(context.getCompatColor(R.color.background)) @@ -26,8 +28,7 @@ class ContentView(context: Context) : FrameLayout(context) { upStyle() upTime() content_text_view.upView = { - tv_bottom_left.text = it.title - setPageIndex(it.index, it.pageSize) + setProgress(it) } } @@ -60,6 +61,7 @@ class ContentView(context: Context) : FrameLayout(context) { footerPaddingRight.dp, footerPaddingBottom.dp ) + vw_top_divider.visible(showHeaderLine) vw_bottom_divider.visible(showFooterLine) content_text_view.upVisibleRect() durConfig.textColor().let { @@ -67,6 +69,14 @@ class ContentView(context: Context) : FrameLayout(context) { tv_top_right.setTextColor(it) tv_bottom_left.setTextColor(it) tv_bottom_right.setTextColor(it) + battery_view.setColor(it) + } + if (hideStatusBar) { + tv_bottom_left.text = timeFormat.format(Date(System.currentTimeMillis())) + battery_view.visible() + battery_view.setBattery(battery) + } else { + battery_view.gone() } } } @@ -85,20 +95,20 @@ class ContentView(context: Context) : FrameLayout(context) { } fun upTime() { - tv_top_left.text = TIME_FORMAT.format(Date(System.currentTimeMillis())) + if (ReadBookConfig.hideStatusBar) { + tv_bottom_right.text = timeFormat.format(Date(System.currentTimeMillis())) + } } fun upBattery(battery: Int) { - tv_top_right.text = context.getString(R.string.battery_show, battery) + this.battery = battery + if (ReadBookConfig.hideStatusBar) { + battery_view.setBattery(battery) + } } fun setContent(textPage: TextPage) { - tv_bottom_left.text = when (AppConfig.chineseConverterType) { - 1 -> ZhConvertBootstrap.newInstance().toSimple(textPage.title) - 2 -> ZhConvertBootstrap.newInstance().toTraditional(textPage.title) - else -> textPage.title - } - setPageIndex(textPage.index, textPage.pageSize) + setProgress(textPage) content_text_view.resetPageOffset() content_text_view.setContent(textPage) } @@ -108,9 +118,19 @@ class ContentView(context: Context) : FrameLayout(context) { } @SuppressLint("SetTextI18n") - fun setPageIndex(pageIndex: Int?, pageSize: Int) { - pageIndex?.let { - tv_bottom_right.text = "${pageIndex.plus(1)}/${pageSize}" + fun setProgress(textPage: TextPage) = textPage.apply { + val title = when (AppConfig.chineseConverterType) { + 1 -> ZhConvertBootstrap.newInstance().toSimple(textPage.title) + 2 -> ZhConvertBootstrap.newInstance().toTraditional(textPage.title) + else -> textPage.title + } + if (ReadBookConfig.hideStatusBar) { + tv_top_left.text = title + tv_top_right.text = readProgress + tv_bottom_left.text = "${index.plus(1)}/$pageSize" + } else { + tv_bottom_left.text = title + tv_bottom_right.text = "${index.plus(1)}/$pageSize $readProgress" } } diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/entities/TextPage.kt b/app/src/main/java/io/legado/app/ui/book/read/page/entities/TextPage.kt index f42fcdb5d..bfb8e822c 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/entities/TextPage.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/entities/TextPage.kt @@ -5,6 +5,7 @@ import android.text.StaticLayout import io.legado.app.App import io.legado.app.R import io.legado.app.ui.book.read.page.ChapterProvider +import java.text.DecimalFormat data class TextPage( var index: Int = 0, @@ -84,4 +85,20 @@ data class TextPage( lineStart += textLine.text.length } } + + val readProgress: String + get() { + val df = DecimalFormat("0.0%") + if (chapterSize == 0 || pageSize == 0 && chapterIndex == 0) { + return "0.0%" + } else if (pageSize == 0) { + return df.format((chapterIndex + 1.0f) / chapterSize.toDouble()) + } + var percent = + df.format(chapterIndex * 1.0f / chapterSize + 1.0f / chapterSize * (index + 1) / pageSize.toDouble()) + if (percent == "100.0%" && (chapterIndex + 1 != chapterSize || index + 1 != pageSize)) { + percent = "99.9%" + } + return percent + } } diff --git a/app/src/main/java/io/legado/app/ui/book/search/SearchActivity.kt b/app/src/main/java/io/legado/app/ui/book/search/SearchActivity.kt index c86f96dba..2d1054859 100644 --- a/app/src/main/java/io/legado/app/ui/book/search/SearchActivity.kt +++ b/app/src/main/java/io/legado/app/ui/book/search/SearchActivity.kt @@ -283,15 +283,10 @@ class SearchActivity : VMBaseActivity(R.layout.activity_book_se */ @Synchronized private fun upSearchItems(items: List, isMandatoryUpdate: Boolean) { - if (!isMandatoryUpdate && System.currentTimeMillis() - refreshTime < 500) { - return - } - refreshTime = System.currentTimeMillis() - try { + if (isMandatoryUpdate || System.currentTimeMillis() - refreshTime > 500) { + refreshTime = System.currentTimeMillis() val diffResult = DiffUtil.calculateDiff(DiffCallBack(adapter.getItems(), items)) adapter.setItems(items, diffResult) - } catch (e: Exception) { - e.printStackTrace() } } diff --git a/app/src/main/java/io/legado/app/ui/book/source/manage/BookSourceActivity.kt b/app/src/main/java/io/legado/app/ui/book/source/manage/BookSourceActivity.kt index eba5950f4..385088640 100644 --- a/app/src/main/java/io/legado/app/ui/book/source/manage/BookSourceActivity.kt +++ b/app/src/main/java/io/legado/app/ui/book/source/manage/BookSourceActivity.kt @@ -85,7 +85,12 @@ class BookSourceActivity : VMBaseActivity(R.layout.activity R.id.menu_group_manage -> GroupManageDialog().show(supportFragmentManager, "groupManage") R.id.menu_import_source_local -> FilePicker - .selectFile(this, importRequestCode, "text/*", arrayOf("txt", "json")) + .selectFile( + this, + importRequestCode, + type = "text/*", + allowExtensions = arrayOf("txt", "json") + ) R.id.menu_import_source_onLine -> showImportDialog() } if (item.groupId == R.id.source_group) { diff --git a/app/src/main/java/io/legado/app/ui/filechooser/FilePicker.kt b/app/src/main/java/io/legado/app/ui/filechooser/FilePicker.kt index 9260fc8bd..8f5549709 100644 --- a/app/src/main/java/io/legado/app/ui/filechooser/FilePicker.kt +++ b/app/src/main/java/io/legado/app/ui/filechooser/FilePicker.kt @@ -14,8 +14,13 @@ import org.jetbrains.anko.toast @Suppress("unused") object FilePicker { - fun selectFolder(activity: AppCompatActivity, requestCode: Int, default: (() -> Unit)? = null) { - activity.alert(titleResource = R.string.select_folder) { + fun selectFolder( + activity: AppCompatActivity, + requestCode: Int, + title: String = activity.getString(R.string.select_folder), + default: (() -> Unit)? = null + ) { + activity.alert(title = title) { val selectList = activity.resources.getStringArray(R.array.select_folder).toMutableList() default ?: let { @@ -45,9 +50,14 @@ object FilePicker { }.show() } - fun selectFolder(fragment: Fragment, requestCode: Int, default: (() -> Unit)? = null) { + fun selectFolder( + fragment: Fragment, + requestCode: Int, + title: String = fragment.getString(R.string.select_folder), + default: (() -> Unit)? = null + ) { fragment.requireContext() - .alert(titleResource = R.string.select_folder) { + .alert(title = title) { val selectList = fragment.resources.getStringArray(R.array.select_folder).toMutableList() default ?: let { @@ -80,11 +90,12 @@ object FilePicker { fun selectFile( activity: BaseActivity, requestCode: Int, + title: String = activity.getString(R.string.select_file), type: String, allowExtensions: Array?, default: (() -> Unit)? = null ) { - activity.alert(titleResource = R.string.select_file) { + activity.alert(title = title) { val selectList = activity.resources.getStringArray(R.array.select_folder).toMutableList() default ?: let { @@ -119,12 +130,13 @@ object FilePicker { fun selectFile( fragment: Fragment, requestCode: Int, + title: String = fragment.getString(R.string.select_file), type: String, allowExtensions: Array, default: (() -> Unit)? = null ) { fragment.requireContext() - .alert(titleResource = R.string.select_file) { + .alert(title = title) { val selectList = fragment.resources.getStringArray(R.array.select_folder).toMutableList() default ?: let { diff --git a/app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfFragment.kt b/app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfFragment.kt index 9cb7dc9de..3608a79ed 100644 --- a/app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfFragment.kt +++ b/app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfFragment.kt @@ -17,6 +17,7 @@ import io.legado.app.base.VMBaseFragment import io.legado.app.constant.AppConst import io.legado.app.constant.PreferKey import io.legado.app.data.entities.BookGroup +import io.legado.app.help.AppConfig import io.legado.app.lib.dialogs.alert import io.legado.app.lib.dialogs.customView import io.legado.app.lib.dialogs.noButton @@ -108,11 +109,13 @@ class BookshelfFragment : VMBaseFragment(R.layout.fragment_b synchronized(this) { tab_layout.removeOnTabSelectedListener(this) bookGroups.clear() - bookGroups.add(AppConst.bookGroupAll) - if (AppConst.bookGroupLocalShow) { + if (AppConfig.bookGroupAllShow) { + bookGroups.add(AppConst.bookGroupAll) + } + if (AppConfig.bookGroupLocalShow) { bookGroups.add(AppConst.bookGroupLocal) } - if (AppConst.bookGroupAudioShow) { + if (AppConfig.bookGroupAudioShow) { bookGroups.add(AppConst.bookGroupAudio) } bookGroups.addAll(it) @@ -134,13 +137,17 @@ class BookshelfFragment : VMBaseFragment(R.layout.fragment_b override fun upGroup() { synchronized(this) { + bookGroups.remove(AppConst.bookGroupAll) bookGroups.remove(AppConst.bookGroupLocal) bookGroups.remove(AppConst.bookGroupAudio) - if (getPrefBoolean("bookGroupAudio", true)) { - bookGroups.add(1, AppConst.bookGroupAudio) + if (AppConfig.bookGroupAudioShow) { + bookGroups.add(0, AppConst.bookGroupAudio) + } + if (AppConfig.bookGroupLocalShow) { + bookGroups.add(0, AppConst.bookGroupLocal) } - if (getPrefBoolean("bookGroupLocal", true)) { - bookGroups.add(1, AppConst.bookGroupLocal) + if (AppConfig.bookGroupAllShow) { + bookGroups.add(0, AppConst.bookGroupAll) } bookshelfAdapter.notifyDataSetChanged() } @@ -196,13 +203,9 @@ class BookshelfFragment : VMBaseFragment(R.layout.fragment_b }.show().applyTint() } - override fun onTabReselected(tab: TabLayout.Tab?) { - - } - - override fun onTabUnselected(tab: TabLayout.Tab?) { + override fun onTabReselected(tab: TabLayout.Tab?) = Unit - } + override fun onTabUnselected(tab: TabLayout.Tab?) = Unit override fun onTabSelected(tab: TabLayout.Tab?) { tab?.position?.let { diff --git a/app/src/main/java/io/legado/app/ui/replacerule/ReplaceRuleActivity.kt b/app/src/main/java/io/legado/app/ui/replacerule/ReplaceRuleActivity.kt index 16ffee4a1..fd0a591b6 100644 --- a/app/src/main/java/io/legado/app/ui/replacerule/ReplaceRuleActivity.kt +++ b/app/src/main/java/io/legado/app/ui/replacerule/ReplaceRuleActivity.kt @@ -180,7 +180,12 @@ class ReplaceRuleActivity : VMBaseActivity(R.layout.activi R.id.menu_del_selection -> viewModel.delSelection(adapter.getSelection()) R.id.menu_import_source_onLine -> showImportDialog() R.id.menu_import_source_local -> FilePicker - .selectFile(this, importRequestCode, "text/*", arrayOf("txt", "json")) + .selectFile( + this, + importRequestCode, + type = "text/*", + allowExtensions = arrayOf("txt", "json") + ) } return super.onCompatOptionsItemSelected(item) } diff --git a/app/src/main/java/io/legado/app/ui/rss/read/ReadRssActivity.kt b/app/src/main/java/io/legado/app/ui/rss/read/ReadRssActivity.kt index 1764a1f32..79ea599d1 100644 --- a/app/src/main/java/io/legado/app/ui/rss/read/ReadRssActivity.kt +++ b/app/src/main/java/io/legado/app/ui/rss/read/ReadRssActivity.kt @@ -1,37 +1,42 @@ package io.legado.app.ui.rss.read import android.annotation.SuppressLint +import android.content.Intent +import android.content.pm.ActivityInfo +import android.content.res.Configuration import android.os.Bundle -import android.view.KeyEvent -import android.view.Menu -import android.view.MenuItem -import android.webkit.WebResourceRequest -import android.webkit.WebSettings -import android.webkit.WebView -import android.webkit.WebViewClient +import android.view.* +import android.webkit.* +import androidx.core.view.size import androidx.lifecycle.Observer import io.legado.app.R import io.legado.app.base.VMBaseActivity import io.legado.app.lib.theme.DrawableUtils import io.legado.app.lib.theme.primaryTextColor -import io.legado.app.utils.NetworkUtils -import io.legado.app.utils.getViewModel -import io.legado.app.utils.openUrl +import io.legado.app.ui.filechooser.FileChooserDialog +import io.legado.app.ui.filechooser.FilePicker +import io.legado.app.utils.* import kotlinx.android.synthetic.main.activity_rss_read.* import kotlinx.coroutines.launch import org.apache.commons.text.StringEscapeUtils import org.jetbrains.anko.share +import org.jetbrains.anko.toast import org.jsoup.Jsoup import org.jsoup.safety.Whitelist + class ReadRssActivity : VMBaseActivity(R.layout.activity_rss_read), + FileChooserDialog.CallBack, ReadRssViewModel.CallBack { override val viewModel: ReadRssViewModel get() = getViewModel(ReadRssViewModel::class.java) - + private val savePathRequestCode = 132 + private val imagePathKey = "" private var starMenuItem: MenuItem? = null private var ttsMenuItem: MenuItem? = null + private var customWebViewCallback: WebChromeClient.CustomViewCallback? = null + private var webPic: String? = null override fun onActivityCreated(savedInstanceState: Bundle?) { viewModel.callBack = this @@ -41,6 +46,20 @@ class ReadRssActivity : VMBaseActivity(R.layout.activity_rss_r viewModel.initData(intent) } + override fun onConfigurationChanged(newConfig: Configuration) { + super.onConfigurationChanged(newConfig) + when (newConfig.orientation) { + Configuration.ORIENTATION_LANDSCAPE -> { + window.clearFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN) + window.addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN) + } + Configuration.ORIENTATION_PORTRAIT -> { + window.clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN) + window.addFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN) + } + } + } + override fun onCompatCreateOptionsMenu(menu: Menu): Boolean { menuInflater.inflate(R.menu.rss_read, menu) return super.onCompatCreateOptionsMenu(menu) @@ -65,6 +84,20 @@ class ReadRssActivity : VMBaseActivity(R.layout.activity_rss_r } private fun initWebView() { + web_view.webChromeClient = object : WebChromeClient() { + override fun onShowCustomView(view: View?, callback: CustomViewCallback?) { + requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR + ll_view.invisible() + custom_web_view.addView(view) + customWebViewCallback = callback + } + + override fun onHideCustomView() { + custom_web_view.removeAllViews() + ll_view.visible() + requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED + } + } web_view.webViewClient = object : WebViewClient() { override fun shouldOverrideUrlLoading( view: WebView?, @@ -95,6 +128,30 @@ class ReadRssActivity : VMBaseActivity(R.layout.activity_rss_r domStorageEnabled = true allowContentAccess = true } + web_view.setOnLongClickListener { + val hitTestResult = web_view.hitTestResult + if (hitTestResult.type == WebView.HitTestResult.IMAGE_TYPE || + hitTestResult.type == WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE + ) { + hitTestResult.extra?.let { + webPic = it + saveImage() + return@setOnLongClickListener true + } + } + return@setOnLongClickListener false + } + } + + private fun saveImage() { + FilePicker.selectFolder(this, savePathRequestCode, getString(R.string.save_image)) { + val path = ACache.get(this).getAsString(imagePathKey) + if (path.isNullOrEmpty()) { + toast(R.string.no_default_path) + } else { + viewModel.saveImage(webPic, path) + } + } } @SuppressLint("SetJavaScriptEnabled") @@ -113,7 +170,6 @@ class ReadRssActivity : VMBaseActivity(R.layout.activity_rss_r url )//不想用baseUrl进else } else { - //webView.loadData(html, "text/html;charset=utf-8", "utf-8")//经测试可以解决中文乱码 web_view.loadDataWithBaseURL( null, html, @@ -175,7 +231,10 @@ class ReadRssActivity : VMBaseActivity(R.layout.activity_rss_r event?.let { when (keyCode) { KeyEvent.KEYCODE_BACK -> if (event.isTracking && !event.isCanceled && web_view.canGoBack()) { - if (web_view.copyBackForwardList().size > 1) { + if (custom_web_view.size > 0) { + customWebViewCallback?.onCustomViewHidden() + return true + } else if (web_view.copyBackForwardList().size > 1) { web_view.goBack() return true } @@ -203,6 +262,24 @@ class ReadRssActivity : VMBaseActivity(R.layout.activity_rss_r } } + override fun onFilePicked(requestCode: Int, currentPath: String) { + when (requestCode) { + savePathRequestCode -> { + ACache.get(this).put(imagePathKey, currentPath) + viewModel.saveImage(webPic, currentPath) + } + } + } + + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + super.onActivityResult(requestCode, resultCode, data) + when (requestCode) { + savePathRequestCode -> data?.data?.let { + onFilePicked(requestCode, it.toString()) + } + } + } + override fun onDestroy() { super.onDestroy() web_view.destroy() diff --git a/app/src/main/java/io/legado/app/ui/rss/read/ReadRssViewModel.kt b/app/src/main/java/io/legado/app/ui/rss/read/ReadRssViewModel.kt index 17b66f41c..6d90fd181 100644 --- a/app/src/main/java/io/legado/app/ui/rss/read/ReadRssViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/rss/read/ReadRssViewModel.kt @@ -2,19 +2,31 @@ package io.legado.app.ui.rss.read import android.app.Application import android.content.Intent +import android.net.Uri import android.speech.tts.TextToSpeech import android.speech.tts.UtteranceProgressListener +import android.util.Base64 +import android.webkit.URLUtil +import androidx.documentfile.provider.DocumentFile import androidx.lifecycle.MutableLiveData import io.legado.app.App import io.legado.app.R import io.legado.app.base.BaseViewModel +import io.legado.app.constant.AppConst import io.legado.app.data.entities.RssArticle import io.legado.app.data.entities.RssSource +import io.legado.app.help.http.HttpHelper import io.legado.app.model.Rss import io.legado.app.model.analyzeRule.AnalyzeUrl +import io.legado.app.utils.DocumentUtils +import io.legado.app.utils.FileUtils +import io.legado.app.utils.isContentPath +import io.legado.app.utils.writeBytes import kotlinx.coroutines.launch +import java.io.File import java.util.* + class ReadRssViewModel(application: Application) : BaseViewModel(application), TextToSpeech.OnInitListener { var callBack: CallBack? = null @@ -85,6 +97,37 @@ class ReadRssViewModel(application: Application) : BaseViewModel(application), } } + fun saveImage(webPic: String?, path: String) { + webPic ?: return + execute { + val fileName = "${AppConst.fileNameFormat.format(Date(System.currentTimeMillis()))}.jpg" + webData2bitmap(webPic)?.let { biteArray -> + if (path.isContentPath()) { + val uri = Uri.parse(path) + DocumentFile.fromTreeUri(context, uri)?.let { doc -> + DocumentUtils.createFileIfNotExist(doc, fileName) + ?.writeBytes(context, biteArray) + } + } else { + val file = FileUtils.createFileIfNotExist(File(path), fileName) + file.writeBytes(biteArray) + } + } ?: throw Throwable("NULL") + }.onError { + toast("保存图片失败:${it.localizedMessage}") + }.onSuccess { + toast("保存成功") + } + } + + private suspend fun webData2bitmap(data: String): ByteArray? { + return if (URLUtil.isValidUrl(data)) { + HttpHelper.simpleGetByteAsync(data) + } else { + Base64.decode(data.split(",").toTypedArray()[1], Base64.DEFAULT) + } + } + fun clHtml(content: String): String { return if (content.contains("
书名:${book.name}