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. 29
      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. 5
      build.gradle

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

@ -3,7 +3,30 @@
* 关注合作公众号 **[小说拾遗]** 获取好看的小说。 * 关注合作公众号 **[小说拾遗]** 获取好看的小说。
* 旧版数据导入教程:先在旧版阅读(2.x)中进行备份,然后在新版阅读(3.x)【我的】->【备份与恢复】,选择【导入旧版本数据】。 * 旧版数据导入教程:先在旧版阅读(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 * 修复bug
* url参数添加重置次数,retry * url参数添加重置次数,retry
* 修改默认tts, 手动导入 * 修改默认tts, 手动导入

@ -33,7 +33,8 @@ abstract class BaseActivity<VB : ViewBinding>(
val fullScreen: Boolean = true, val fullScreen: Boolean = true,
private val theme: Theme = Theme.Auto, private val theme: Theme = Theme.Auto,
private val toolBarTheme: 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(), ) : AppCompatActivity(),
CoroutineScope by MainScope() { CoroutineScope by MainScope() {
@ -173,9 +174,13 @@ abstract class BaseActivity<VB : ViewBinding>(
ATH.applyBackgroundTint(window.decorView) ATH.applyBackgroundTint(window.decorView)
} }
} }
if (imageBg) {
ThemeConfig.getBgImage(this)?.let { ThemeConfig.getBgImage(this)?.let {
kotlin.runCatching { try {
window.decorView.background = it 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>( abstract class VMBaseActivity<VB : ViewBinding, VM : ViewModel>(
fullScreen: Boolean = true, fullScreen: Boolean = true,
theme: Theme = Theme.Auto, theme: Theme = Theme.Auto,
toolBarTheme: Theme = Theme.Auto toolBarTheme: Theme = Theme.Auto,
) : BaseActivity<VB>(fullScreen, theme, toolBarTheme) { transparent: Boolean = false,
imageBg: Boolean = true
) : BaseActivity<VB>(fullScreen, theme, toolBarTheme, transparent, imageBg) {
protected abstract val viewModel: VM protected abstract val viewModel: VM

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

@ -1,7 +1,6 @@
package io.legado.app.data.dao package io.legado.app.data.dao
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.paging.DataSource
import androidx.room.* import androidx.room.*
import io.legado.app.data.entities.BookSource 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) <> ''") @Query("select distinct bookSourceGroup from book_sources where enabledExplore = 1 and trim(exploreUrl) <> '' and trim(bookSourceGroup) <> ''")
fun liveExploreGroup(): LiveData<List<String>> 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 || '%'") @Query("select * from book_sources where bookSourceGroup like '%' || :group || '%'")
fun getByGroup(group: String): List<BookSource> fun getByGroup(group: String): List<BookSource>

@ -1,6 +1,6 @@
package io.legado.app.data.dao package io.legado.app.data.dao
import androidx.paging.DataSource import androidx.lifecycle.LiveData
import androidx.room.* import androidx.room.*
import io.legado.app.data.entities.Bookmark import io.legado.app.data.entities.Bookmark
@ -16,10 +16,10 @@ interface BookmarkDao {
bookUrl: String, bookUrl: String,
bookName: String, bookName: String,
bookAuthor: 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||'%'") @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) @Insert(onConflict = OnConflictStrategy.REPLACE)
fun insert(vararg bookmark: Bookmark) fun insert(vararg bookmark: Bookmark)

@ -1,6 +1,5 @@
package io.legado.app.data.dao package io.legado.app.data.dao
import androidx.paging.DataSource
import androidx.room.Dao import androidx.room.Dao
import androidx.room.Insert import androidx.room.Insert
import androidx.room.OnConflictStrategy import androidx.room.OnConflictStrategy
@ -10,12 +9,6 @@ import io.legado.app.data.entities.SearchBook
@Dao @Dao
interface SearchBookDao { 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") @Query("select * from searchBooks where bookUrl = :bookUrl")
fun getSearchBook(bookUrl: String): SearchBook? 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 val isTransparentStatusBar: Boolean
get() = appCtx.getPrefBoolean(PreferKey.transparentStatusBar, true) get() = appCtx.getPrefBoolean(PreferKey.transparentStatusBar, true)

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

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

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

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

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

@ -115,9 +115,13 @@ class CacheActivity : VMBaseActivity<ActivityCacheBookBinding, CacheViewModel>()
CacheBook.stop(this@CacheActivity) CacheBook.stop(this@CacheActivity)
} }
} }
R.id.menu_export_all -> exportAll()
R.id.menu_enable_replace -> AppConfig.exportUseReplace = !item.isChecked R.id.menu_enable_replace -> AppConfig.exportUseReplace = !item.isChecked
R.id.menu_export_web_dav -> AppConfig.exportToWebDav = !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_type -> showExportTypeConfig()
R.id.menu_export_charset -> showCharsetConfig() R.id.menu_export_charset -> showCharsetConfig()
R.id.menu_log -> R.id.menu_log ->
@ -212,7 +216,17 @@ class CacheActivity : VMBaseActivity<ActivityCacheBookBinding, CacheViewModel>()
override fun export(position: Int) { override fun export(position: Int) {
exportPosition = position exportPosition = position
val path = ACache.get(this@CacheActivity).getAsString(exportBookPathKey) 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() selectExportFolder()
} else { } else {
startExport(path) startExport(path)
@ -233,6 +247,33 @@ class CacheActivity : VMBaseActivity<ActivityCacheBookBinding, CacheViewModel>()
} }
private fun startExport(path: String) { 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 -> adapter.getItem(exportPosition)?.let { book ->
Snackbar.make(binding.titleBar, R.string.exporting, Snackbar.LENGTH_INDEFINITE) Snackbar.make(binding.titleBar, R.string.exporting, Snackbar.LENGTH_INDEFINITE)
.show() .show()
@ -246,6 +287,7 @@ class CacheActivity : VMBaseActivity<ActivityCacheBookBinding, CacheViewModel>()
} }
} }
} }
}
private fun showExportTypeConfig() { private fun showExportTypeConfig() {
selector(R.string.export_type, arrayListOf("txt", "epub")) { _, i -> selector(R.string.export_type, arrayListOf("txt", "epub")) { _, i ->

@ -39,7 +39,7 @@ import io.legado.app.utils.requestInputMethod
* 阅读界面 * 阅读界面
*/ */
abstract class ReadBookBaseActivity : abstract class ReadBookBaseActivity :
VMBaseActivity<ActivityBookReadBinding, ReadBookViewModel>() { VMBaseActivity<ActivityBookReadBinding, ReadBookViewModel>(imageBg = false) {
override val viewModel: ReadBookViewModel override val viewModel: ReadBookViewModel
by viewModels() 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.activity
import io.legado.app.utils.getCompatColor import io.legado.app.utils.getCompatColor
import io.legado.app.utils.getPrefBoolean import io.legado.app.utils.getPrefBoolean
import io.legado.app.utils.toastOnUi
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlin.math.min import kotlin.math.min
@ -178,7 +179,11 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at
val div = (lineBottom - lineTop - h) / 2 val div = (lineBottom - lineTop - h) / 2
RectF(textChar.start, lineTop + div, textChar.end, lineBottom - div) RectF(textChar.start, lineTop + div, textChar.end, lineBottom - div)
} }
kotlin.runCatching {
canvas.drawBitmap(it, null, rectF, null) 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) { fun scroll(offset: Int) {
binding.contentTextView.scroll(offset) 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) { fun upSelectAble(selectAble: Boolean) {

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

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

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

@ -1,60 +1,48 @@
package io.legado.app.ui.book.toc package io.legado.app.ui.book.toc
import android.view.LayoutInflater import android.content.Context
import android.view.ViewGroup import android.view.ViewGroup
import androidx.paging.PagedListAdapter import io.legado.app.base.adapter.ItemViewHolder
import androidx.recyclerview.widget.DiffUtil import io.legado.app.base.adapter.RecyclerAdapter
import androidx.recyclerview.widget.RecyclerView
import io.legado.app.data.entities.Bookmark import io.legado.app.data.entities.Bookmark
import io.legado.app.databinding.ItemBookmarkBinding import io.legado.app.databinding.ItemBookmarkBinding
import splitties.views.onLongClick 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 { override fun getViewBinding(parent: ViewGroup): ItemBookmarkBinding {
return ItemBookmarkBinding.inflate(inflater, parent, false)
@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 onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder { override fun convert(
val binding = holder: ItemViewHolder,
ItemBookmarkBinding.inflate(LayoutInflater.from(parent.context), parent, false) binding: ItemBookmarkBinding,
return MyViewHolder(binding) 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) { override fun registerListener(holder: ItemViewHolder, binding: ItemBookmarkBinding) {
getItem(position)?.let { binding.root.setOnClickListener {
holder.bind(it, callback) 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 { binding.root.onLongClick {
callback?.onLongClick(bookmark) getItem(holder.layoutPosition)?.let { bookmark ->
callback.onLongClick(bookmark)
} }
} }
} }
interface Callback { interface Callback {
fun onClick(bookmark: Bookmark) fun onClick(bookmark: Bookmark)
fun onLongClick(bookmark: Bookmark) fun onLongClick(bookmark: Bookmark)
} }
} }

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

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

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

@ -5,11 +5,14 @@ import android.os.Bundle
import android.view.Menu import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
import android.view.View import android.view.View
import android.view.ViewGroup
import androidx.appcompat.widget.SearchView 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.lifecycle.LiveData
import androidx.viewpager2.adapter.FragmentStateAdapter
import com.google.android.material.tabs.TabLayout import com.google.android.material.tabs.TabLayout
import com.google.android.material.tabs.TabLayoutMediator
import io.legado.app.R import io.legado.app.R
import io.legado.app.base.VMBaseFragment import io.legado.app.base.VMBaseFragment
import io.legado.app.constant.AppConst 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.DialogBookshelfConfigBinding
import io.legado.app.databinding.DialogEditTextBinding import io.legado.app.databinding.DialogEditTextBinding
import io.legado.app.databinding.FragmentBookshelfBinding import io.legado.app.databinding.FragmentBookshelfBinding
import io.legado.app.help.AppConfig
import io.legado.app.lib.dialogs.alert import io.legado.app.lib.dialogs.alert
import io.legado.app.lib.theme.ATH import io.legado.app.lib.theme.ATH
import io.legado.app.lib.theme.accentColor 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) private val binding by viewBinding(FragmentBookshelfBinding::bind)
override val viewModel: BookshelfViewModel by viewModels() override val viewModel: BookshelfViewModel by viewModels()
private val activityViewModel: MainViewModel by activityViewModels() private val activityViewModel: MainViewModel by activityViewModels()
private lateinit var adapter: FragmentStatePagerAdapter private lateinit var adapter: FragmentStateAdapter
private lateinit var tabLayout: TabLayout private lateinit var tabLayout: TabLayout
private var bookGroupLiveData: LiveData<List<BookGroup>>? = null private var bookGroupLiveData: LiveData<List<BookGroup>>? = null
private val bookGroups = mutableListOf<BookGroup>() private val bookGroups = mutableListOf<BookGroup>()
@ -107,10 +111,12 @@ class BookshelfFragment : VMBaseFragment<BookshelfViewModel>(R.layout.fragment_b
tabLayout.isTabIndicatorFullWidth = false tabLayout.isTabIndicatorFullWidth = false
tabLayout.tabMode = TabLayout.MODE_SCROLLABLE tabLayout.tabMode = TabLayout.MODE_SCROLLABLE
tabLayout.setSelectedTabIndicatorColor(requireContext().accentColor) tabLayout.setSelectedTabIndicatorColor(requireContext().accentColor)
tabLayout.setupWithViewPager(binding.viewPagerBookshelf)
binding.viewPagerBookshelf.offscreenPageLimit = 1 binding.viewPagerBookshelf.offscreenPageLimit = 1
adapter = TabFragmentPageAdapter(childFragmentManager) adapter = TabFragmentPageAdapter()
binding.viewPagerBookshelf.adapter = adapter binding.viewPagerBookshelf.adapter = adapter
TabLayoutMediator(tabLayout, binding.viewPagerBookshelf) { tab, i ->
tab.text = bookGroups[i].groupName
}.attach()
} }
private fun initBookGroupData() { private fun initBookGroupData() {
@ -165,6 +171,7 @@ class BookshelfFragment : VMBaseFragment<BookshelfViewModel>(R.layout.fragment_b
.apply { .apply {
rgLayout.checkByIndex(bookshelfLayout) rgLayout.checkByIndex(bookshelfLayout)
rgSort.checkByIndex(bookshelfSort) rgSort.checkByIndex(bookshelfSort)
swShowUnread.isChecked = AppConfig.showUnread
} }
customView { alertBinding.root } customView { alertBinding.root }
okButton { okButton {
@ -178,6 +185,10 @@ class BookshelfFragment : VMBaseFragment<BookshelfViewModel>(R.layout.fragment_b
putPrefInt(PreferKey.bookshelfSort, rgSort.getCheckedIndex()) putPrefInt(PreferKey.bookshelfSort, rgSort.getCheckedIndex())
changed = true changed = true
} }
if (AppConfig.showUnread != swShowUnread.isChecked) {
AppConfig.showUnread = swShowUnread.isChecked
changed = true
}
if (changed) { if (changed) {
activity?.recreate() activity?.recreate()
} }
@ -240,29 +251,25 @@ class BookshelfFragment : VMBaseFragment<BookshelfViewModel>(R.layout.fragment_b
}.show() }.show()
} }
private inner class TabFragmentPageAdapter(fm: FragmentManager) : private inner class TabFragmentPageAdapter :
FragmentStatePagerAdapter(fm, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) { FragmentStateAdapter(this) {
override fun getPageTitle(position: Int): CharSequence {
return bookGroups[position].groupName
}
override fun getItemPosition(`object`: Any): Int { override fun getItemId(position: Int): Long {
return POSITION_NONE val group = bookGroups[position]
return group.groupId
} }
override fun getItem(position: Int): Fragment { override fun containsItem(itemId: Long): Boolean {
val group = bookGroups[position] return fragmentMap.containsKey(itemId)
return BooksFragment.newInstance(position, group.groupId)
} }
override fun getCount(): Int { override fun getItemCount(): Int {
return bookGroups.size return bookGroups.size
} }
override fun instantiateItem(container: ViewGroup, position: Int): Any { override fun createFragment(position: Int): Fragment {
val fragment = super.instantiateItem(container, position) as BooksFragment
val group = bookGroups[position] val group = bookGroups[position]
val fragment = BooksFragment.newInstance(position, group.groupId)
fragmentMap[group.groupId] = fragment fragmentMap[group.groupId] = fragment
return 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.constant.BookType
import io.legado.app.data.entities.Book import io.legado.app.data.entities.Book
import io.legado.app.databinding.ItemBookshelfGridBinding import io.legado.app.databinding.ItemBookshelfGridBinding
import io.legado.app.lib.theme.backgroundColor import io.legado.app.help.AppConfig
import io.legado.app.utils.ColorUtils
import io.legado.app.utils.invisible import io.legado.app.utils.invisible
import splitties.views.onLongClick import splitties.views.onLongClick
@ -27,7 +26,6 @@ class BooksAdapterGrid(context: Context, private val callBack: CallBack) :
) = with(binding) { ) = with(binding) {
val bundle = payloads.getOrNull(0) as? Bundle val bundle = payloads.getOrNull(0) as? Bundle
if (bundle == null) { if (bundle == null) {
root.setBackgroundColor(ColorUtils.withAlpha(context.backgroundColor, 0.5f))
tvName.text = item.name tvName.text = item.name
ivCover.load(item.getDisplayCover(), item.name, item.author) ivCover.load(item.getDisplayCover(), item.name, item.author)
upRefresh(binding, item) upRefresh(binding, item)
@ -48,8 +46,12 @@ class BooksAdapterGrid(context: Context, private val callBack: CallBack) :
binding.rlLoading.show() binding.rlLoading.show()
} else { } else {
binding.rlLoading.hide() binding.rlLoading.hide()
if (AppConfig.showUnread) {
binding.bvUnread.setBadgeCount(item.getUnreadChapterNum()) binding.bvUnread.setBadgeCount(item.getUnreadChapterNum())
binding.bvUnread.setHighlight(item.lastCheckCount > 0) 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.constant.BookType
import io.legado.app.data.entities.Book import io.legado.app.data.entities.Book
import io.legado.app.databinding.ItemBookshelfListBinding import io.legado.app.databinding.ItemBookshelfListBinding
import io.legado.app.lib.theme.backgroundColor import io.legado.app.help.AppConfig
import io.legado.app.utils.ColorUtils
import io.legado.app.utils.invisible import io.legado.app.utils.invisible
import splitties.views.onLongClick import splitties.views.onLongClick
@ -27,7 +26,6 @@ class BooksAdapterList(context: Context, private val callBack: CallBack) :
) = with(binding) { ) = with(binding) {
val bundle = payloads.getOrNull(0) as? Bundle val bundle = payloads.getOrNull(0) as? Bundle
if (bundle == null) { if (bundle == null) {
root.setBackgroundColor(ColorUtils.withAlpha(context.backgroundColor, 0.5f))
tvName.text = item.name tvName.text = item.name
tvAuthor.text = item.author tvAuthor.text = item.author
tvRead.text = item.durChapterTitle tvRead.text = item.durChapterTitle
@ -54,8 +52,12 @@ class BooksAdapterList(context: Context, private val callBack: CallBack) :
binding.rlLoading.show() binding.rlLoading.show()
} else { } else {
binding.rlLoading.hide() binding.rlLoading.hide()
if (AppConfig.showUnread) {
binding.bvUnread.setHighlight(item.lastCheckCount > 0) binding.bvUnread.setHighlight(item.lastCheckCount > 0)
binding.bvUnread.setBadgeCount(item.getUnreadChapterNum()) binding.bvUnread.setBadgeCount(item.getUnreadChapterNum())
} else {
binding.bvUnread.invisible()
}
} }
} }

@ -22,6 +22,7 @@ class RssSortActivity : VMBaseActivity<ActivityRssArtivlesBinding, RssSortViewMo
by viewModels() by viewModels()
private var sorts = linkedMapOf<String, String>() private var sorts = linkedMapOf<String, String>()
private lateinit var adapter: TabFragmentPageAdapter private lateinit var adapter: TabFragmentPageAdapter
private val fragmentMap = hashMapOf<Long, Fragment>()
private val upSourceResult = registerForActivityResult( private val upSourceResult = registerForActivityResult(
ActivityResultContracts.StartActivityForResult() ActivityResultContracts.StartActivityForResult()
) { ) {
@ -99,12 +100,20 @@ class RssSortActivity : VMBaseActivity<ActivityRssArtivlesBinding, RssSortViewMo
return style * 100 + super.getItemId(position) return style * 100 + super.getItemId(position)
} }
override fun containsItem(itemId: Long): Boolean {
return fragmentMap.containsKey(itemId)
}
override fun createFragment(position: Int): Fragment { override fun createFragment(position: Int): Fragment {
return RssArticlesFragment.create( val itemId = getItemId(position)
val fragment = RssArticlesFragment.create(
sorts.keys.elementAt(position), sorts.keys.elementAt(position),
sorts.values.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.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import androidx.viewpager.widget.ViewPager
/** /**
* Created by GKF on 2018/3/16. * Created by GKF on 2018/3/16.
*/ */
@Suppress("MemberVisibilityCanBePrivate") @Suppress("MemberVisibilityCanBePrivate")
class ItemTouchCallback(private val callback: Callback) : ItemTouchHelper.Callback() { class ItemTouchCallback(private val callback: Callback) : ItemTouchHelper.Callback() {
private var swipeRefreshLayout: SwipeRefreshLayout? = null 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) super.onSelectedChanged(viewHolder, actionState)
val swiping = actionState == ItemTouchHelper.ACTION_STATE_DRAG val swiping = actionState == ItemTouchHelper.ACTION_STATE_DRAG
swipeRefreshLayout?.isEnabled = !swiping swipeRefreshLayout?.isEnabled = !swiping
viewPager?.requestDisallowInterceptTouchEvent(swiping)
} }
override fun clearView(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder) { 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:layout_height="match_parent"
android:orientation="vertical"> android:orientation="vertical">
<androidx.viewpager.widget.ViewPager <androidx.viewpager2.widget.ViewPager2
android:id="@+id/view_pager_main" android:id="@+id/view_pager_main"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="0dp" android:layout_height="0dp"

@ -101,5 +101,15 @@
</LinearLayout> </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> </androidx.constraintlayout.widget.ConstraintLayout>

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?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" xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
@ -10,12 +10,12 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
app:attachToActivity="false" 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:id="@+id/view_pager_bookshelf"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="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"?> <?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:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" 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_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical" android:orientation="vertical"
android:paddingTop="8dp" android:paddingTop="8dp"
android:paddingLeft="8dp" android:paddingLeft="8dp"
android:paddingRight="8dp"> android:paddingRight="8dp"
app:layout_constraintTop_toTopOf="parent">
<androidx.constraintlayout.widget.ConstraintLayout <androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent" android:layout_width="match_parent"
@ -62,6 +72,19 @@
android:textSize="12sp" android:textSize="12sp"
tools:ignore="RtlHardcoded,RtlSymmetry" /> tools:ignore="RtlHardcoded,RtlSymmetry" />
</LinearLayout> </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" app:layout_constraintVertical_bias="0.0"
tools:layout_editor_absoluteX="0dp" /> 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> </androidx.constraintlayout.widget.ConstraintLayout>

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

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

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

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

@ -827,5 +827,8 @@
<string name="url_already">此url已订阅</string> <string name="url_already">此url已订阅</string>
<string name="high_brush_title">高刷</string> <string name="high_brush_title">高刷</string>
<string name="high_brush_summary">使用屏幕最高刷新率</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> </resources>

@ -11,7 +11,8 @@ buildscript {
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:4.2.0' 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" classpath "de.timfreiheit.resourceplaceholders:placeholders:0.3"
} }
} }
@ -19,7 +20,7 @@ buildscript {
allprojects { allprojects {
repositories { repositories {
google() google()
maven { url 'https://maven.aliyun.com/repository/public/'} maven { url 'https://maven.aliyun.com/repository/public/' }
maven { url "https://jitpack.io" } maven { url "https://jitpack.io" }
maven { url "https://maven.google.com/" } maven { url "https://maven.google.com/" }
} }

Loading…
Cancel
Save