Merge branch 'master' of github.com:gedoor/legado

pull/401/head
fisher 4 years ago
commit 5d683ff1f2
  1. 13
      app/src/main/assets/updateLog.md
  2. 2
      app/src/main/assets/web/new/css/detail.ef664f74.css
  3. 4
      app/src/main/assets/web/new/index.html
  4. 0
      app/src/main/assets/web/new/js/about.b239b102.js
  5. 2
      app/src/main/assets/web/new/js/app.f319fc97.js
  6. 1
      app/src/main/assets/web/new/js/detail.06210052.js
  7. 1
      app/src/main/assets/web/new/js/detail.11777eca.js
  8. 22
      app/src/main/assets/web/new/precache-manifest.64ecbb9cbd290bcd22476b715a2e6c05.js
  9. 2
      app/src/main/assets/web/new/service-worker.js
  10. 88
      app/src/main/java/io/legado/app/help/JsExtensions.kt
  11. 18
      app/src/main/java/io/legado/app/help/http/HttpHelper.kt
  12. 27
      app/src/main/java/io/legado/app/lib/theme/ThemeStore.kt
  13. 49
      app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeUrl.kt
  14. 4
      app/src/main/java/io/legado/app/model/rss/RssParser.kt
  15. 27
      app/src/main/java/io/legado/app/ui/main/MainActivity.kt
  16. 5
      app/src/main/java/io/legado/app/ui/widget/SelectActionBar.kt
  17. 78
      app/src/main/java/io/legado/app/ui/widget/recycler/scroller/FastScrollRecyclerView.kt
  18. 14
      app/src/main/java/io/legado/app/ui/widget/recycler/scroller/FastScroller.kt
  19. 27
      app/src/main/java/io/legado/app/utils/StringUtils.kt
  20. 15
      app/src/main/res/layout/activity_main.xml
  21. 12
      app/src/main/res/layout/dialog_recycler_view.xml

@ -3,6 +3,19 @@
* 关注合作公众号 **[小说拾遗]()** 获取好看的小说。
- 旧版数据导入教程:先在旧版阅读(2.x)中进行备份,然后在新版阅读(3.x)【我的】->【备份与恢复】,选择【导入旧版本数据】。
**2020/09/29**
* 增加了几个方法用于处理文件 by [Celeter](https://github.com/Celeter)
```
//文件下载,content为十六进制字符串,url用于生成文件名,返回文件路径
downloadFile(content: String, url: String): String
//文件解压,zipPath为压缩文件路径,返回解压路径
unzipFile(zipPath: String): String
//文件夹内所有文件读取
getTxtInFolder(unzipPath: String): String
```
* 增加type字段,返回16进制字符串,例:`https://www.baidu.com,{"type":"zip"}`
* 底部操作栏阴影跟随设置调节
**2020/09/24**
* 修复规则解析bug

@ -1,3 +1,3 @@
<!DOCTYPE html><html lang=en style="padding: 0;height:100%"><head><meta charset=utf-8><meta http-equiv=X-UA-Compatible content="IE=edge"><meta name=viewport content="width=device-width,initial-scale=1"><!--[if IE]><link rel="icon" href="favicon.ico" /><![endif]--><title>Legado Bookshelf</title><link href=css/about.dbe575e1.css rel=prefetch><link href=css/detail.9ba76c69.css rel=prefetch><link href=js/about.59a63964.js rel=prefetch><link href=js/about~detail.1caf6ef5.js rel=prefetch><link href=js/detail.11777eca.js rel=prefetch><link href=css/app.e4c919b7.css rel=preload as=style><link href=css/chunk-vendors.ad4ff18f.css rel=preload as=style><link href=js/app.d7843716.js rel=preload as=script><link href=js/chunk-vendors.8dd9045a.js rel=preload as=script><link href=css/chunk-vendors.ad4ff18f.css rel=stylesheet><link href=css/app.e4c919b7.css rel=stylesheet><link rel=icon type=image/png sizes=32x32 href=img/icons/favicon-32x32.png><link rel=icon type=image/png sizes=16x16 href=img/icons/favicon-16x16.png><link rel=manifest href=manifest.json><meta name=theme-color content=#4DBA87><meta name=apple-mobile-web-app-capable content=no><meta name=apple-mobile-web-app-status-bar-style content=default><meta name=apple-mobile-web-app-title content=yd-web-tool><link rel=apple-touch-icon href=img/icons/apple-touch-icon-152x152.png><link rel=mask-icon href=img/icons/safari-pinned-tab.svg color=#4DBA87><meta name=msapplication-TileImage content=img/icons/msapplication-icon-144x144.png><meta name=msapplication-TileColor content=#000000></head><style>body::-webkit-scrollbar {
<!DOCTYPE html><html lang=en style="padding: 0;height:100%"><head><meta charset=utf-8><meta http-equiv=X-UA-Compatible content="IE=edge"><meta name=viewport content="width=device-width,initial-scale=1"><!--[if IE]><link rel="icon" href="favicon.ico" /><![endif]--><title>Legado Bookshelf</title><link href=css/about.dbe575e1.css rel=prefetch><link href=css/detail.ef664f74.css rel=prefetch><link href=js/about.b239b102.js rel=prefetch><link href=js/about~detail.1caf6ef5.js rel=prefetch><link href=js/detail.06210052.js rel=prefetch><link href=css/app.e4c919b7.css rel=preload as=style><link href=css/chunk-vendors.ad4ff18f.css rel=preload as=style><link href=js/app.f319fc97.js rel=preload as=script><link href=js/chunk-vendors.8dd9045a.js rel=preload as=script><link href=css/chunk-vendors.ad4ff18f.css rel=stylesheet><link href=css/app.e4c919b7.css rel=stylesheet><link rel=icon type=image/png sizes=32x32 href=img/icons/favicon-32x32.png><link rel=icon type=image/png sizes=16x16 href=img/icons/favicon-16x16.png><link rel=manifest href=manifest.json><meta name=theme-color content=#4DBA87><meta name=apple-mobile-web-app-capable content=no><meta name=apple-mobile-web-app-status-bar-style content=default><meta name=apple-mobile-web-app-title content=yd-web-tool><link rel=apple-touch-icon href=img/icons/apple-touch-icon-152x152.png><link rel=mask-icon href=img/icons/safari-pinned-tab.svg color=#4DBA87><meta name=msapplication-TileImage content=img/icons/msapplication-icon-144x144.png><meta name=msapplication-TileColor content=#000000></head><style>body::-webkit-scrollbar {
display: none;
}</style><body style="margin: 0;height:100%"><noscript><strong>We're sorry but yd-web-tool doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id=app></div><script src=js/chunk-vendors.8dd9045a.js></script><script src=js/app.d7843716.js></script></body></html>
}</style><body style="margin: 0;height:100%"><noscript><strong>We're sorry but yd-web-tool doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id=app></div><script src=js/chunk-vendors.8dd9045a.js></script><script src=js/app.f319fc97.js></script></body></html>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -1,10 +1,10 @@
self.__precacheManifest = (self.__precacheManifest || []).concat([
{
"revision": "a77097c019b699bc81ee",
"revision": "1f198e66ea99b77b82a1",
"url": "css/about.dbe575e1.css"
},
{
"revision": "4d729c4b428d537ebd8d",
"revision": "e82f63018b8756547377",
"url": "css/app.e4c919b7.css"
},
{
@ -12,8 +12,8 @@ self.__precacheManifest = (self.__precacheManifest || []).concat([
"url": "css/chunk-vendors.ad4ff18f.css"
},
{
"revision": "8f2124417070a994ebbd",
"url": "css/detail.9ba76c69.css"
"revision": "f2d5bf5a45e7429367f8",
"url": "css/detail.ef664f74.css"
},
{
"revision": "535877f50039c0cb49a6196a5b7517cd",
@ -40,28 +40,28 @@ self.__precacheManifest = (self.__precacheManifest || []).concat([
"url": "img/noCover.b5c48bc1.jpeg"
},
{
"revision": "ad9f43586bb9220e0df71ce8fad92d8b",
"revision": "48e76635babe1b6abdaface8af25dd98",
"url": "index.html"
},
{
"revision": "a77097c019b699bc81ee",
"url": "js/about.59a63964.js"
"revision": "1f198e66ea99b77b82a1",
"url": "js/about.b239b102.js"
},
{
"revision": "2c81bd893f3a92f018d8",
"url": "js/about~detail.1caf6ef5.js"
},
{
"revision": "4d729c4b428d537ebd8d",
"url": "js/app.d7843716.js"
"revision": "e82f63018b8756547377",
"url": "js/app.f319fc97.js"
},
{
"revision": "3e91096748e0f4d6bb89",
"url": "js/chunk-vendors.8dd9045a.js"
},
{
"revision": "8f2124417070a994ebbd",
"url": "js/detail.11777eca.js"
"revision": "f2d5bf5a45e7429367f8",
"url": "js/detail.06210052.js"
},
{
"revision": "b46d04eb43bc31ca0f9f95121646440d",

@ -14,7 +14,7 @@
importScripts("https://storage.googleapis.com/workbox-cdn/releases/4.3.1/workbox-sw.js");
importScripts(
"precache-manifest.78eb8adcb8f052b2a72d462abe0dc498.js"
"precache-manifest.64ecbb9cbd290bcd22476b715a2e6c05.js"
);
workbox.core.setCacheNameDetails({prefix: "yd-web-tool"});

@ -3,12 +3,14 @@ package io.legado.app.help
import android.util.Base64
import androidx.annotation.Keep
import io.legado.app.constant.AppConst.dateFormat
import io.legado.app.help.http.SSLHelper
import io.legado.app.model.analyzeRule.AnalyzeUrl
import io.legado.app.utils.EncoderUtils
import io.legado.app.utils.MD5Utils
import io.legado.app.utils.htmlFormat
import io.legado.app.utils.msg
import io.legado.app.utils.*
import io.legado.app.utils.EncodingDetect
import org.jsoup.Connection
import org.jsoup.Jsoup
import java.net.URLEncoder
import java.nio.charset.Charset
import java.util.*
@Keep
@ -40,6 +42,84 @@ interface JsExtensions {
}
}
/**
* js实现文件下载
*/
fun downloadFile(content: String, url: String): String {
val zipPath = FileUtils.getPath(
FileUtils.createFolderIfNotExist(FileUtils.getCachePath()),
"${MD5Utils.md5Encode16(url)}.zip"
)
FileUtils.deleteFile(zipPath)
val zipFile = FileUtils.createFileIfNotExist(zipPath)
StringUtils.hexStringToByte(content).let {
if (it != null) {
zipFile.writeBytes(it)
}
}
return zipPath
}
/**
* js实现压缩文件解压
*/
fun unzipFile(zipPath: String): String {
val unzipPath = FileUtils.getPath(
FileUtils.createFolderIfNotExist(FileUtils.getCachePath()),
FileUtils.getNameExcludeExtension(zipPath)
)
FileUtils.deleteFile(unzipPath)
val zipFile = FileUtils.createFileIfNotExist(zipPath)
val unzipFolder = FileUtils.createFolderIfNotExist(unzipPath)
ZipUtils.unzipFile(zipFile, unzipFolder)
FileUtils.deleteFile(zipPath)
return unzipPath
}
/**
* js实现文件夹内所有文件读取
*/
fun getTxtInFolder(unzipPath: String): String {
val unzipFolder = FileUtils.createFolderIfNotExist(unzipPath)
val contents = StringBuilder()
unzipFolder.listFiles().let {
if (it != null) {
for (f in it) {
val charsetName = EncodingDetect.getEncode(f)
contents.append(String(f.readBytes(), Charset.forName(charsetName)))
.append("\n")
}
contents.deleteCharAt(contents.length - 1)
}
}
FileUtils.deleteFile(unzipPath)
return contents.toString()
}
/**
* js实现重定向拦截,不能删
*/
fun get(urlStr: String, headers: Map<String, String>): Connection.Response {
return Jsoup.connect(urlStr)
.sslSocketFactory(SSLHelper.unsafeSSLSocketFactory)
.ignoreContentType(true)
.followRedirects(false)
.headers(headers)
.method(Connection.Method.GET)
.execute()
}
fun post(urlStr: String, body: String, headers: Map<String, String>): Connection.Response {
return Jsoup.connect(urlStr)
.sslSocketFactory(SSLHelper.unsafeSSLSocketFactory)
.ignoreContentType(true)
.followRedirects(false)
.requestBody(body)
.headers(headers)
.method(Connection.Method.POST)
.execute()
}
/**
* js实现解码,不能删
*/

@ -48,7 +48,11 @@ object HttpHelper {
return null
}
fun getBytes(url: String, queryMap: Map<String, String>, headers: Map<String, String>): ByteArray? {
fun getBytes(
url: String,
queryMap: Map<String, String>,
headers: Map<String, String>
): ByteArray? {
NetworkUtils.getBaseUrl(url)?.let { baseUrl ->
return getByteRetrofit(baseUrl)
.create(HttpGetApi::class.java)
@ -78,16 +82,16 @@ object HttpHelper {
return null
}
inline fun <reified T> getApiService(baseUrl: String, encode: String? = null): T {
return getRetrofit(baseUrl, encode).create(T::class.java)
}
inline fun <reified T> getApiServiceWithProxy(
inline fun <reified T> getApiService(
baseUrl: String,
encode: String? = null,
proxy: String? = null
): T {
return getRetrofitWithProxy(baseUrl, encode, proxy).create(T::class.java)
return if (proxy.isNullOrEmpty()) {
getRetrofit(baseUrl, encode).create(T::class.java)
} else {
getRetrofitWithProxy(baseUrl, encode, proxy).create(T::class.java)
}
}
inline fun <reified T> getBytesApiService(baseUrl: String): T {

@ -187,7 +187,10 @@ private constructor(private val mContext: Context) : ThemeStoreInterface {
@CheckResult
internal fun prefs(context: Context): SharedPreferences {
return context.getSharedPreferences(ThemeStorePrefKeys.CONFIG_PREFS_KEY_DEFAULT, Context.MODE_PRIVATE)
return context.getSharedPreferences(
ThemeStorePrefKeys.CONFIG_PREFS_KEY_DEFAULT,
Context.MODE_PRIVATE
)
}
fun markChanged(context: Context) {
@ -227,9 +230,15 @@ private constructor(private val mContext: Context) : ThemeStoreInterface {
return if (!coloredStatusBar(context)) {
Color.BLACK
} else if (transparent) {
prefs(context).getInt(ThemeStorePrefKeys.KEY_STATUS_BAR_COLOR, primaryColor(context))
prefs(context).getInt(
ThemeStorePrefKeys.KEY_STATUS_BAR_COLOR,
primaryColor(context)
)
} else {
prefs(context).getInt(ThemeStorePrefKeys.KEY_STATUS_BAR_COLOR, primaryColorDark(context))
prefs(context).getInt(
ThemeStorePrefKeys.KEY_STATUS_BAR_COLOR,
primaryColorDark(context)
)
}
}
@ -289,11 +298,16 @@ private constructor(private val mContext: Context) : ThemeStoreInterface {
)
}
@SuppressLint("PrivateResource")
@CheckResult
fun elevation(context: Context): Float {
return prefs(context).getFloat(
ThemeStorePrefKeys.KEY_ELEVATION,
ATHUtils.resolveFloat(context, android.R.attr.elevation, context.resources.getDimension(R.dimen.design_appbar_elevation))
ATHUtils.resolveFloat(
context,
android.R.attr.elevation,
context.resources.getDimension(R.dimen.design_appbar_elevation)
)
)
}
@ -308,7 +322,10 @@ private constructor(private val mContext: Context) : ThemeStoreInterface {
@CheckResult
fun coloredStatusBar(context: Context): Boolean {
return prefs(context).getBoolean(ThemeStorePrefKeys.KEY_APPLY_PRIMARYDARK_STATUSBAR, true)
return prefs(context).getBoolean(
ThemeStorePrefKeys.KEY_APPLY_PRIMARYDARK_STATUSBAR,
true
)
}
@CheckResult

@ -60,6 +60,7 @@ class AnalyzeUrl(
private var method = RequestMethod.GET
private val splitUrlRegex = Regex(",\\s*(?=\\{)")
private var proxy: String? = null
private var type: String? = null
init {
baseUrl?.let {
@ -191,6 +192,7 @@ class AnalyzeUrl(
val option = GSON.fromJsonObject<UrlOption>(urlArray[1])
option?.let { _ ->
option.method?.let { if (it.equals("POST", true)) method = RequestMethod.POST }
option.type?.let { type = it }
option.headers?.let { headers ->
if (headers is Map<*, *>) {
headers.forEach { entry ->
@ -304,19 +306,19 @@ class AnalyzeUrl(
method == RequestMethod.POST -> {
if (fieldMap.isNotEmpty()) {
HttpHelper
.getApiService<HttpPostApi>(baseUrl, charset)
.getApiService<HttpPostApi>(baseUrl, charset, proxy)
.postMap(url, fieldMap, headerMap)
} else {
HttpHelper
.getApiService<HttpPostApi>(baseUrl, charset)
.getApiService<HttpPostApi>(baseUrl, charset, proxy)
.postBody(url, requestBody!!, headerMap)
}
}
fieldMap.isEmpty() -> HttpHelper
.getApiService<HttpGetApi>(baseUrl, charset)
.getApiService<HttpGetApi>(baseUrl, charset, proxy)
.get(url, headerMap)
else -> HttpHelper
.getApiService<HttpGetApi>(baseUrl, charset)
.getApiService<HttpGetApi>(baseUrl, charset, proxy)
.getMap(url, fieldMap, headerMap)
}
}
@ -326,6 +328,9 @@ class AnalyzeUrl(
jsStr: String? = null,
sourceRegex: String? = null,
): Res {
if (type != null) {
return Res(url, StringUtils.byteToHexString(getResponseBytes(tag)))
}
if (useWebView) {
val params = AjaxWebView.AjaxParams(url)
params.headerMap = headerMap
@ -343,51 +348,24 @@ class AnalyzeUrl(
val res = when {
method == RequestMethod.POST -> {
if (fieldMap.isNotEmpty()) {
if (proxy == null) {
HttpHelper
.getApiService<HttpPostApi>(baseUrl, charset)
.getApiService<HttpPostApi>(baseUrl, charset, proxy)
.postMapAsync(url, fieldMap, headerMap)
} else {
HttpHelper
.getApiServiceWithProxy<HttpPostApi>(baseUrl, charset, proxy)
.postMapAsync(url, fieldMap, headerMap)
}
} else {
if (proxy == null) {
HttpHelper
.getApiService<HttpPostApi>(baseUrl, charset)
.postBodyAsync(url, requestBody!!, headerMap)
} else {
HttpHelper
.getApiServiceWithProxy<HttpPostApi>(baseUrl, charset, proxy)
.getApiService<HttpPostApi>(baseUrl, charset, proxy)
.postBodyAsync(url, requestBody!!, headerMap)
}
}
}
fieldMap.isEmpty() -> {
if (proxy == null) {
HttpHelper
.getApiService<HttpGetApi>(baseUrl, charset)
.getAsync(url, headerMap)
} else {
HttpHelper
.getApiServiceWithProxy<HttpGetApi>(baseUrl, charset, proxy)
.getApiService<HttpGetApi>(baseUrl, charset, proxy)
.getAsync(url, headerMap)
}
}
else -> {
if (proxy == null) {
HttpHelper
.getApiService<HttpGetApi>(baseUrl, charset)
.getApiService<HttpGetApi>(baseUrl, charset, proxy)
.getMapAsync(url, fieldMap, headerMap)
} else {
HttpHelper
.getApiServiceWithProxy<HttpGetApi>(baseUrl, charset, proxy)
.getMapAsync(url, fieldMap, headerMap)
}
}
}
return Res(NetworkUtils.getUrl(res), res.body())
@ -448,6 +426,7 @@ class AnalyzeUrl(
val webView: Any?,
val headers: Any?,
val body: Any?,
val type: String?
)
}

@ -117,11 +117,11 @@ object RssParser {
private fun getImageUrl(input: String): String? {
var url: String? = null
val patternImg = "(<img .*?>)".toPattern()
val patternImg = "(<img [^>]*>)".toPattern()
val matcherImg = patternImg.matcher(input)
if (matcherImg.find()) {
val imgTag = matcherImg.group(1)
val patternLink = "src\\s*=\\s*\"(.+?)\"".toPattern()
val patternLink = "src\\s*=\\s*\"([^\"]+)\"".toPattern()
val matcherLink = patternLink.matcher(imgTag!!)
if (matcherLink.find()) {
url = matcherLink.group(1)!!.trim()

@ -19,6 +19,7 @@ import io.legado.app.help.AppConfig
import io.legado.app.help.BookHelp
import io.legado.app.help.storage.Backup
import io.legado.app.lib.theme.ATH
import io.legado.app.lib.theme.elevation
import io.legado.app.service.BaseReadAloudService
import io.legado.app.ui.main.bookshelf.BookshelfFragment
import io.legado.app.ui.main.explore.ExploreFragment
@ -47,6 +48,8 @@ class MainActivity : VMBaseActivity<MainViewModel>(R.layout.activity_main),
view_pager_main.offscreenPageLimit = 3
view_pager_main.adapter = TabFragmentPageAdapter(supportFragmentManager)
view_pager_main.addOnPageChangeListener(this)
bottom_navigation_view.elevation =
if (AppConfig.elevation < 0) elevation else AppConfig.elevation.toFloat()
bottom_navigation_view.setOnNavigationItemSelectedListener(this)
bottom_navigation_view.setOnNavigationItemReselectedListener(this)
bottom_navigation_view.menu.findItem(R.id.menu_rss).isVisible = AppConfig.isShowRSS
@ -167,28 +170,32 @@ class MainActivity : VMBaseActivity<MainViewModel>(R.layout.activity_main),
private inner class TabFragmentPageAdapter(fm: FragmentManager) :
FragmentStatePagerAdapter(fm, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
private fun getBookshelfFragment(): Fragment {
val bookshelfFragment: Fragment
get() {
if (!fragmentMap.containsKey(0)) {
fragmentMap[0] = BookshelfFragment()
}
return fragmentMap.getValue(0)
}
private fun getExploreFragment(): Fragment {
val exploreFragment: Fragment
get() {
if (!fragmentMap.containsKey(1)) {
fragmentMap[1] = ExploreFragment()
}
return fragmentMap.getValue(1)
}
private fun getRssFragment(): Fragment {
val rssFragment: Fragment
get() {
if (!fragmentMap.containsKey(2)) {
fragmentMap[2] = RssFragment()
}
return fragmentMap.getValue(2)
}
private fun getMyFragment(): Fragment {
val myFragment: Fragment
get() {
if (!fragmentMap.containsKey(3)) {
fragmentMap[3] = MyFragment()
}
@ -201,14 +208,10 @@ class MainActivity : VMBaseActivity<MainViewModel>(R.layout.activity_main),
override fun getItem(position: Int): Fragment {
return when (position) {
0 -> getBookshelfFragment()
1 -> getExploreFragment()
2 -> if (AppConfig.isShowRSS) {
getRssFragment()
} else {
getMyFragment()
}
else -> getMyFragment()
0 -> bookshelfFragment
1 -> exploreFragment
2 -> if (AppConfig.isShowRSS) rssFragment else myFragment
else -> myFragment
}
}

@ -9,9 +9,9 @@ import androidx.annotation.MenuRes
import androidx.annotation.StringRes
import androidx.appcompat.widget.PopupMenu
import io.legado.app.R
import io.legado.app.help.AppConfig
import io.legado.app.lib.theme.*
import io.legado.app.utils.ColorUtils
import io.legado.app.utils.dp
import io.legado.app.utils.visible
import kotlinx.android.synthetic.main.view_select_action_bar.view.*
import org.jetbrains.anko.sdk27.listeners.onClick
@ -22,7 +22,8 @@ class SelectActionBar(context: Context, attrs: AttributeSet?) : FrameLayout(cont
init {
setBackgroundColor(context.bottomBackground)
elevation = 10.dp.toFloat()
elevation =
if (AppConfig.elevation < 0) context.elevation else AppConfig.elevation.toFloat()
View.inflate(context, R.layout.view_select_action_bar, this)
val textIsDark = ColorUtils.isColorLight(context.bottomBackground)
val primaryTextColor = context.getPrimaryTextColor(textIsDark)

@ -9,44 +9,41 @@ import io.legado.app.R
class FastScrollRecyclerView : RecyclerView {
private var mFastScroller: FastScroller? = null
private lateinit var mFastScroller: FastScroller
constructor(context: Context) : super(context) {
layout(context, null)
layoutParams =
LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT)
}
@JvmOverloads
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int = 0) : super(context, attrs, defStyleAttr) {
constructor(
context: Context,
attrs: AttributeSet,
defStyleAttr: Int = 0
) : super(context, attrs, defStyleAttr) {
layout(context, attrs)
}
private fun layout(context: Context, attrs: AttributeSet?) {
mFastScroller = FastScroller(context, attrs)
mFastScroller.id = R.id.fast_scroller
}
override fun setAdapter(adapter: Adapter<*>?) {
super.setAdapter(adapter)
if (adapter is FastScroller.SectionIndexer) {
setSectionIndexer(adapter as FastScroller.SectionIndexer?)
} else if (adapter == null) {
setSectionIndexer(null)
}
}
override fun setVisibility(visibility: Int) {
super.setVisibility(visibility)
mFastScroller?.visibility = visibility
mFastScroller.visibility = visibility
}
@ -56,9 +53,7 @@ class FastScrollRecyclerView : RecyclerView {
* @param sectionIndexer The SectionIndexer that provides section text for the FastScroller
*/
fun setSectionIndexer(sectionIndexer: FastScroller.SectionIndexer?) {
mFastScroller?.setSectionIndexer(sectionIndexer)
mFastScroller.setSectionIndexer(sectionIndexer)
}
@ -68,9 +63,7 @@ class FastScrollRecyclerView : RecyclerView {
* @param enabled True to enable fast scrolling, false otherwise
*/
fun setFastScrollEnabled(enabled: Boolean) {
mFastScroller!!.isEnabled = enabled
mFastScroller.isEnabled = enabled
}
@ -80,9 +73,7 @@ class FastScrollRecyclerView : RecyclerView {
* @param hideScrollbar True to hide the scrollbar, false to show
*/
fun setHideScrollbar(hideScrollbar: Boolean) {
mFastScroller?.setFadeScrollbar(hideScrollbar)
mFastScroller.setFadeScrollbar(hideScrollbar)
}
/**
@ -91,9 +82,7 @@ class FastScrollRecyclerView : RecyclerView {
* @param visible True to show scroll track, false to hide
*/
fun setTrackVisible(visible: Boolean) {
mFastScroller?.setTrackVisible(visible)
mFastScroller.setTrackVisible(visible)
}
/**
@ -102,9 +91,7 @@ class FastScrollRecyclerView : RecyclerView {
* @param color The color for the scroll track
*/
fun setTrackColor(@ColorInt color: Int) {
mFastScroller?.setTrackColor(color)
mFastScroller.setTrackColor(color)
}
@ -114,9 +101,7 @@ class FastScrollRecyclerView : RecyclerView {
* @param color The color for the scroll handle
*/
fun setHandleColor(@ColorInt color: Int) {
mFastScroller?.setHandleColor(color)
mFastScroller.setHandleColor(color)
}
@ -126,9 +111,7 @@ class FastScrollRecyclerView : RecyclerView {
* @param visible True to show the bubble, false to hide
*/
fun setBubbleVisible(visible: Boolean) {
mFastScroller?.setBubbleVisible(visible)
mFastScroller.setBubbleVisible(visible)
}
@ -138,9 +121,7 @@ class FastScrollRecyclerView : RecyclerView {
* @param color The background color for the index bubble
*/
fun setBubbleColor(@ColorInt color: Int) {
mFastScroller?.setBubbleColor(color)
mFastScroller.setBubbleColor(color)
}
@ -150,7 +131,7 @@ class FastScrollRecyclerView : RecyclerView {
* @param color The text color for the index bubble
*/
fun setBubbleTextColor(@ColorInt color: Int) {
mFastScroller?.setBubbleTextColor(color)
mFastScroller.setBubbleTextColor(color)
}
@ -160,35 +141,24 @@ class FastScrollRecyclerView : RecyclerView {
* @param fastScrollStateChangeListener The interface that will listen to fastscroll state change events
*/
fun setFastScrollStateChangeListener(fastScrollStateChangeListener: FastScrollStateChangeListener) {
mFastScroller?.setFastScrollStateChangeListener(fastScrollStateChangeListener)
mFastScroller.setFastScrollStateChangeListener(fastScrollStateChangeListener)
}
override fun onAttachedToWindow() {
super.onAttachedToWindow()
mFastScroller?.attachRecyclerView(this)
mFastScroller.attachRecyclerView(this)
val parent = parent
if (parent is ViewGroup) {
parent.addView(mFastScroller)
mFastScroller?.setLayoutParams(parent)
mFastScroller.setLayoutParams(parent)
}
}
override fun onDetachedFromWindow() {
mFastScroller?.detachRecyclerView()
mFastScroller.detachRecyclerView()
super.onDetachedFromWindow()
}
private fun layout(context: Context, attrs: AttributeSet?) {
mFastScroller = FastScroller(context, attrs)
mFastScroller?.id = R.id.fast_scroller
}
}

@ -173,7 +173,7 @@ class FastScroller : LinearLayout {
*/
fun setFadeScrollbar(fadeScrollbar: Boolean) {
mFadeScrollbar = fadeScrollbar
mScrollbar.visibility = if (fadeScrollbar) View.GONE else View.VISIBLE
mScrollbar.visibility = if (fadeScrollbar) View.INVISIBLE else View.VISIBLE
}
/**
@ -191,7 +191,7 @@ class FastScroller : LinearLayout {
* @param visible True to show scroll track, false to hide
*/
fun setTrackVisible(visible: Boolean) {
mTrackView.visibility = if (visible) View.VISIBLE else View.GONE
mTrackView.visibility = if (visible) View.VISIBLE else View.INVISIBLE
}
/**
@ -264,7 +264,7 @@ class FastScroller : LinearLayout {
override fun setEnabled(enabled: Boolean) {
super.setEnabled(enabled)
visibility = if (enabled) View.VISIBLE else View.GONE
visibility = if (enabled) View.VISIBLE else View.INVISIBLE
}
@SuppressLint("ClickableViewAccessibility")
@ -411,13 +411,13 @@ class FastScroller : LinearLayout {
.setListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator) {
super.onAnimationEnd(animation)
mBubbleView.visibility = View.GONE
mBubbleView.visibility = View.INVISIBLE
mBubbleAnimator = null
}
override fun onAnimationCancel(animation: Animator) {
super.onAnimationCancel(animation)
mBubbleView.visibility = View.GONE
mBubbleView.visibility = View.INVISIBLE
mBubbleAnimator = null
}
})
@ -447,13 +447,13 @@ class FastScroller : LinearLayout {
.setListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator) {
super.onAnimationEnd(animation)
mScrollbar.visibility = View.GONE
mScrollbar.visibility = View.INVISIBLE
mScrollbarAnimator = null
}
override fun onAnimationCancel(animation: Animator) {
super.onAnimationCancel(animation)
mScrollbar.visibility = View.GONE
mScrollbar.visibility = View.INVISIBLE
mScrollbarAnimator = null
}
})

@ -45,6 +45,7 @@ object StringUtils {
//将时间转换成日期
fun dateConvert(time: Long, pattern: String): String {
val date = Date(time)
@SuppressLint("SimpleDateFormat")
val format = SimpleDateFormat(pattern)
return format.format(date)
@ -292,4 +293,30 @@ object StringUtils {
return buf.toString()
}
fun byteToHexString(bytes: ByteArray?): String {
if (bytes == null) return ""
val sb = StringBuilder(bytes.size * 2)
for (b in bytes) {
val hex = 0xff and b.toInt()
if (hex < 16) {
sb.append('0')
}
sb.append(Integer.toHexString(hex))
}
return sb.toString()
}
fun hexStringToByte(hexString: String): ByteArray? {
val hexStr = hexString.replace(" ", "")
val len = hexStr.length
val bytes = ByteArray(len / 2)
var i = 0
while (i < len) {
// 两位一组,表示一个字节,把这样表示的16进制字符串,还原成一个字节
bytes[i / 2] = ((Character.digit(hexString[i], 16) shl 4) +
Character.digit(hexString[i + 1], 16)).toByte()
i += 2
}
return bytes
}
}

@ -4,21 +4,20 @@
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.viewpager.widget.ViewPager
android:id="@+id/view_pager_main"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintBottom_toTopOf="@+id/bottom_navigation_view"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="@+id/bottom_navigation_view"
android:layout_width="match_parent"
android:layout_height="50dp"
android:elevation="10dp"
android:background="@color/background"
app:labelVisibilityMode="unlabeled"
app:menu="@menu/main_bnv"
app:layout_constraintBottom_toBottomOf="parent" />
<androidx.viewpager.widget.ViewPager
android:id="@+id/view_pager_main"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintBottom_toTopOf="@+id/bottom_navigation_view"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

@ -14,11 +14,17 @@
app:titleTextAppearance="@style/ToolbarTitle"
app:popupTheme="@style/AppTheme.PopupOverlay" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_view"
<FrameLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
android:layout_weight="1">
<io.legado.app.ui.widget.recycler.scroller.FastScrollRecyclerView
android:id="@+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</FrameLayout>
<LinearLayout
android:layout_width="match_parent"

Loading…
Cancel
Save