Merge pull request #11 from gedoor/master

Sys code
pull/981/head
ag2s20150909 4 years ago committed by GitHub
commit 9d6cd71128
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 8
      app/build.gradle
  2. 25
      app/src/main/assets/updateLog.md
  3. 9
      app/src/main/java/io/legado/app/base/BaseActivity.kt
  4. 6
      app/src/main/java/io/legado/app/base/VMBaseActivity.kt
  5. 1
      app/src/main/java/io/legado/app/constant/PreferKey.kt
  6. 4
      app/src/main/java/io/legado/app/data/dao/BookSourceDao.kt
  7. 6
      app/src/main/java/io/legado/app/data/dao/BookmarkDao.kt
  8. 7
      app/src/main/java/io/legado/app/data/dao/SearchBookDao.kt
  9. 6
      app/src/main/java/io/legado/app/help/AppConfig.kt
  10. 4
      app/src/main/java/io/legado/app/help/http/OkHttpUtils.kt
  11. 8
      app/src/main/java/io/legado/app/lib/theme/ATH.kt
  12. 43
      app/src/main/java/io/legado/app/lib/webdav/WebDav.kt
  13. 7
      app/src/main/java/io/legado/app/model/analyzeRule/QueryTTF.java
  14. 12
      app/src/main/java/io/legado/app/service/help/ReadBook.kt
  15. 46
      app/src/main/java/io/legado/app/ui/book/cache/CacheActivity.kt
  16. 2
      app/src/main/java/io/legado/app/ui/book/read/ReadBookBaseActivity.kt
  17. 5
      app/src/main/java/io/legado/app/ui/book/read/page/ContentTextView.kt
  18. 9
      app/src/main/java/io/legado/app/ui/book/read/page/PageView.kt
  19. 4
      app/src/main/java/io/legado/app/ui/book/read/page/ReadView.kt
  20. 4
      app/src/main/java/io/legado/app/ui/book/read/page/delegate/HorizontalPageDelegate.kt
  21. 4
      app/src/main/java/io/legado/app/ui/book/read/page/delegate/PageDelegate.kt
  22. 62
      app/src/main/java/io/legado/app/ui/book/toc/BookmarkAdapter.kt
  23. 24
      app/src/main/java/io/legado/app/ui/book/toc/BookmarkFragment.kt
  24. 82
      app/src/main/java/io/legado/app/ui/main/MainActivity.kt
  25. 4
      app/src/main/java/io/legado/app/ui/main/MainViewModel.kt
  26. 45
      app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfFragment.kt
  27. 82
      app/src/main/java/io/legado/app/ui/main/bookshelf/RootView.kt
  28. 8
      app/src/main/java/io/legado/app/ui/main/bookshelf/books/BooksAdapterGrid.kt
  29. 8
      app/src/main/java/io/legado/app/ui/main/bookshelf/books/BooksAdapterList.kt
  30. 11
      app/src/main/java/io/legado/app/ui/rss/article/RssSortActivity.kt
  31. 4
      app/src/main/java/io/legado/app/ui/widget/recycler/ItemTouchCallback.kt
  32. 41
      app/src/main/java/io/legado/app/ui/widget/recycler/RecyclerViewAtViewPager2.kt
  33. 2
      app/src/main/res/layout/activity_main.xml
  34. 10
      app/src/main/res/layout/dialog_bookshelf_config.xml
  35. 10
      app/src/main/res/layout/fragment_bookshelf.xml
  36. 27
      app/src/main/res/layout/item_bookshelf_grid.xml
  37. 9
      app/src/main/res/layout/item_bookshelf_list.xml
  38. 5
      app/src/main/res/menu/book_cache.xml
  39. 3
      app/src/main/res/values-zh-rHK/strings.xml
  40. 3
      app/src/main/res/values-zh-rTW/strings.xml
  41. 3
      app/src/main/res/values-zh/strings.xml
  42. 3
      app/src/main/res/values/strings.xml
  43. 3
      build.gradle

@ -97,6 +97,9 @@ android {
jvmTarget = "1.8"
}
buildToolsVersion '30.0.3'
// tasks.withType(JavaCompile) {
// options.compilerArgs << "-Xlint:unchecked"
// }
}
resourcePlaceholders {
@ -149,8 +152,6 @@ dependencies {
//lifecycle
def lifecycle_version = '2.3.1'
//2.3.0 lifecycle-extensions
//implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version"
implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version"
//room
@ -159,9 +160,6 @@ dependencies {
kapt "androidx.room:room-compiler:$room_version"
testImplementation "androidx.room:room-testing:$room_version"
//paging
implementation 'androidx.paging:paging-runtime-ktx:2.1.2'
//liveEventBus
implementation 'com.jeremyliao:live-event-bus-x:1.5.7'

@ -3,7 +3,30 @@
* 关注合作公众号 **[小说拾遗]** 获取好看的小说。
* 旧版数据导入教程:先在旧版阅读(2.x)中进行备份,然后在新版阅读(3.x)【我的】->【备份与恢复】,选择【导入旧版本数据】。
**2021/05/04**
**2021/05/12**
* 修复预下载bug
* 添加显示未读开关,书架布局中
**2021/05/11**
* 修复部分webDav bug
* 修复图片太大时会崩溃的bug
* 修复其它一些bug
**2021/05/10**
* 修复书签bug
* 修改弃用类ViewPager
**2021/05/09**
* 网页访问404依然返回body
* 添加书籍批量导出
**2021/05/08**
* 预下载章节可调整数目
* 修复低版本Android使用TTS闪退。 by ag2s20150909
* 修复WebDav报错
* 优化翻页动画点击翻页
**2021/05/06**
* 修复bug
* url参数添加重置次数,retry
* 修改默认tts, 手动导入

@ -33,7 +33,8 @@ abstract class BaseActivity<VB : ViewBinding>(
val fullScreen: Boolean = true,
private val theme: Theme = Theme.Auto,
private val toolBarTheme: Theme = Theme.Auto,
private val transparent: Boolean = false
private val transparent: Boolean = false,
private val imageBg: Boolean = true
) : AppCompatActivity(),
CoroutineScope by MainScope() {
@ -173,9 +174,13 @@ abstract class BaseActivity<VB : ViewBinding>(
ATH.applyBackgroundTint(window.decorView)
}
}
if (imageBg) {
ThemeConfig.getBgImage(this)?.let {
kotlin.runCatching {
try {
window.decorView.background = it
} catch (e: OutOfMemoryError) {
toastOnUi("Image Bg Out Of Memory")
}
}
}
}

@ -7,8 +7,10 @@ import io.legado.app.constant.Theme
abstract class VMBaseActivity<VB : ViewBinding, VM : ViewModel>(
fullScreen: Boolean = true,
theme: Theme = Theme.Auto,
toolBarTheme: Theme = Theme.Auto
) : BaseActivity<VB>(fullScreen, theme, toolBarTheme) {
toolBarTheme: Theme = Theme.Auto,
transparent: Boolean = false,
imageBg: Boolean = true
) : BaseActivity<VB>(fullScreen, theme, toolBarTheme, transparent, imageBg) {
protected abstract val viewModel: VM

@ -4,6 +4,7 @@ object PreferKey {
const val language = "language"
const val themeMode = "themeMode"
const val userAgent = "userAgent"
const val showUnread = "showUnread"
const val hideStatusBar = "hideStatusBar"
const val clickActionTL = "clickActionTopLeft"
const val clickActionTC = "clickActionTopCenter"

@ -1,7 +1,6 @@
package io.legado.app.data.dao
import androidx.lifecycle.LiveData
import androidx.paging.DataSource
import androidx.room.*
import io.legado.app.data.entities.BookSource
@ -41,9 +40,6 @@ interface BookSourceDao {
@Query("select distinct bookSourceGroup from book_sources where enabledExplore = 1 and trim(exploreUrl) <> '' and trim(bookSourceGroup) <> ''")
fun liveExploreGroup(): LiveData<List<String>>
@Query("select * from book_sources where enabledExplore = 1 order by customOrder asc")
fun observeFind(): DataSource.Factory<Int, BookSource>
@Query("select * from book_sources where bookSourceGroup like '%' || :group || '%'")
fun getByGroup(group: String): List<BookSource>

@ -1,6 +1,6 @@
package io.legado.app.data.dao
import androidx.paging.DataSource
import androidx.lifecycle.LiveData
import androidx.room.*
import io.legado.app.data.entities.Bookmark
@ -16,10 +16,10 @@ interface BookmarkDao {
bookUrl: String,
bookName: String,
bookAuthor: String
): DataSource.Factory<Int, Bookmark>
): LiveData<List<Bookmark>>
@Query("SELECT * FROM bookmarks where bookUrl = :bookUrl and chapterName like '%'||:key||'%' or content like '%'||:key||'%'")
fun liveDataSearch(bookUrl: String, key: String): DataSource.Factory<Int, Bookmark>
fun liveDataSearch(bookUrl: String, key: String): LiveData<List<Bookmark>>
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insert(vararg bookmark: Bookmark)

@ -1,6 +1,5 @@
package io.legado.app.data.dao
import androidx.paging.DataSource
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
@ -10,12 +9,6 @@ import io.legado.app.data.entities.SearchBook
@Dao
interface SearchBookDao {
@Query("SELECT * FROM searchBooks")
fun observeAll(): DataSource.Factory<Int, SearchBook>
@Query("SELECT * FROM searchBooks where time >= :time")
fun observeNew(time: Long): DataSource.Factory<Int, SearchBook>
@Query("select * from searchBooks where bookUrl = :bookUrl")
fun getSearchBook(bookUrl: String): SearchBook?

@ -72,6 +72,12 @@ object AppConfig : SharedPreferences.OnSharedPreferenceChangeListener {
}
}
var showUnread: Boolean
get() = appCtx.getPrefBoolean(PreferKey.showUnread, true)
set(value) {
appCtx.putPrefBoolean(PreferKey.showUnread, value)
}
val isTransparentStatusBar: Boolean
get() = appCtx.getPrefBoolean(PreferKey.transparentStatusBar, true)

@ -28,7 +28,7 @@ suspend fun OkHttpClient.newCall(
return response.body!!
}
}
throw IOException(response!!.message)
return response!!.body ?: throw IOException(response.message)
}
suspend fun OkHttpClient.newCallStrResponse(
@ -45,7 +45,7 @@ suspend fun OkHttpClient.newCallStrResponse(
return StrResponse(response, response.body!!.text())
}
}
throw IOException(response!!.message)
return StrResponse(response!!, response.body?.text() ?: response.message)
}
suspend fun Call.await(): Response = suspendCancellableCoroutine { block ->

@ -16,7 +16,7 @@ import android.widget.ScrollView
import androidx.annotation.ColorInt
import androidx.appcompat.app.AlertDialog
import androidx.recyclerview.widget.RecyclerView
import androidx.viewpager.widget.ViewPager
import androidx.viewpager2.widget.ViewPager2
import com.google.android.material.bottomnavigation.BottomNavigationView
import io.legado.app.R
import io.legado.app.help.AppConfig
@ -219,9 +219,9 @@ object ATH {
}
}
fun setEdgeEffectColor(viewPager: ViewPager?, @ColorInt color: Int) {
fun setEdgeEffectColor(viewPager: ViewPager2?, @ColorInt color: Int) {
try {
val clazz = ViewPager::class.java
val clazz = ViewPager2::class.java
for (name in arrayOf("mLeftEdge", "mRightEdge")) {
val field = clazz.getDeclaredField(name)
field.isAccessible = true
@ -280,7 +280,7 @@ object ATH {
fun applyEdgeEffectColor(view: View?) {
when (view) {
is RecyclerView -> view.edgeEffectFactory = DEFAULT_EFFECT_FACTORY
is ViewPager -> setEdgeEffectColor(view, ThemeStore.primaryColor(view.context))
is ViewPager2 -> setEdgeEffectColor(view, ThemeStore.primaryColor(view.context))
is ScrollView -> setEdgeEffectColor(view, ThemeStore.primaryColor(view.context))
}
}

@ -9,7 +9,6 @@ import okhttp3.RequestBody.Companion.asRequestBody
import okhttp3.RequestBody.Companion.toRequestBody
import org.jsoup.Jsoup
import java.io.File
import java.io.IOException
import java.io.InputStream
import java.net.MalformedURLException
import java.net.URL
@ -58,28 +57,25 @@ class WebDav(urlStr: String) {
* @return 远程文件是否存在
*/
suspend fun indexFileInfo(): Boolean {
propFindResponse(ArrayList())?.let {
if (it.text().isNotEmpty()) {
return true
}
}
return false
return !propFindResponse(ArrayList()).isNullOrEmpty()
}
/**
* 列出当前路径下的文件
*
* @param propsList 指定列出文件的哪些属性
* @return 文件列表
*/
suspend fun listFiles(propsList: ArrayList<String> = ArrayList()): List<WebDav> {
propFindResponse(propsList)?.text()?.let { body ->
suspend fun listFiles(): List<WebDav> {
propFindResponse()?.let { body ->
return parseDir(body)
}
return ArrayList()
}
private suspend fun propFindResponse(propsList: ArrayList<String>): ResponseBody? {
/**
* @param propsList 指定列出文件的哪些属性
*/
private suspend fun propFindResponse(propsList: List<String> = emptyList()): String? {
val requestProps = StringBuilder()
for (p in propsList) {
requestProps.append("<a:").append(p).append("/>\n")
@ -96,11 +92,12 @@ class WebDav(urlStr: String) {
okHttpClient.newCall {
url(url)
addHeader("Authorization", Credentials.basic(auth.user, auth.pass))
addHeader("Depth", "1")
// 添加RequestBody对象,可以只返回的属性。如果设为null,则会返回全部属性
// 注意:尽量手动指定需要返回的属性。若返回全部属性,可能后由于Prop.java里没有该属性名,而崩溃。
val requestBody = requestPropsStr.toRequestBody("text/plain".toMediaType())
method("PROPFIND", requestBody)
}
}.text()
}.onFailure {
it.printStackTrace()
}.getOrNull()
@ -144,19 +141,20 @@ class WebDav(urlStr: String) {
/**
* 根据自己的URL在远程处创建对应的文件夹
*
* @return 是否创建成功
*/
suspend fun makeAsDir(): Boolean {
val url = httpUrl
val auth = HttpAuth.auth
if (url != null && auth != null) {
//防止报错
return kotlin.runCatching {
okHttpClient.newCall {
url(url)
method("MKCOL", null)
addHeader("Authorization", Credentials.basic(auth.user, auth.pass))
}
return true
}.close()
}.isSuccess
}
return false
}
@ -193,12 +191,13 @@ class WebDav(urlStr: String) {
val url = httpUrl
val auth = HttpAuth.auth
if (url != null && auth != null) {
return kotlin.runCatching {
okHttpClient.newCall {
url(url)
put(fileBody)
addHeader("Authorization", Credentials.basic(auth.user, auth.pass))
}
return true
}.close()
}.isSuccess
}
return false
}
@ -209,25 +208,27 @@ class WebDav(urlStr: String) {
val url = httpUrl
val auth = HttpAuth.auth
if (url != null && auth != null) {
return kotlin.runCatching {
okHttpClient.newCall {
url(url)
put(fileBody)
addHeader("Authorization", Credentials.basic(auth.user, auth.pass))
}
return true
}.close()
}.isSuccess
}
return false
}
@Throws(IOException::class)
private suspend fun getInputStream(): InputStream? {
val url = httpUrl
val auth = HttpAuth.auth
if (url != null && auth != null) {
return okHttpClient.newCall {
return kotlin.runCatching {
okHttpClient.newCall {
url(url)
addHeader("Authorization", Credentials.basic(auth.user, auth.pass))
}.byteStream()
}.getOrNull()
}
return null
}

@ -11,7 +11,7 @@ import java.util.LinkedList;
import java.util.List;
import java.util.Map;
@SuppressWarnings({"FieldCanBeLocal", "rawtypes", "StatementWithEmptyBody", "unused"})
@SuppressWarnings({"FieldCanBeLocal", "StatementWithEmptyBody", "unused"})
public class QueryTTF {
private static class Header {
public int majorVersion;
@ -223,7 +223,8 @@ public class QueryTTF {
private final List<Integer> loca = new LinkedList<>();
private final CmapLayout Cmap = new CmapLayout();
private final List<GlyfLayout> glyf = new LinkedList<>();
private final Pair[] pps = new Pair[]{
@SuppressWarnings("unchecked")
private final Pair<Integer, Integer>[] pps = new Pair[]{
Pair.of(3, 10),
Pair.of(0, 4),
Pair.of(3, 1),
@ -515,7 +516,7 @@ public class QueryTTF {
private int getGlyfIndex(int code) {
if (code == 0) return 0;
int fmtKey = 0;
for (@SuppressWarnings("unchecked") Pair<Integer, Integer> item : pps) {
for (Pair<Integer, Integer> item : pps) {
for (CmapRecord record : Cmap.records) {
if ((item.getLeft() == record.platformID) && (item.getRight() == record.encodingID)) {
fmtKey = record.offset;

@ -21,6 +21,7 @@ import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import splitties.init.appCtx
import kotlin.math.max
import kotlin.math.min
@ -141,9 +142,9 @@ object ReadBook {
GlobalScope.launch(Dispatchers.IO) {
val maxChapterIndex =
min(chapterSize - 1, durChapterIndex + AppConfig.preDownloadNum)
for (i in 2..maxChapterIndex) {
for (i in durChapterIndex.plus(2)..maxChapterIndex) {
delay(1000)
download(durChapterIndex + i)
download(i)
}
}
}
@ -171,11 +172,10 @@ object ReadBook {
}
loadContent(durChapterIndex.minus(1), upContent, false)
GlobalScope.launch(Dispatchers.IO) {
val maxChapterIndex =
min(chapterSize - 1, durChapterIndex + AppConfig.preDownloadNum)
for (i in 2..maxChapterIndex) {
val minChapterIndex = max(0, durChapterIndex - 5)
for (i in durChapterIndex.minus(2) downTo minChapterIndex) {
delay(1000)
download(durChapterIndex - i)
download(i)
}
}
}

@ -115,9 +115,13 @@ class CacheActivity : VMBaseActivity<ActivityCacheBookBinding, CacheViewModel>()
CacheBook.stop(this@CacheActivity)
}
}
R.id.menu_export_all -> exportAll()
R.id.menu_enable_replace -> AppConfig.exportUseReplace = !item.isChecked
R.id.menu_export_web_dav -> AppConfig.exportToWebDav = !item.isChecked
R.id.menu_export_folder -> export(-1)
R.id.menu_export_folder -> {
exportPosition = -1
selectExportFolder()
}
R.id.menu_export_type -> showExportTypeConfig()
R.id.menu_export_charset -> showCharsetConfig()
R.id.menu_log ->
@ -212,7 +216,17 @@ class CacheActivity : VMBaseActivity<ActivityCacheBookBinding, CacheViewModel>()
override fun export(position: Int) {
exportPosition = position
val path = ACache.get(this@CacheActivity).getAsString(exportBookPathKey)
if (path.isNullOrEmpty() || position < 0) {
if (path.isNullOrEmpty()) {
selectExportFolder()
} else {
startExport(path)
}
}
private fun exportAll() {
exportPosition = -10
val path = ACache.get(this@CacheActivity).getAsString(exportBookPathKey)
if (path.isNullOrEmpty()) {
selectExportFolder()
} else {
startExport(path)
@ -233,6 +247,33 @@ class CacheActivity : VMBaseActivity<ActivityCacheBookBinding, CacheViewModel>()
}
private fun startExport(path: String) {
if (exportPosition == -10) {
if (adapter.getItems().isNotEmpty()) {
Snackbar.make(binding.titleBar, R.string.exporting, Snackbar.LENGTH_INDEFINITE)
.show()
var exportSize = adapter.getItems().size
adapter.getItems().forEach { book ->
when (AppConfig.exportType) {
1 -> viewModel.exportEPUB(path, book) {
exportSize--
toastOnUi(it)
if (exportSize <= 0) {
binding.titleBar.snackbar(R.string.complete)
}
}
else -> viewModel.export(path, book) {
exportSize--
toastOnUi(it)
if (exportSize <= 0) {
binding.titleBar.snackbar(R.string.complete)
}
}
}
}
} else {
toastOnUi(R.string.no_book)
}
} else if (exportPosition >= 0) {
adapter.getItem(exportPosition)?.let { book ->
Snackbar.make(binding.titleBar, R.string.exporting, Snackbar.LENGTH_INDEFINITE)
.show()
@ -246,6 +287,7 @@ class CacheActivity : VMBaseActivity<ActivityCacheBookBinding, CacheViewModel>()
}
}
}
}
private fun showExportTypeConfig() {
selector(R.string.export_type, arrayListOf("txt", "epub")) { _, i ->

@ -39,7 +39,7 @@ import io.legado.app.utils.requestInputMethod
* 阅读界面
*/
abstract class ReadBookBaseActivity :
VMBaseActivity<ActivityBookReadBinding, ReadBookViewModel>() {
VMBaseActivity<ActivityBookReadBinding, ReadBookViewModel>(imageBg = false) {
override val viewModel: ReadBookViewModel
by viewModels()

@ -23,6 +23,7 @@ import io.legado.app.ui.widget.dialog.PhotoDialog
import io.legado.app.utils.activity
import io.legado.app.utils.getCompatColor
import io.legado.app.utils.getPrefBoolean
import io.legado.app.utils.toastOnUi
import kotlinx.coroutines.CoroutineScope
import kotlin.math.min
@ -178,7 +179,11 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at
val div = (lineBottom - lineTop - h) / 2
RectF(textChar.start, lineTop + div, textChar.end, lineBottom - div)
}
kotlin.runCatching {
canvas.drawBitmap(it, null, rectF, null)
}.onFailure { e ->
context.toastOnUi(e.localizedMessage)
}
}
}
}

@ -237,15 +237,6 @@ class PageView(context: Context) : FrameLayout(context) {
fun scroll(offset: Int) {
binding.contentTextView.scroll(offset)
// if (offset > 0) {
// for (i in 1..offset) {
// binding.contentTextView.scroll(1)
// }
// } else {
// for (i in offset..-1) {
// binding.contentTextView.scroll(-1)
// }
// }
}
fun upSelectAble(selectAble: Boolean) {

@ -102,8 +102,8 @@ class ReadView(context: Context, attrs: AttributeSet) :
setRect9x()
}
public fun setRect9x() {
val edge = if (AppConfig.fullScreenGesturesSupport) { 200f } else { 0f }
fun setRect9x() {
val edge = if (AppConfig.fullScreenGesturesSupport) 200f else 0f
tlRect.set(0f + edge, 0f, width * 0.33f, height * 0.33f)
tcRect.set(width * 0.33f, 0f, width * 0.66f, height * 0.33f)
trRect.set(width * 0.36f, 0f, width - 0f - edge, height * 0.33f)

@ -119,7 +119,7 @@ abstract class HorizontalPageDelegate(readView: ReadView) : PageDelegate(readVie
abortAnim()
if (!hasNext()) return
setDirection(PageDirection.NEXT)
readView.setTouchPoint(viewWidth.toFloat(), 0f, false)
readView.setStartPoint(viewWidth.toFloat(), 0f, false)
onAnimStart(animationSpeed)
}
@ -127,7 +127,7 @@ abstract class HorizontalPageDelegate(readView: ReadView) : PageDelegate(readVie
abortAnim()
if (!hasPrev()) return
setDirection(PageDirection.PREV)
readView.setTouchPoint(0f, 0f)
readView.setStartPoint(0f, 0f, false)
onAnimStart(animationSpeed)
}

@ -3,7 +3,7 @@ package io.legado.app.ui.book.read.page.delegate
import android.content.Context
import android.graphics.Canvas
import android.view.MotionEvent
import android.view.animation.DecelerateInterpolator
import android.view.animation.LinearInterpolator
import android.widget.Scroller
import androidx.annotation.CallSuper
import com.google.android.material.snackbar.Snackbar
@ -37,7 +37,7 @@ abstract class PageDelegate(protected val readView: ReadView) {
protected var viewHeight: Int = readView.height
protected val scroller: Scroller by lazy {
Scroller(readView.context, DecelerateInterpolator())
Scroller(readView.context, LinearInterpolator())
}
private val snackBar: Snackbar by lazy {

@ -1,60 +1,48 @@
package io.legado.app.ui.book.toc
import android.view.LayoutInflater
import android.content.Context
import android.view.ViewGroup
import androidx.paging.PagedListAdapter
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView
import io.legado.app.base.adapter.ItemViewHolder
import io.legado.app.base.adapter.RecyclerAdapter
import io.legado.app.data.entities.Bookmark
import io.legado.app.databinding.ItemBookmarkBinding
import splitties.views.onLongClick
class BookmarkAdapter(val callback: Callback) : PagedListAdapter<Bookmark, BookmarkAdapter.MyViewHolder>(DIFF_CALLBACK) {
class BookmarkAdapter(context: Context, val callback: Callback) :
RecyclerAdapter<Bookmark, ItemBookmarkBinding>(context) {
companion object {
@JvmField
val DIFF_CALLBACK = object : DiffUtil.ItemCallback<Bookmark>() {
override fun areItemsTheSame(oldItem: Bookmark, newItem: Bookmark): Boolean =
oldItem.time == newItem.time
override fun areContentsTheSame(oldItem: Bookmark, newItem: Bookmark): Boolean =
oldItem.time == newItem.time
&& oldItem.bookUrl == newItem.bookUrl
&& oldItem.chapterName == newItem.chapterName
&& oldItem.content == newItem.content
}
override fun getViewBinding(parent: ViewGroup): ItemBookmarkBinding {
return ItemBookmarkBinding.inflate(inflater, parent, false)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
val binding =
ItemBookmarkBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return MyViewHolder(binding)
override fun convert(
holder: ItemViewHolder,
binding: ItemBookmarkBinding,
item: Bookmark,
payloads: MutableList<Any>
) {
binding.tvChapterName.text = item.chapterName
binding.tvBookText.text = item.bookText
binding.tvContent.text = item.content
}
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
getItem(position)?.let {
holder.bind(it, callback)
}
override fun registerListener(holder: ItemViewHolder, binding: ItemBookmarkBinding) {
binding.root.setOnClickListener {
getItem(holder.layoutPosition)?.let { bookmark ->
callback.onClick(bookmark)
}
class MyViewHolder(val binding: ItemBookmarkBinding) : RecyclerView.ViewHolder(binding.root) {
fun bind(bookmark: Bookmark, callback: Callback?) = with(binding) {
tvChapterName.text = bookmark.chapterName
tvBookText.text = bookmark.bookText
tvContent.text = bookmark.content
itemView.setOnClickListener {
callback?.onClick(bookmark)
}
itemView.onLongClick {
callback?.onLongClick(bookmark)
binding.root.onLongClick {
getItem(holder.layoutPosition)?.let { bookmark ->
callback.onLongClick(bookmark)
}
}
}
interface Callback {
fun onClick(bookmark: Bookmark)
fun onLongClick(bookmark: Bookmark)
}
}

@ -1,14 +1,11 @@
package io.legado.app.ui.book.toc
import android.annotation.SuppressLint
import android.app.Activity
import android.content.Intent
import android.os.Bundle
import android.view.View
import androidx.fragment.app.activityViewModels
import androidx.lifecycle.LiveData
import androidx.paging.LivePagedListBuilder
import androidx.paging.PagedList
import androidx.recyclerview.widget.LinearLayoutManager
import io.legado.app.R
import io.legado.app.base.VMBaseFragment
@ -31,7 +28,7 @@ class BookmarkFragment : VMBaseFragment<ChapterListViewModel>(R.layout.fragment_
override val viewModel: ChapterListViewModel by activityViewModels()
private val binding by viewBinding(FragmentBookmarkBinding::bind)
private lateinit var adapter: BookmarkAdapter
private var bookmarkLiveData: LiveData<PagedList<Bookmark>>? = null
private var bookmarkLiveData: LiveData<List<Bookmark>>? = null
override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) {
viewModel.bookMarkCallBack = this
@ -43,7 +40,7 @@ class BookmarkFragment : VMBaseFragment<ChapterListViewModel>(R.layout.fragment_
private fun initRecyclerView() {
ATH.applyEdgeEffectColor(binding.recyclerView)
adapter = BookmarkAdapter(this)
adapter = BookmarkAdapter(requireContext(), this)
binding.recyclerView.layoutManager = LinearLayoutManager(requireContext())
binding.recyclerView.addItemDecoration(VerticalDivider(requireContext()))
binding.recyclerView.adapter = adapter
@ -51,11 +48,8 @@ class BookmarkFragment : VMBaseFragment<ChapterListViewModel>(R.layout.fragment_
private fun initData(book: Book) {
bookmarkLiveData?.removeObservers(viewLifecycleOwner)
bookmarkLiveData =
LivePagedListBuilder(
appDb.bookmarkDao.observeByBook(book.bookUrl, book.name, book.author), 20
).build()
bookmarkLiveData?.observe(viewLifecycleOwner, { adapter.submitList(it) })
bookmarkLiveData = appDb.bookmarkDao.observeByBook(book.bookUrl, book.name, book.author)
bookmarkLiveData?.observe(viewLifecycleOwner, { adapter.setItems(it) })
}
override fun startBookmarkSearch(newText: String?) {
@ -65,13 +59,8 @@ class BookmarkFragment : VMBaseFragment<ChapterListViewModel>(R.layout.fragment_
}
} else {
bookmarkLiveData?.removeObservers(viewLifecycleOwner)
bookmarkLiveData = LivePagedListBuilder(
appDb.bookmarkDao.liveDataSearch(
viewModel.bookUrl,
newText
), 20
).build()
bookmarkLiveData?.observe(viewLifecycleOwner, { adapter.submitList(it) })
bookmarkLiveData = appDb.bookmarkDao.liveDataSearch(viewModel.bookUrl, newText)
bookmarkLiveData?.observe(viewLifecycleOwner, { adapter.setItems(it) })
}
}
@ -84,7 +73,6 @@ class BookmarkFragment : VMBaseFragment<ChapterListViewModel>(R.layout.fragment_
activity?.finish()
}
@SuppressLint("InflateParams")
override fun onLongClick(bookmark: Bookmark) {
requireContext().alert(R.string.bookmark) {
setMessage(bookmark.chapterName)

@ -3,12 +3,10 @@ package io.legado.app.ui.main
import android.os.Bundle
import android.view.KeyEvent
import android.view.MenuItem
import android.view.ViewGroup
import androidx.activity.viewModels
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager
import androidx.fragment.app.FragmentStatePagerAdapter
import androidx.viewpager.widget.ViewPager
import androidx.viewpager2.adapter.FragmentStateAdapter
import androidx.viewpager2.widget.ViewPager2
import com.google.android.material.bottomnavigation.BottomNavigationView
import io.legado.app.BuildConfig
import io.legado.app.R
@ -36,31 +34,44 @@ import io.legado.app.utils.toastOnUi
class MainActivity : VMBaseActivity<ActivityMainBinding, MainViewModel>(),
BottomNavigationView.OnNavigationItemSelectedListener,
BottomNavigationView.OnNavigationItemReselectedListener,
ViewPager.OnPageChangeListener by ViewPager.SimpleOnPageChangeListener() {
BottomNavigationView.OnNavigationItemReselectedListener {
override val viewModel: MainViewModel by viewModels()
private var exitTime: Long = 0
private var bookshelfReselected: Long = 0
private var exploreReselected: Long = 0
private var pagePosition = 0
private val fragmentMap = hashMapOf<Int, Fragment>()
private val fragmentMap = hashMapOf<Long, Fragment>()
override fun getViewBinding(): ActivityMainBinding {
return ActivityMainBinding.inflate(layoutInflater)
}
override fun onActivityCreated(savedInstanceState: Bundle?) = with(binding) {
override fun onActivityCreated(savedInstanceState: Bundle?) {
binding.apply {
ATH.applyEdgeEffectColor(viewPagerMain)
ATH.applyBottomNavigationColor(bottomNavigationView)
viewPagerMain.offscreenPageLimit = 3
viewPagerMain.adapter = TabFragmentPageAdapter(supportFragmentManager)
viewPagerMain.addOnPageChangeListener(this@MainActivity)
viewPagerMain.adapter = TabFragmentPageAdapter()
viewPagerMain.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
override fun onPageSelected(position: Int) {
pagePosition = position
when (position) {
0, 1, 3 -> bottomNavigationView.menu.getItem(position).isChecked = true
2 -> if (AppConfig.isShowRSS) {
bottomNavigationView.menu.getItem(position).isChecked = true
} else {
bottomNavigationView.menu.getItem(3).isChecked = true
}
}
}
})
bottomNavigationView.elevation =
if (AppConfig.elevation < 0) elevation else AppConfig.elevation.toFloat()
bottomNavigationView.setOnNavigationItemSelectedListener(this@MainActivity)
bottomNavigationView.setOnNavigationItemReselectedListener(this@MainActivity)
bottomNavigationView.menu.findItem(R.id.menu_rss).isVisible = AppConfig.isShowRSS
}
}
override fun onPostCreate(savedInstanceState: Bundle?) {
super.onPostCreate(savedInstanceState)
@ -105,6 +116,10 @@ class MainActivity : VMBaseActivity<ActivityMainBinding, MainViewModel>(),
}
}
fun getViewPager(): ViewPager2 {
return binding.viewPagerMain
}
private fun upVersion() {
if (LocalConfig.versionCode != appInfo.versionCode) {
LocalConfig.versionCode = appInfo.versionCode
@ -120,18 +135,6 @@ class MainActivity : VMBaseActivity<ActivityMainBinding, MainViewModel>(),
}
}
override fun onPageSelected(position: Int) = with(binding) {
pagePosition = position
when (position) {
0, 1, 3 -> bottomNavigationView.menu.getItem(position).isChecked = true
2 -> if (AppConfig.isShowRSS) {
bottomNavigationView.menu.getItem(position).isChecked = true
} else {
bottomNavigationView.menu.getItem(3).isChecked = true
}
}
}
override fun onKeyUp(keyCode: Int, event: KeyEvent?): Boolean {
event?.let {
when (keyCode) {
@ -186,36 +189,33 @@ class MainActivity : VMBaseActivity<ActivityMainBinding, MainViewModel>(),
}
}
private inner class TabFragmentPageAdapter(fm: FragmentManager) :
FragmentStatePagerAdapter(fm, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
private inner class TabFragmentPageAdapter :
FragmentStateAdapter(this) {
private fun getId(position: Int): Int {
override fun getItemId(position: Int): Long {
return when (position) {
2 -> if (AppConfig.isShowRSS) 2 else 3
else -> position
else -> position.toLong()
}
}
override fun getItemPosition(`object`: Any): Int {
return POSITION_NONE
override fun containsItem(itemId: Long): Boolean {
return fragmentMap.containsKey(itemId)
}
override fun getItem(position: Int): Fragment {
return when (getId(position)) {
0 -> BookshelfFragment()
1 -> ExploreFragment()
2 -> RssFragment()
else -> MyFragment()
}
}
override fun getCount(): Int {
override fun getItemCount(): Int {
return if (AppConfig.isShowRSS) 4 else 3
}
override fun instantiateItem(container: ViewGroup, position: Int): Any {
val fragment = super.instantiateItem(container, position) as Fragment
fragmentMap[getId(position)] = fragment
override fun createFragment(position: Int): Fragment {
val id = getItemId(position)
val fragment = when (id) {
0L -> BookshelfFragment()
1L -> ExploreFragment()
2L -> RssFragment()
else -> MyFragment()
}
fragmentMap[id] = fragment
return fragment
}

@ -119,11 +119,11 @@ class MainViewModel(application: Application) : BaseViewModel(application) {
if (!BookHelp.hasContent(book, chapter)) {
var addToCache = false
while (!addToCache) {
if (CacheBook.downloadCount() < 5) {
if (CacheBook.downloadCount() < 10) {
CacheBook.download(this, webBook, book, chapter)
addToCache = true
} else {
delay(1000)
delay(100)
}
}
}

@ -5,11 +5,14 @@ import android.os.Bundle
import android.view.Menu
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.widget.SearchView
import androidx.fragment.app.*
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.fragment.app.viewModels
import androidx.lifecycle.LiveData
import androidx.viewpager2.adapter.FragmentStateAdapter
import com.google.android.material.tabs.TabLayout
import com.google.android.material.tabs.TabLayoutMediator
import io.legado.app.R
import io.legado.app.base.VMBaseFragment
import io.legado.app.constant.AppConst
@ -19,6 +22,7 @@ import io.legado.app.data.entities.BookGroup
import io.legado.app.databinding.DialogBookshelfConfigBinding
import io.legado.app.databinding.DialogEditTextBinding
import io.legado.app.databinding.FragmentBookshelfBinding
import io.legado.app.help.AppConfig
import io.legado.app.lib.dialogs.alert
import io.legado.app.lib.theme.ATH
import io.legado.app.lib.theme.accentColor
@ -44,7 +48,7 @@ class BookshelfFragment : VMBaseFragment<BookshelfViewModel>(R.layout.fragment_b
private val binding by viewBinding(FragmentBookshelfBinding::bind)
override val viewModel: BookshelfViewModel by viewModels()
private val activityViewModel: MainViewModel by activityViewModels()
private lateinit var adapter: FragmentStatePagerAdapter
private lateinit var adapter: FragmentStateAdapter
private lateinit var tabLayout: TabLayout
private var bookGroupLiveData: LiveData<List<BookGroup>>? = null
private val bookGroups = mutableListOf<BookGroup>()
@ -107,10 +111,12 @@ class BookshelfFragment : VMBaseFragment<BookshelfViewModel>(R.layout.fragment_b
tabLayout.isTabIndicatorFullWidth = false
tabLayout.tabMode = TabLayout.MODE_SCROLLABLE
tabLayout.setSelectedTabIndicatorColor(requireContext().accentColor)
tabLayout.setupWithViewPager(binding.viewPagerBookshelf)
binding.viewPagerBookshelf.offscreenPageLimit = 1
adapter = TabFragmentPageAdapter(childFragmentManager)
adapter = TabFragmentPageAdapter()
binding.viewPagerBookshelf.adapter = adapter
TabLayoutMediator(tabLayout, binding.viewPagerBookshelf) { tab, i ->
tab.text = bookGroups[i].groupName
}.attach()
}
private fun initBookGroupData() {
@ -165,6 +171,7 @@ class BookshelfFragment : VMBaseFragment<BookshelfViewModel>(R.layout.fragment_b
.apply {
rgLayout.checkByIndex(bookshelfLayout)
rgSort.checkByIndex(bookshelfSort)
swShowUnread.isChecked = AppConfig.showUnread
}
customView { alertBinding.root }
okButton {
@ -178,6 +185,10 @@ class BookshelfFragment : VMBaseFragment<BookshelfViewModel>(R.layout.fragment_b
putPrefInt(PreferKey.bookshelfSort, rgSort.getCheckedIndex())
changed = true
}
if (AppConfig.showUnread != swShowUnread.isChecked) {
AppConfig.showUnread = swShowUnread.isChecked
changed = true
}
if (changed) {
activity?.recreate()
}
@ -240,29 +251,25 @@ class BookshelfFragment : VMBaseFragment<BookshelfViewModel>(R.layout.fragment_b
}.show()
}
private inner class TabFragmentPageAdapter(fm: FragmentManager) :
FragmentStatePagerAdapter(fm, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
override fun getPageTitle(position: Int): CharSequence {
return bookGroups[position].groupName
}
private inner class TabFragmentPageAdapter :
FragmentStateAdapter(this) {
override fun getItemPosition(`object`: Any): Int {
return POSITION_NONE
override fun getItemId(position: Int): Long {
val group = bookGroups[position]
return group.groupId
}
override fun getItem(position: Int): Fragment {
val group = bookGroups[position]
return BooksFragment.newInstance(position, group.groupId)
override fun containsItem(itemId: Long): Boolean {
return fragmentMap.containsKey(itemId)
}
override fun getCount(): Int {
override fun getItemCount(): Int {
return bookGroups.size
}
override fun instantiateItem(container: ViewGroup, position: Int): Any {
val fragment = super.instantiateItem(container, position) as BooksFragment
override fun createFragment(position: Int): Fragment {
val group = bookGroups[position]
val fragment = BooksFragment.newInstance(position, group.groupId)
fragmentMap[group.groupId] = fragment
return fragment
}

@ -0,0 +1,82 @@
package io.legado.app.ui.main.bookshelf
import android.content.Context
import android.gesture.GestureOverlayView.ORIENTATION_HORIZONTAL
import android.util.AttributeSet
import android.view.MotionEvent
import android.view.View
import android.view.ViewConfiguration
import android.widget.LinearLayout
import androidx.viewpager2.widget.ViewPager2
import io.legado.app.R
import io.legado.app.ui.main.MainActivity
import io.legado.app.utils.activity
import kotlin.math.absoluteValue
import kotlin.math.sign
class RootView : LinearLayout {
constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
private val touchSlop = ViewConfiguration.get(context).scaledTouchSlop
private var initialX = 0f
private var initialY = 0f
private val parentViewPager: ViewPager2?
get() = (activity as? MainActivity)?.getViewPager()
private val childViewPager: View?
get() = findViewById(R.id.view_pager_bookshelf)
private fun canChildScroll(orientation: Int, delta: Float): Boolean {
val direction = -delta.sign.toInt()
return when (orientation) {
0 -> childViewPager?.canScrollHorizontally(direction) ?: false
1 -> childViewPager?.canScrollVertically(direction) ?: false
else -> throw IllegalArgumentException()
}
}
override fun onInterceptTouchEvent(e: MotionEvent): Boolean {
handleInterceptTouchEvent(e)
return super.onInterceptTouchEvent(e)
}
private fun handleInterceptTouchEvent(e: MotionEvent) {
val orientation = parentViewPager?.orientation ?: return
// Early return if child can't scroll in same direction as parent
if (!canChildScroll(orientation, -1f) && !canChildScroll(orientation, 1f)) {
return
}
if (e.action == MotionEvent.ACTION_DOWN) {
initialX = e.x
initialY = e.y
parent.requestDisallowInterceptTouchEvent(true)
} else if (e.action == MotionEvent.ACTION_MOVE) {
val dx = e.x - initialX
val dy = e.y - initialY
val isVpHorizontal = orientation == ORIENTATION_HORIZONTAL
// assuming ViewPager2 touch-slop is 2x touch-slop of child
val scaledDx = dx.absoluteValue * if (isVpHorizontal) .5f else 1f
val scaledDy = dy.absoluteValue * if (isVpHorizontal) 1f else .5f
if (scaledDx > touchSlop || scaledDy > touchSlop) {
if (isVpHorizontal == (scaledDy > scaledDx)) {
// Gesture is perpendicular, allow all parents to intercept
parent.requestDisallowInterceptTouchEvent(false)
} else {
// Gesture is parallel, query child if movement in that direction is possible
if (canChildScroll(orientation, if (isVpHorizontal) dx else dy)) {
// Child can scroll, disallow all parents to intercept
parent.requestDisallowInterceptTouchEvent(true)
} else {
// Child cannot scroll, allow all parents to intercept
parent.requestDisallowInterceptTouchEvent(false)
}
}
}
}
}
}

@ -7,8 +7,7 @@ import io.legado.app.base.adapter.ItemViewHolder
import io.legado.app.constant.BookType
import io.legado.app.data.entities.Book
import io.legado.app.databinding.ItemBookshelfGridBinding
import io.legado.app.lib.theme.backgroundColor
import io.legado.app.utils.ColorUtils
import io.legado.app.help.AppConfig
import io.legado.app.utils.invisible
import splitties.views.onLongClick
@ -27,7 +26,6 @@ class BooksAdapterGrid(context: Context, private val callBack: CallBack) :
) = with(binding) {
val bundle = payloads.getOrNull(0) as? Bundle
if (bundle == null) {
root.setBackgroundColor(ColorUtils.withAlpha(context.backgroundColor, 0.5f))
tvName.text = item.name
ivCover.load(item.getDisplayCover(), item.name, item.author)
upRefresh(binding, item)
@ -48,8 +46,12 @@ class BooksAdapterGrid(context: Context, private val callBack: CallBack) :
binding.rlLoading.show()
} else {
binding.rlLoading.hide()
if (AppConfig.showUnread) {
binding.bvUnread.setBadgeCount(item.getUnreadChapterNum())
binding.bvUnread.setHighlight(item.lastCheckCount > 0)
} else {
binding.bvUnread.invisible()
}
}
}

@ -7,8 +7,7 @@ import io.legado.app.base.adapter.ItemViewHolder
import io.legado.app.constant.BookType
import io.legado.app.data.entities.Book
import io.legado.app.databinding.ItemBookshelfListBinding
import io.legado.app.lib.theme.backgroundColor
import io.legado.app.utils.ColorUtils
import io.legado.app.help.AppConfig
import io.legado.app.utils.invisible
import splitties.views.onLongClick
@ -27,7 +26,6 @@ class BooksAdapterList(context: Context, private val callBack: CallBack) :
) = with(binding) {
val bundle = payloads.getOrNull(0) as? Bundle
if (bundle == null) {
root.setBackgroundColor(ColorUtils.withAlpha(context.backgroundColor, 0.5f))
tvName.text = item.name
tvAuthor.text = item.author
tvRead.text = item.durChapterTitle
@ -54,8 +52,12 @@ class BooksAdapterList(context: Context, private val callBack: CallBack) :
binding.rlLoading.show()
} else {
binding.rlLoading.hide()
if (AppConfig.showUnread) {
binding.bvUnread.setHighlight(item.lastCheckCount > 0)
binding.bvUnread.setBadgeCount(item.getUnreadChapterNum())
} else {
binding.bvUnread.invisible()
}
}
}

@ -22,6 +22,7 @@ class RssSortActivity : VMBaseActivity<ActivityRssArtivlesBinding, RssSortViewMo
by viewModels()
private var sorts = linkedMapOf<String, String>()
private lateinit var adapter: TabFragmentPageAdapter
private val fragmentMap = hashMapOf<Long, Fragment>()
private val upSourceResult = registerForActivityResult(
ActivityResultContracts.StartActivityForResult()
) {
@ -99,12 +100,20 @@ class RssSortActivity : VMBaseActivity<ActivityRssArtivlesBinding, RssSortViewMo
return style * 100 + super.getItemId(position)
}
override fun containsItem(itemId: Long): Boolean {
return fragmentMap.containsKey(itemId)
}
override fun createFragment(position: Int): Fragment {
return RssArticlesFragment.create(
val itemId = getItemId(position)
val fragment = RssArticlesFragment.create(
sorts.keys.elementAt(position),
sorts.values.elementAt(position)
)
fragmentMap[itemId] = fragment
return fragment
}
}
}

@ -6,17 +6,14 @@ import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import androidx.viewpager.widget.ViewPager
/**
* Created by GKF on 2018/3/16.
*/
@Suppress("MemberVisibilityCanBePrivate")
class ItemTouchCallback(private val callback: Callback) : ItemTouchHelper.Callback() {
private var swipeRefreshLayout: SwipeRefreshLayout? = null
private var viewPager: ViewPager? = null
/**
* 是否可以拖拽
@ -107,7 +104,6 @@ class ItemTouchCallback(private val callback: Callback) : ItemTouchHelper.Callba
super.onSelectedChanged(viewHolder, actionState)
val swiping = actionState == ItemTouchHelper.ACTION_STATE_DRAG
swipeRefreshLayout?.isEnabled = !swiping
viewPager?.requestDisallowInterceptTouchEvent(swiping)
}
override fun clearView(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder) {

@ -1,41 +0,0 @@
package io.legado.app.ui.widget.recycler
import android.content.Context
import android.util.AttributeSet
import android.view.MotionEvent
import androidx.recyclerview.widget.RecyclerView
import kotlin.math.abs
class RecyclerViewAtViewPager2(context: Context, attrs: AttributeSet?) :
RecyclerView(context, attrs) {
private var startX: Int = 0
private var startY: Int = 0
override fun dispatchTouchEvent(ev: MotionEvent): Boolean {
when (ev.action) {
MotionEvent.ACTION_DOWN -> {
startX = ev.x.toInt()
startY = ev.y.toInt()
parent.requestDisallowInterceptTouchEvent(true)
}
MotionEvent.ACTION_MOVE -> {
val endX = ev.x.toInt()
val endY = ev.y.toInt()
val disX = abs(endX - startX)
val disY = abs(endY - startY)
if (disX > disY) {
parent.requestDisallowInterceptTouchEvent(canScrollHorizontally(startX - endX))
} else {
parent.requestDisallowInterceptTouchEvent(canScrollVertically(startY - endY))
}
}
MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> parent.requestDisallowInterceptTouchEvent(
false
)
}
return super.dispatchTouchEvent(ev)
}
}

@ -5,7 +5,7 @@
android:layout_height="match_parent"
android:orientation="vertical">
<androidx.viewpager.widget.ViewPager
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/view_pager_main"
android:layout_width="match_parent"
android:layout_height="0dp"

@ -101,5 +101,15 @@
</LinearLayout>
<io.legado.app.lib.theme.view.ATESwitch
android:id="@+id/sw_show_unread"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:padding="6dp"
android:text="@string/show_unread"
app:layout_constraintTop_toBottomOf="@+id/ll_layout"
app:layout_constraintLeft_toLeftOf="@+id/ll_layout"
app:layout_constraintRight_toRightOf="@id/ll_layout" />
</androidx.constraintlayout.widget.ConstraintLayout>

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
<io.legado.app.ui.main.bookshelf.RootView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
@ -10,12 +10,12 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:attachToActivity="false"
app:title="@string/bookshelf"
app:contentLayout="@layout/view_tab_layout_min" />
app:contentLayout="@layout/view_tab_layout_min"
app:title="@string/bookshelf" />
<androidx.viewpager.widget.ViewPager
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/view_pager_bookshelf"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
</io.legado.app.ui.main.bookshelf.RootView>

@ -1,13 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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:tools="http://schemas.android.com/tools"
android:id="@+id/cv_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clickable="true"
android:focusable="true"
tools:ignore="UnusedAttribute">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingTop="8dp"
android:paddingLeft="8dp"
android:paddingRight="8dp">
android:paddingRight="8dp"
app:layout_constraintTop_toTopOf="parent">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
@ -64,4 +74,17 @@
</LinearLayout>
<View
android:id="@+id/vw_foreground"
android:layout_width="match_parent"
android:layout_height="0dp"
android:background="?android:attr/selectableItemBackground"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.0"
tools:layout_editor_absoluteX="0dp" />
</androidx.constraintlayout.widget.ConstraintLayout>

@ -167,13 +167,4 @@
app:layout_constraintVertical_bias="0.0"
tools:layout_editor_absoluteX="0dp" />
<View
android:id="@+id/vw_select"
android:layout_width="match_parent"
android:layout_height="0dp"
android:visibility="gone"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

@ -20,6 +20,11 @@
</item>
<item
android:id="@+id/menu_export_all"
android:title="@string/export_all"
app:showAsAction="never" />
<item
android:id="@+id/menu_enable_replace"
android:title="@string/replace_purify"

@ -822,5 +822,8 @@
<string name="url_already">此url已订阅</string>
<string name="high_brush_title">高刷</string>
<string name="high_brush_summary">使用屏幕最高刷新率</string>
<string name="export_all">导出所有</string>
<string name="complete">完成</string>
<string name="show_unread">显示未读标志</string>
</resources>

@ -826,5 +826,8 @@
<string name="url_already">此url已訂閱</string>
<string name="high_brush_title">高刷</string>
<string name="high_brush_summary">使用螢幕最高刷新率</string>
<string name="export_all">导出所有</string>
<string name="complete">完成</string>
<string name="show_unread">显示未读标志</string>
</resources>

@ -826,5 +826,8 @@
<string name="url_already">此url已订阅</string>
<string name="high_brush_title">高刷</string>
<string name="high_brush_summary">使用屏幕最高刷新率</string>
<string name="export_all">导出所有</string>
<string name="complete">完成</string>
<string name="show_unread">显示未读标志</string>
</resources>

@ -827,5 +827,8 @@
<string name="url_already">此url已订阅</string>
<string name="high_brush_title">高刷</string>
<string name="high_brush_summary">使用屏幕最高刷新率</string>
<string name="export_all">export_all</string>
<string name="complete">complete</string>
<string name="show_unread">Show unread</string>
</resources>

@ -11,7 +11,8 @@ buildscript {
}
dependencies {
classpath 'com.android.tools.build:gradle:4.2.0'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.5.0"
//noinspection DifferentKotlinGradleVersion
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath "de.timfreiheit.resourceplaceholders:placeholders:0.3"
}
}

Loading…
Cancel
Save