Merge remote-tracking branch 'origin/master'

# Conflicts:
#	app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfFragment.kt
#	app/src/main/java/io/legado/app/utils/MenuExtensions.kt
#	app/src/main/res/layout/activity_source_edit.xml
#	app/src/main/res/layout/view_titlebar.xml
pull/32/head
Invinciblelee 6 years ago
commit 3f3593c616
  1. 4
      app/build.gradle
  2. 7
      app/src/main/java/io/legado/app/App.kt
  3. 7
      app/src/main/java/io/legado/app/base/adapter/ItemAnimation.kt
  4. 1
      app/src/main/java/io/legado/app/base/adapter/animations/SlideInBottomAnimation.kt
  5. 1
      app/src/main/java/io/legado/app/base/adapter/animations/SlideInLeftAnimation.kt
  6. 1
      app/src/main/java/io/legado/app/base/adapter/animations/SlideInRightAnimation.kt
  7. 21
      app/src/main/java/io/legado/app/data/AppDatabase.kt
  8. 4
      app/src/main/java/io/legado/app/data/dao/BookDao.kt
  9. 15
      app/src/main/java/io/legado/app/data/dao/BookGroupDao.kt
  10. 39
      app/src/main/java/io/legado/app/data/dao/BookSourceDao.kt
  11. 10
      app/src/main/java/io/legado/app/data/dao/ReplaceRuleDao.kt
  12. 7
      app/src/main/java/io/legado/app/data/entities/Book.kt
  13. 15
      app/src/main/java/io/legado/app/data/entities/BookGroup.kt
  14. 17
      app/src/main/java/io/legado/app/data/entities/BookSource.kt
  15. 14
      app/src/main/java/io/legado/app/data/entities/Chapter.kt
  16. 10
      app/src/main/java/io/legado/app/data/entities/ExploreSearchUrl.kt
  17. 6
      app/src/main/java/io/legado/app/data/entities/ReplaceRule.kt
  18. 2
      app/src/main/java/io/legado/app/data/entities/rule/Rule.kt
  19. 127
      app/src/main/java/io/legado/app/help/ItemTouchCallback.kt
  20. 1
      app/src/main/java/io/legado/app/help/permission/RequestSource.kt
  21. 35
      app/src/main/java/io/legado/app/lib/theme/view/ATEStockSwitch.kt
  22. 1
      app/src/main/java/io/legado/app/lib/theme/view/ATESwitch.kt
  23. 17
      app/src/main/java/io/legado/app/lib/webdav/WebDav.kt
  24. 2
      app/src/main/java/io/legado/app/ui/config/ConfigActivity.kt
  25. 46
      app/src/main/java/io/legado/app/ui/config/ConfigFragment.kt
  26. 89
      app/src/main/java/io/legado/app/ui/config/ThemeConfigFragment.kt
  27. 32
      app/src/main/java/io/legado/app/ui/config/WebDavConfigFragment.kt
  28. 3
      app/src/main/java/io/legado/app/ui/main/MainActivity.kt
  29. 1
      app/src/main/java/io/legado/app/ui/main/MainViewModel.kt
  30. 38
      app/src/main/java/io/legado/app/ui/main/bookshelf/BookGroupAdapter.kt
  31. 80
      app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfAdapter.kt
  32. 40
      app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfFragment.kt
  33. 121
      app/src/main/java/io/legado/app/ui/main/booksource/BookSourceAdapter.kt
  34. 84
      app/src/main/java/io/legado/app/ui/main/booksource/BookSourceFragment.kt
  35. 3
      app/src/main/java/io/legado/app/ui/main/findbook/FindBookFragment.kt
  36. 2
      app/src/main/java/io/legado/app/ui/main/myconfig/MyConfigFragment.kt
  37. 5
      app/src/main/java/io/legado/app/ui/replacerule/ReplaceRuleActivity.kt
  38. 2
      app/src/main/java/io/legado/app/ui/replacerule/ReplaceRuleAdapter.kt
  39. 1
      app/src/main/java/io/legado/app/ui/search/SearchActivity.kt
  40. 2
      app/src/main/java/io/legado/app/ui/sourcedebug/SourceDebugActivity.kt
  41. 92
      app/src/main/java/io/legado/app/ui/sourceedit/SourceEditActivity.kt
  42. 72
      app/src/main/java/io/legado/app/ui/sourceedit/SourceEditAdapter.kt
  43. 23
      app/src/main/java/io/legado/app/ui/sourceedit/SourceEditViewModel.kt
  44. 233
      app/src/main/java/io/legado/app/ui/widget/BadgeView.kt
  45. 212
      app/src/main/java/io/legado/app/ui/widget/RotateLoading.kt
  46. 2
      app/src/main/java/io/legado/app/ui/widget/TitleBar.kt
  47. 56
      app/src/main/java/io/legado/app/ui/widget/image/CoverImageView.kt
  48. 92
      app/src/main/java/io/legado/app/ui/widget/image/FilletImageView.kt
  49. 2
      app/src/main/java/io/legado/app/utils/AnkoExtensions.kt
  50. 1
      app/src/main/java/io/legado/app/utils/StringExtensions.kt
  51. 3
      app/src/main/java/io/legado/app/utils/ViewModelKt.kt
  52. 4
      app/src/main/res/drawable-v21/bg_ib_pre.xml
  53. 3
      app/src/main/res/drawable/bg_edit.xml
  54. 9
      app/src/main/res/drawable/bg_textfield_search.xml
  55. 6
      app/src/main/res/drawable/fastscroll_bubble.xml
  56. 6
      app/src/main/res/drawable/fastscroll_handle.xml
  57. 6
      app/src/main/res/drawable/fastscroll_track.xml
  58. 39
      app/src/main/res/drawable/ic_import.xml
  59. 16
      app/src/main/res/drawable/ic_last_read.xml
  60. 230
      app/src/main/res/drawable/ic_launcher_background.xml
  61. 9
      app/src/main/res/drawable/selector_common_bg.xml
  62. 3
      app/src/main/res/drawable/shape_radius_1dp.xml
  63. 3
      app/src/main/res/layout/activity_about.xml
  64. 3
      app/src/main/res/layout/activity_main.xml
  65. 3
      app/src/main/res/layout/activity_replace_rule.xml
  66. 6
      app/src/main/res/layout/activity_search.xml
  67. 8
      app/src/main/res/layout/activity_source_edit.xml
  68. 11
      app/src/main/res/layout/fragment_book_source.xml
  69. 19
      app/src/main/res/layout/fragment_bookshelf.xml
  70. 5
      app/src/main/res/layout/fragment_find_book.xml
  71. 3
      app/src/main/res/layout/fragment_my_config.xml
  72. 5
      app/src/main/res/layout/item_book_group.xml
  73. 36
      app/src/main/res/layout/item_book_source.xml
  74. 175
      app/src/main/res/layout/item_bookshelf_list.xml
  75. 4
      app/src/main/res/layout/item_icon_preference.xml
  76. 4
      app/src/main/res/layout/item_relace_rule.xml
  77. 3
      app/src/main/res/layout/item_search.xml
  78. 11
      app/src/main/res/layout/item_source_edit.xml
  79. 17
      app/src/main/res/layout/view_title_bar.xml
  80. 6
      app/src/main/res/menu/book_source.xml
  81. 3
      app/src/main/res/menu/find_book.xml
  82. 3
      app/src/main/res/menu/my_config.xml
  83. 51
      app/src/main/res/menu/source_edit.xml
  84. 6
      app/src/main/res/values/attrs.xml
  85. 6
      app/src/main/res/values/ids.xml
  86. 6
      app/src/main/res/values/styles.xml
  87. 3
      app/src/main/res/xml/pref_config.xml
  88. 3
      app/src/main/res/xml/pref_config_theme.xml
  89. 4
      app/src/main/res/xml/pref_config_web_dav.xml
  90. 3
      app/src/main/res/xml/pref_main.xml

@ -80,7 +80,7 @@ dependencies {
//androidX //androidX
implementation 'androidx.core:core-ktx:1.2.0-alpha01' implementation 'androidx.core:core-ktx:1.2.0-alpha01'
implementation 'androidx.appcompat:appcompat:1.1.0-alpha05' implementation 'androidx.appcompat:appcompat:1.1.0-beta01'
implementation 'androidx.preference:preference:1.1.0-alpha05' implementation 'androidx.preference:preference:1.1.0-alpha05'
implementation 'androidx.constraintlayout:constraintlayout:2.0.0-beta1' implementation 'androidx.constraintlayout:constraintlayout:2.0.0-beta1'
implementation 'com.google.android.material:material:1.1.0-alpha07' implementation 'com.google.android.material:material:1.1.0-alpha07'
@ -121,7 +121,7 @@ dependencies {
//Retrofit //Retrofit
implementation 'com.squareup.okhttp3:logging-interceptor:3.14.0' implementation 'com.squareup.okhttp3:logging-interceptor:3.14.0'
implementation 'com.squareup.retrofit2:retrofit:2.5.0' implementation 'com.squareup.retrofit2:retrofit:2.6.0'
//Glide //Glide
implementation 'com.github.bumptech.glide:glide:4.9.0' implementation 'com.github.bumptech.glide:glide:4.9.0'

@ -22,6 +22,7 @@ import io.legado.app.utils.getPrefBoolean
import io.legado.app.utils.getPrefInt import io.legado.app.utils.getPrefInt
import java.util.* import java.util.*
@Suppress("DEPRECATION")
class App : Application() { class App : Application() {
companion object { companion object {
@ -96,7 +97,8 @@ class App : Application() {
*/ */
@RequiresApi(Build.VERSION_CODES.O) @RequiresApi(Build.VERSION_CODES.O)
private fun createChannelId() { private fun createChannelId() {
val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as? NotificationManager
notificationManager?.let {
//用唯一的ID创建渠道对象 //用唯一的ID创建渠道对象
val downloadChannel = NotificationChannel( val downloadChannel = NotificationChannel(
channelIdDownload, channelIdDownload,
@ -131,6 +133,7 @@ class App : Application() {
webChannel.setSound(null, null) webChannel.setSound(null, null)
//向notification manager 提交channel //向notification manager 提交channel
notificationManager.createNotificationChannels(Arrays.asList(downloadChannel, readAloudChannel, webChannel)) it.createNotificationChannels(Arrays.asList(downloadChannel, readAloudChannel, webChannel))
}
} }
} }

@ -2,12 +2,7 @@ package io.legado.app.base.adapter
import android.view.animation.Interpolator import android.view.animation.Interpolator
import android.view.animation.LinearInterpolator import android.view.animation.LinearInterpolator
import io.legado.app.base.adapter.animations.AlphaInAnimation import io.legado.app.base.adapter.animations.*
import io.legado.app.base.adapter.animations.BaseAnimation
import io.legado.app.base.adapter.animations.ScaleInAnimation
import io.legado.app.base.adapter.animations.SlideInBottomAnimation
import io.legado.app.base.adapter.animations.SlideInLeftAnimation
import io.legado.app.base.adapter.animations.SlideInRightAnimation
/** /**
* Created by Invincible on 2017/12/15. * Created by Invincible on 2017/12/15.

@ -3,7 +3,6 @@ package io.legado.app.base.adapter.animations
import android.animation.Animator import android.animation.Animator
import android.animation.ObjectAnimator import android.animation.ObjectAnimator
import android.view.View import android.view.View
import io.legado.app.base.adapter.animations.BaseAnimation
class SlideInBottomAnimation : BaseAnimation { class SlideInBottomAnimation : BaseAnimation {

@ -3,7 +3,6 @@ package io.legado.app.base.adapter.animations
import android.animation.Animator import android.animation.Animator
import android.animation.ObjectAnimator import android.animation.ObjectAnimator
import android.view.View import android.view.View
import io.legado.app.base.adapter.animations.BaseAnimation
class SlideInLeftAnimation : BaseAnimation { class SlideInLeftAnimation : BaseAnimation {

@ -3,7 +3,6 @@ package io.legado.app.base.adapter.animations
import android.animation.Animator import android.animation.Animator
import android.animation.ObjectAnimator import android.animation.ObjectAnimator
import android.view.View import android.view.View
import io.legado.app.base.adapter.animations.BaseAnimation
class SlideInRightAnimation : BaseAnimation { class SlideInRightAnimation : BaseAnimation {

@ -7,15 +7,17 @@ import androidx.room.RoomDatabase
import androidx.room.migration.Migration import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase import androidx.sqlite.db.SupportSQLiteDatabase
import io.legado.app.data.dao.BookDao import io.legado.app.data.dao.BookDao
import io.legado.app.data.dao.BookGroupDao
import io.legado.app.data.dao.BookSourceDao
import io.legado.app.data.dao.ReplaceRuleDao import io.legado.app.data.dao.ReplaceRuleDao
import io.legado.app.data.entities.Book import io.legado.app.data.entities.*
import io.legado.app.data.entities.Chapter
import io.legado.app.data.entities.ReplaceRule
import javax.xml.transform.Source
@Database(entities = [Book::class, Chapter::class, ReplaceRule::class], version = 1, exportSchema = true) @Database(
// @TypeConverters(Converters::class) entities = [Book::class, BookGroup::class, BookSource::class, Chapter::class, ReplaceRule::class],
version = 1,
exportSchema = true
)
abstract class AppDatabase : RoomDatabase() { abstract class AppDatabase : RoomDatabase() {
companion object { companion object {
@ -39,17 +41,14 @@ abstract class AppDatabase : RoomDatabase() {
// .addMigrations(MIGRATION_3_4) // .addMigrations(MIGRATION_3_4)
// .addMigrations(MIGRATION_4_5) // .addMigrations(MIGRATION_4_5)
// .addMigrations(MIGRATION_5_6) // .addMigrations(MIGRATION_5_6)
.addCallback(object : Callback() {
override fun onCreate(db: SupportSQLiteDatabase) {
super.onCreate(db)
}
})
.build() .build()
} }
} }
abstract fun bookDao(): BookDao abstract fun bookDao(): BookDao
abstract fun bookGroupDao(): BookGroupDao
abstract fun bookSourceDao(): BookSourceDao
abstract fun replaceRuleDao(): ReplaceRuleDao abstract fun replaceRuleDao(): ReplaceRuleDao
} }

@ -8,7 +8,6 @@ import androidx.room.OnConflictStrategy
import androidx.room.Query import androidx.room.Query
import io.legado.app.data.entities.Book import io.legado.app.data.entities.Book
@Dao @Dao
interface BookDao { interface BookDao {
@ -27,6 +26,9 @@ interface BookDao {
@get:Query("SELECT COUNT(*) FROM books") @get:Query("SELECT COUNT(*) FROM books")
val allBookCount: Int val allBookCount: Int
@Query("SELECT * FROM books ORDER BY durChapterTime DESC limit 0,10")
fun recentRead(): DataSource.Factory<Int, Book>
@Insert(onConflict = OnConflictStrategy.REPLACE) @Insert(onConflict = OnConflictStrategy.REPLACE)
fun insert(vararg books: Book) fun insert(vararg books: Book)

@ -0,0 +1,15 @@
package io.legado.app.data.dao
import androidx.paging.DataSource
import androidx.room.Dao
import androidx.room.Query
import io.legado.app.data.entities.BookGroup
@Dao
interface BookGroupDao {
@Query("SELECT * FROM book_groups ORDER BY `order`")
fun observeAll(): DataSource.Factory<Int, BookGroup>
}

@ -0,0 +1,39 @@
package io.legado.app.data.dao
import androidx.paging.DataSource
import androidx.room.*
import io.legado.app.data.entities.BookSource
@Dao
interface BookSourceDao {
@Query("select * from book_sources order by customOrder asc")
fun observeAll(): DataSource.Factory<Int, BookSource>
@Query("select * from book_sources where name like :searchKey or `group` like :searchKey or origin like :searchKey order by customOrder asc")
fun observeSearch(searchKey: String = ""): DataSource.Factory<Int, BookSource>
@Query("select * from book_sources where origin = :key")
fun findByKey(key: String): BookSource?
@Query("select count(*) from book_sources")
fun allCount(): Int
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insert(bookSource: BookSource): Long
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insert(vararg bookSource: BookSource)
@Update
fun update(bookSource: BookSource)
@Update
fun update(vararg bookSource: BookSource)
@Delete
fun delete(bookSource: BookSource)
@Delete
fun delete(vararg bookSource: BookSource)
}

@ -40,16 +40,20 @@ interface ReplaceRuleDao {
fun enableAll(enable: Boolean) fun enableAll(enable: Boolean)
@Insert(onConflict = OnConflictStrategy.REPLACE) @Insert(onConflict = OnConflictStrategy.REPLACE)
fun insert(vararg replaceRules: ReplaceRule) fun insert(vararg replaceRules: ReplaceRule): List<Long>
@Insert(onConflict = OnConflictStrategy.REPLACE) @Insert(onConflict = OnConflictStrategy.REPLACE)
fun insert(replaceRule: ReplaceRule): Long fun insert(replaceRule: ReplaceRule): Long
@Update
fun update(replaceRules: ReplaceRule)
@Update @Update
fun update(vararg replaceRules: ReplaceRule) fun update(vararg replaceRules: ReplaceRule)
@Delete @Delete
fun delete(vararg replaceRules: ReplaceRule) fun delete(replaceRules: ReplaceRule)
@Delete
fun delete(vararg replaceRules: ReplaceRule)
} }

@ -1,13 +1,16 @@
package io.legado.app.data.entities package io.legado.app.data.entities
import android.os.Parcelable import android.os.Parcelable
import androidx.room.* import androidx.room.Entity
import androidx.room.Index
import androidx.room.PrimaryKey
import io.legado.app.constant.AppConst.NOT_AVAILABLE import io.legado.app.constant.AppConst.NOT_AVAILABLE
import kotlinx.android.parcel.Parcelize import kotlinx.android.parcel.Parcelize
@Parcelize @Parcelize
@Entity(tableName = "books", indices = [(Index(value = ["descUrl"], unique = true))]) @Entity(tableName = "books", indices = [(Index(value = ["descUrl"], unique = true))])
data class Book(@PrimaryKey data class Book(
@PrimaryKey
var descUrl: String = "", // 详情页Url(本地书源存储完整文件路径) var descUrl: String = "", // 详情页Url(本地书源存储完整文件路径)
var tocUrl: String = "", // 目录页Url (toc=table of Contents) var tocUrl: String = "", // 目录页Url (toc=table of Contents)
var sourceId: Int = -1, // 书源规则id(默认-1,表示本地书籍) var sourceId: Int = -1, // 书源规则id(默认-1,表示本地书籍)

@ -0,0 +1,15 @@
package io.legado.app.data.entities
import android.os.Parcelable
import androidx.room.Entity
import androidx.room.PrimaryKey
import kotlinx.android.parcel.Parcelize
@Parcelize
@Entity(tableName = "book_groups")
data class BookGroup(
@PrimaryKey
var groupId: Int = 0,
var groupName: String,
var order: Int = 0
) : Parcelable

@ -1,7 +1,9 @@
package io.legado.app.data.entities package io.legado.app.data.entities
import android.os.Parcelable import android.os.Parcelable
import androidx.room.* import androidx.room.Entity
import androidx.room.Index
import androidx.room.PrimaryKey
import io.legado.app.constant.AppUtils.GSON_CONVERTER import io.legado.app.constant.AppUtils.GSON_CONVERTER
import io.legado.app.data.entities.rule.* import io.legado.app.data.entities.rule.*
import kotlinx.android.parcel.IgnoredOnParcel import kotlinx.android.parcel.IgnoredOnParcel
@ -9,15 +11,13 @@ import kotlinx.android.parcel.Parcelize
@Parcelize @Parcelize
@Entity( @Entity(
tableName = "sources", tableName = "book_sources",
indices = [(Index(value = ["sourceId"])), (Index(value = ["origin"], unique = false))] indices = [(Index(value = ["origin"], unique = false))]
) )
data class Source ( data class BookSource(
@PrimaryKey(autoGenerate = true) @PrimaryKey
@ColumnInfo(name = "sourceId")
var id: Int = 0, // 编号
var name: String = "", // 名称
var origin: String = "", // 地址,包括 http/https var origin: String = "", // 地址,包括 http/https
var name: String = "", // 名称
var type: Int = 0, // 类型,0 文本,1 音频 var type: Int = 0, // 类型,0 文本,1 音频
var group: String? = null, // 分组 var group: String? = null, // 分组
var header: String? = null, // header var header: String? = null, // header
@ -26,6 +26,7 @@ data class Source (
var lastUpdateTime: Long = 0, // 最后更新时间,用于排序 var lastUpdateTime: Long = 0, // 最后更新时间,用于排序
var customOrder: Int = 0, // 手动排序编号 var customOrder: Int = 0, // 手动排序编号
var weight: Int = 0, // 智能排序的权重 var weight: Int = 0, // 智能排序的权重
var exploreIsEnabled: Boolean = true, //启用发现
var exploreRule: String? = null, // 发现规则 var exploreRule: String? = null, // 发现规则
var searchRule: String? = null, // 搜索规则 var searchRule: String? = null, // 搜索规则
var bookInfoRule: String? = null, // 书籍信息页规则 var bookInfoRule: String? = null, // 书籍信息页规则

@ -4,18 +4,22 @@ import android.os.Parcelable
import androidx.room.Entity import androidx.room.Entity
import androidx.room.ForeignKey import androidx.room.ForeignKey
import androidx.room.Index import androidx.room.Index
import androidx.room.PrimaryKey
import kotlinx.android.parcel.Parcelize import kotlinx.android.parcel.Parcelize
@Parcelize @Parcelize
@Entity(tableName = "chapters", @Entity(
tableName = "chapters",
primaryKeys = ["url", "bookUrl"],
indices = [(Index(value = ["url"], unique = true)), (Index(value = ["bookUrl", "index"], unique = true))], indices = [(Index(value = ["url"], unique = true)), (Index(value = ["bookUrl", "index"], unique = true))],
foreignKeys = [(ForeignKey(entity = Book::class, foreignKeys = [(ForeignKey(
entity = Book::class,
parentColumns = ["descUrl"], parentColumns = ["descUrl"],
childColumns = ["bookUrl"], childColumns = ["bookUrl"],
onDelete = ForeignKey.CASCADE))]) // 删除书籍时自动删除章节 onDelete = ForeignKey.CASCADE
data class Chapter(@PrimaryKey ))]
) // 删除书籍时自动删除章节
data class Chapter(
var url: String = "", // 章节地址 var url: String = "", // 章节地址
var title: String = "", // 章节标题 var title: String = "", // 章节标题
var bookUrl: String = "", // 书籍地址 var bookUrl: String = "", // 书籍地址

@ -8,12 +8,16 @@ import androidx.room.PrimaryKey
import kotlinx.android.parcel.Parcelize import kotlinx.android.parcel.Parcelize
@Parcelize @Parcelize
@Entity(tableName = "explore_search_urls", @Entity(
tableName = "explore_search_urls",
indices = [(Index(value = ["sourceId", "url"], unique = true))], indices = [(Index(value = ["sourceId", "url"], unique = true))],
foreignKeys = [(ForeignKey(entity = Source::class, foreignKeys = [(ForeignKey(
entity = BookSource::class,
parentColumns = ["sourceId"], parentColumns = ["sourceId"],
childColumns = ["sourceId"], childColumns = ["sourceId"],
onDelete = ForeignKey.CASCADE))]) // 删除书源时自动删除章节 onDelete = ForeignKey.CASCADE
))]
) // 删除书源时自动删除章节
data class ExploreSearchUrl( data class ExploreSearchUrl(
@PrimaryKey(autoGenerate = true) @PrimaryKey(autoGenerate = true)
var esId: Int = 0, // 编号 var esId: Int = 0, // 编号

@ -8,8 +8,10 @@ import androidx.room.PrimaryKey
import kotlinx.android.parcel.Parcelize import kotlinx.android.parcel.Parcelize
@Parcelize @Parcelize
@Entity(tableName = "replace_rules", @Entity(
indices = [(Index(value = ["id"]))]) tableName = "replace_rules",
indices = [(Index(value = ["id"]))]
)
data class ReplaceRule( data class ReplaceRule(
@PrimaryKey(autoGenerate = true) @PrimaryKey(autoGenerate = true)
var id: Int = 0, var id: Int = 0,

@ -1,7 +1,7 @@
package io.legado.app.data.entities.rule package io.legado.app.data.entities.rule
import io.legado.app.utils.splitNotBlank
import io.legado.app.utils.safeTrim import io.legado.app.utils.safeTrim
import io.legado.app.utils.splitNotBlank
data class Rule( data class Rule(
var selectors: List<BaseRule>, var selectors: List<BaseRule>,

@ -0,0 +1,127 @@
package io.legado.app.help
import androidx.recyclerview.widget.GridLayoutManager
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.
*/
class ItemTouchCallback : ItemTouchHelper.Callback() {
private var swipeRefreshLayout: SwipeRefreshLayout? = null
private var viewPager: ViewPager? = null
/**
* Item操作的回调
*/
var onItemTouchCallbackListener: OnItemTouchCallbackListener? = null
/**
* 是否可以拖拽
*/
var isCanDrag = false
/**
* 是否可以被滑动
*/
var isCanSwipe = false
/**
* 当Item被长按的时候是否可以被拖拽
*/
override fun isLongPressDragEnabled(): Boolean {
return isCanDrag
}
/**
* Item是否可以被滑动(H左右滑动V上下滑动)
*/
override fun isItemViewSwipeEnabled(): Boolean {
return isCanSwipe
}
/**
* 当用户拖拽或者滑动Item的时候需要我们告诉系统滑动或者拖拽的方向
*/
override fun getMovementFlags(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder): Int {
val layoutManager = recyclerView.layoutManager
if (layoutManager is GridLayoutManager) {// GridLayoutManager
// flag如果值是0,相当于这个功能被关闭
val dragFlag = ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT or ItemTouchHelper.UP or ItemTouchHelper.DOWN
val swipeFlag = 0
// create make
return makeMovementFlags(dragFlag, swipeFlag)
} else if (layoutManager is LinearLayoutManager) {// linearLayoutManager
val linearLayoutManager = layoutManager as LinearLayoutManager?
val orientation = linearLayoutManager!!.orientation
var dragFlag = 0
var swipeFlag = 0
// 为了方便理解,相当于分为横着的ListView和竖着的ListView
if (orientation == LinearLayoutManager.HORIZONTAL) {// 如果是横向的布局
swipeFlag = ItemTouchHelper.UP or ItemTouchHelper.DOWN
dragFlag = ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT
} else if (orientation == LinearLayoutManager.VERTICAL) {// 如果是竖向的布局,相当于ListView
dragFlag = ItemTouchHelper.UP or ItemTouchHelper.DOWN
swipeFlag = ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT
}
return makeMovementFlags(dragFlag, swipeFlag)
}
return 0
}
/**
* 当Item被拖拽的时候被回调
*
* @param recyclerView recyclerView
* @param srcViewHolder 拖拽的ViewHolder
* @param targetViewHolder 目的地的viewHolder
*/
override fun onMove(
recyclerView: RecyclerView,
srcViewHolder: RecyclerView.ViewHolder,
targetViewHolder: RecyclerView.ViewHolder
): Boolean {
onItemTouchCallbackListener?.let {
return it.onMove(srcViewHolder.adapterPosition, targetViewHolder.adapterPosition)
}
return false
}
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
onItemTouchCallbackListener?.let {
return it.onSwiped(viewHolder.adapterPosition)
}
}
override fun onSelectedChanged(viewHolder: RecyclerView.ViewHolder?, actionState: Int) {
super.onSelectedChanged(viewHolder, actionState)
val swiping = actionState == ItemTouchHelper.ACTION_STATE_DRAG
swipeRefreshLayout?.isEnabled = !swiping
viewPager?.requestDisallowInterceptTouchEvent(swiping)
}
interface OnItemTouchCallbackListener {
/**
* 当某个Item被滑动删除的时候
*
* @param adapterPosition item的position
*/
fun onSwiped(adapterPosition: Int)
/**
* 当两个Item位置互换的时候被回调
*
* @param srcPosition 拖拽的item的position
* @param targetPosition 目的地的Item的position
* @return 开发者处理了操作应该返回true开发者没有处理就返回false
*/
fun onMove(srcPosition: Int, targetPosition: Int): Boolean
}
}

@ -2,7 +2,6 @@ package io.legado.app.help.permission
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import androidx.fragment.app.FragmentManager
interface RequestSource { interface RequestSource {

@ -1,35 +0,0 @@
package io.legado.app.lib.theme.view
import android.content.Context
import android.util.AttributeSet
import android.view.View
import android.widget.Switch
import androidx.appcompat.widget.SwitchCompat
import io.legado.app.lib.theme.ATH
import io.legado.app.lib.theme.ThemeStore
/**
* @author Aidan Follestad (afollestad)
*/
class ATEStockSwitch : SwitchCompat {
constructor(context: Context) : super(context) {
init(context, null)
}
constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {
init(context, attrs)
}
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {
init(context, attrs)
}
private fun init(context: Context, attrs: AttributeSet?) {
ATH.setTint(this, ThemeStore.accentColor(context))
}
override fun isShown(): Boolean {
return parent != null && visibility == View.VISIBLE
}
}

@ -3,7 +3,6 @@ package io.legado.app.lib.theme.view
import android.content.Context import android.content.Context
import android.util.AttributeSet import android.util.AttributeSet
import android.view.View import android.view.View
import android.widget.Switch
import androidx.appcompat.widget.SwitchCompat import androidx.appcompat.widget.SwitchCompat
import io.legado.app.lib.theme.ATH import io.legado.app.lib.theme.ATH
import io.legado.app.lib.theme.ThemeStore import io.legado.app.lib.theme.ThemeStore

@ -3,29 +3,16 @@ package io.legado.app.lib.webdav
import io.legado.app.lib.webdav.http.Handler import io.legado.app.lib.webdav.http.Handler
import io.legado.app.lib.webdav.http.HttpAuth import io.legado.app.lib.webdav.http.HttpAuth
import io.legado.app.lib.webdav.http.OkHttp import io.legado.app.lib.webdav.http.OkHttp
import okhttp3.*
import org.jsoup.Jsoup import org.jsoup.Jsoup
import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
import org.jsoup.select.Elements
import java.io.File import java.io.File
import java.io.FileOutputStream
import java.io.IOException import java.io.IOException
import java.io.InputStream import java.io.InputStream
import java.io.UnsupportedEncodingException import java.io.UnsupportedEncodingException
import java.lang.reflect.Field
import java.net.MalformedURLException import java.net.MalformedURLException
import java.net.URL import java.net.URL
import java.net.URLEncoder import java.net.URLEncoder
import java.util.ArrayList import java.util.*
import okhttp3.Credentials
import okhttp3.MediaType
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.RequestBody
import okhttp3.Response
class WebDav @Throws(MalformedURLException::class) class WebDav @Throws(MalformedURLException::class)
constructor(url: String) { constructor(url: String) {

@ -5,7 +5,7 @@ import io.legado.app.R
import io.legado.app.base.BaseActivity import io.legado.app.base.BaseActivity
import io.legado.app.utils.getViewModel import io.legado.app.utils.getViewModel
import kotlinx.android.synthetic.main.activity_config.* import kotlinx.android.synthetic.main.activity_config.*
import kotlinx.android.synthetic.main.view_titlebar.* import kotlinx.android.synthetic.main.view_title_bar.*
class ConfigActivity : BaseActivity<ConfigViewModel>() { class ConfigActivity : BaseActivity<ConfigViewModel>() {
override val viewModel: ConfigViewModel override val viewModel: ConfigViewModel

@ -1,15 +1,59 @@
package io.legado.app.ui.config package io.legado.app.ui.config
import android.content.SharedPreferences
import android.os.Bundle import android.os.Bundle
import androidx.preference.ListPreference
import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat import androidx.preference.PreferenceFragmentCompat
import io.legado.app.R import io.legado.app.R
import io.legado.app.utils.getPrefString
class ConfigFragment : PreferenceFragmentCompat() { class ConfigFragment : PreferenceFragmentCompat(), Preference.OnPreferenceChangeListener,
SharedPreferences.OnSharedPreferenceChangeListener {
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
addPreferencesFromResource(R.xml.pref_config) addPreferencesFromResource(R.xml.pref_config)
} }
override fun onResume() {
super.onResume()
preferenceManager.sharedPreferences.registerOnSharedPreferenceChangeListener(this)
}
override fun onPause() {
preferenceManager.sharedPreferences.unregisterOnSharedPreferenceChangeListener(this)
super.onPause()
}
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {
}
override fun onPreferenceChange(preference: Preference?, newValue: Any?): Boolean {
val stringValue = newValue.toString()
if (preference is ListPreference) {
val index = preference.findIndexOfValue(stringValue)
// Set the summary to reflect the new value.
preference.setSummary(if (index >= 0) preference.entries[index] else null)
} else {
// For all other preferences, set the summary to the value's
preference?.summary = stringValue
}
return true
}
private fun bindPreferenceSummaryToValue(preference: Preference?) {
preference?.let {
preference.onPreferenceChangeListener = this
onPreferenceChange(
preference,
preference.context.getPrefString(preference.key, "")
)
}
}
} }

@ -3,8 +3,8 @@ package io.legado.app.ui.config
import android.content.SharedPreferences import android.content.SharedPreferences
import android.os.Bundle import android.os.Bundle
import android.os.Handler import android.os.Handler
import android.os.Looper
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat import androidx.preference.PreferenceFragmentCompat
import com.jeremyliao.liveeventbus.LiveEventBus import com.jeremyliao.liveeventbus.LiveEventBus
import io.legado.app.App import io.legado.app.App
@ -36,35 +36,34 @@ class ThemeConfigFragment : PreferenceFragmentCompat(), SharedPreferences.OnShar
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) { override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {
sharedPreferences ?: return sharedPreferences ?: return
when (key) { when (key) {
"colorPrimary", "colorAccent", "colorBackground" -> "transparentStatusBar" -> {
if (!ColorUtils.isColorLight( LiveEventBus.get().with(Bus.recreate).post("")
sharedPreferences.getInt( Handler().postDelayed({ activity?.recreate() }, 100)
"colorBackground", }
App.INSTANCE.getCompatColor(R.color.md_grey_100) "colorPrimary", "colorAccent", "colorBackground" -> {
) if (backgroundIsDark(sharedPreferences)) {
) activity?.let {
) { AlertDialog.Builder(it)
AlertDialog.Builder(App.INSTANCE)
.setTitle("白天背景太暗") .setTitle("白天背景太暗")
.setMessage("将会恢复默认背景?") .setMessage("将会恢复默认背景?")
.setPositiveButton(R.string.ok) { _, _ -> .setPositiveButton(R.string.ok) { _, _ ->
App.INSTANCE.putPrefInt("colorBackground", App.INSTANCE.getCompatColor(R.color.md_grey_100)) App.INSTANCE.putPrefInt(
"colorBackground",
App.INSTANCE.getCompatColor(R.color.md_grey_100)
)
upTheme(false) upTheme(false)
} }
.setNegativeButton(R.string.cancel) { _, _ -> upTheme(false) } .setNegativeButton(R.string.cancel) { _, _ -> upTheme(false) }
.show().upTint .show().upTint
}
} else { } else {
upTheme(false) upTheme(false)
} }
"colorPrimaryNight", "colorAccentNight", "colorBackgroundNight" -> }
if (ColorUtils.isColorLight( "colorPrimaryNight", "colorAccentNight", "colorBackgroundNight" -> {
sharedPreferences.getInt( if (backgroundIsLight(sharedPreferences)) {
"colorBackgroundNight", activity?.let {
App.INSTANCE.getCompatColor(R.color.md_grey_800) AlertDialog.Builder(it)
)
)
) {
AlertDialog.Builder(App.INSTANCE)
.setTitle("夜间背景太亮") .setTitle("夜间背景太亮")
.setMessage("将会恢复默认背景?") .setMessage("将会恢复默认背景?")
.setPositiveButton(R.string.ok) { _, _ -> .setPositiveButton(R.string.ok) { _, _ ->
@ -75,12 +74,60 @@ class ThemeConfigFragment : PreferenceFragmentCompat(), SharedPreferences.OnShar
upTheme(true) upTheme(true)
} }
.setNegativeButton(R.string.cancel) { _, _ -> upTheme(true) } .setNegativeButton(R.string.cancel) { _, _ -> upTheme(true) }
.show() .show().upTint
}
} else { } else {
upTheme(true) upTheme(true)
} }
} }
}
}
override fun onPreferenceTreeClick(preference: Preference?): Boolean {
when (preference?.key) {
"defaultTheme" -> {
activity?.let {
AlertDialog.Builder(it)
.setTitle("恢复默认主题")
.setMessage("是否确认恢复?")
.setPositiveButton(R.string.ok) { _, _ ->
preferenceManager.sharedPreferences.edit()
.putInt("colorPrimary", App.INSTANCE.getCompatColor(R.color.md_grey_100))
.putInt("colorAccent", App.INSTANCE.getCompatColor(R.color.md_pink_600))
.putInt("colorBackground", App.INSTANCE.getCompatColor(R.color.md_grey_100))
.putInt("colorPrimaryNight", App.INSTANCE.getCompatColor(R.color.md_grey_800))
.putInt("colorAccentNight", App.INSTANCE.getCompatColor(R.color.md_pink_800))
.putInt("colorBackgroundNight", App.INSTANCE.getCompatColor(R.color.md_grey_800))
.apply()
App.INSTANCE.upThemeStore()
LiveEventBus.get().with(Bus.recreate).post("")
Handler().postDelayed({ activity?.recreate() }, 100)
}
.setNegativeButton(R.string.cancel, null)
.show().upTint
}
}
}
return super.onPreferenceTreeClick(preference)
}
private fun backgroundIsDark(sharedPreferences: SharedPreferences): Boolean {
return !ColorUtils.isColorLight(
sharedPreferences.getInt(
"colorBackground",
App.INSTANCE.getCompatColor(R.color.md_grey_100)
)
)
}
private fun backgroundIsLight(sharedPreferences: SharedPreferences): Boolean {
return ColorUtils.isColorLight(
sharedPreferences.getInt(
"colorBackgroundNight",
App.INSTANCE.getCompatColor(R.color.md_grey_800)
)
)
} }
private fun upTheme(isNightTheme: Boolean) { private fun upTheme(isNightTheme: Boolean) {

@ -1,13 +1,43 @@
package io.legado.app.ui.config package io.legado.app.ui.config
import android.os.Bundle import android.os.Bundle
import androidx.preference.ListPreference
import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat import androidx.preference.PreferenceFragmentCompat
import io.legado.app.R import io.legado.app.R
import io.legado.app.utils.getPrefString
class WebDavConfigFragment : PreferenceFragmentCompat() { class WebDavConfigFragment : PreferenceFragmentCompat(), Preference.OnPreferenceChangeListener {
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
addPreferencesFromResource(R.xml.pref_config_web_dav) addPreferencesFromResource(R.xml.pref_config_web_dav)
bindPreferenceSummaryToValue(findPreference("web_dav_url"))
bindPreferenceSummaryToValue(findPreference("web_dav_account"))
}
override fun onPreferenceChange(preference: Preference?, newValue: Any?): Boolean {
val stringValue = newValue.toString()
if (preference is ListPreference) {
val index = preference.findIndexOfValue(stringValue)
// Set the summary to reflect the new value.
preference.setSummary(if (index >= 0) preference.entries[index] else null)
} else {
// For all other preferences, set the summary to the value's
preference?.summary = stringValue
}
return true
}
private fun bindPreferenceSummaryToValue(preference: Preference?) {
preference?.let {
preference.onPreferenceChangeListener = this
onPreferenceChange(
preference,
preference.context.getPrefString(preference.key, "")
)
}
} }
} }

@ -1,7 +1,6 @@
package io.legado.app.ui.main package io.legado.app.ui.main
import android.os.Bundle import android.os.Bundle
import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager import androidx.fragment.app.FragmentManager
@ -16,6 +15,7 @@ import io.legado.app.constant.Bus
import io.legado.app.help.permission.Permissions import io.legado.app.help.permission.Permissions
import io.legado.app.help.permission.PermissionsCompat import io.legado.app.help.permission.PermissionsCompat
import io.legado.app.help.storage.Restore import io.legado.app.help.storage.Restore
import io.legado.app.lib.theme.ThemeStore
import io.legado.app.ui.main.bookshelf.BookshelfFragment import io.legado.app.ui.main.bookshelf.BookshelfFragment
import io.legado.app.ui.main.booksource.BookSourceFragment import io.legado.app.ui.main.booksource.BookSourceFragment
import io.legado.app.ui.main.findbook.FindBookFragment import io.legado.app.ui.main.findbook.FindBookFragment
@ -33,6 +33,7 @@ class MainActivity : BaseActivity<MainViewModel>(), BottomNavigationView.OnNavig
get() = R.layout.activity_main get() = R.layout.activity_main
override fun onViewModelCreated(viewModel: MainViewModel, savedInstanceState: Bundle?) { override fun onViewModelCreated(viewModel: MainViewModel, savedInstanceState: Bundle?) {
bottom_navigation_view.setBackgroundColor(ThemeStore.backgroundColor(this))
view_pager_main.offscreenPageLimit = 3 view_pager_main.offscreenPageLimit = 3
view_pager_main.adapter = TabFragmentPageAdapter(supportFragmentManager) view_pager_main.adapter = TabFragmentPageAdapter(supportFragmentManager)
view_pager_main.addOnPageChangeListener(this) view_pager_main.addOnPageChangeListener(this)

@ -6,5 +6,4 @@ import androidx.lifecycle.AndroidViewModel
class MainViewModel(application: Application) : AndroidViewModel(application) { class MainViewModel(application: Application) : AndroidViewModel(application) {
} }

@ -0,0 +1,38 @@
package io.legado.app.ui.main.bookshelf
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.paging.PagedListAdapter
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView
import io.legado.app.R
import io.legado.app.data.entities.BookGroup
class BookGroupAdapter : PagedListAdapter<BookGroup, BookGroupAdapter.MyViewHolder>(DIFF_CALLBACK) {
companion object {
@JvmField
val DIFF_CALLBACK = object : DiffUtil.ItemCallback<BookGroup>() {
override fun areItemsTheSame(oldItem: BookGroup, newItem: BookGroup): Boolean =
oldItem.groupId == newItem.groupId
override fun areContentsTheSame(oldItem: BookGroup, newItem: BookGroup): Boolean =
oldItem.groupId == newItem.groupId
&& oldItem.groupName == newItem.groupName
&& oldItem.order == newItem.order
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
return MyViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.item_book_group, parent, false))
}
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
}
class MyViewHolder(view: View) : RecyclerView.ViewHolder(view) {
}
}

@ -0,0 +1,80 @@
package io.legado.app.ui.main.bookshelf
import android.text.TextUtils.isEmpty
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.paging.PagedListAdapter
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide
import com.bumptech.glide.load.engine.DiskCacheStrategy
import io.legado.app.R
import io.legado.app.data.entities.Book
import io.legado.app.lib.theme.ThemeStore
import kotlinx.android.synthetic.main.item_bookshelf_list.view.*
import kotlinx.android.synthetic.main.item_relace_rule.view.tv_name
import java.io.File
class BookshelfAdapter : PagedListAdapter<Book, BookshelfAdapter.MyViewHolder>(DIFF_CALLBACK) {
companion object {
@JvmField
val DIFF_CALLBACK = object : DiffUtil.ItemCallback<Book>() {
override fun areItemsTheSame(oldItem: Book, newItem: Book): Boolean =
oldItem.descUrl == newItem.descUrl
override fun areContentsTheSame(oldItem: Book, newItem: Book): Boolean =
oldItem.descUrl == newItem.descUrl
&& oldItem.durChapterTitle == newItem.durChapterTitle
&& oldItem.latestChapterTitle == newItem.latestChapterTitle
}
}
var callBack: CallBack? = null
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
return MyViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.item_bookshelf_list, parent, false))
}
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
currentList?.get(position)?.let {
holder.bind(it, callBack)
}
}
class MyViewHolder(view: View) : RecyclerView.ViewHolder(view) {
init {
itemView.setBackgroundColor(ThemeStore.backgroundColor(itemView.context))
}
fun bind(book: Book, callBack: CallBack?) = with(itemView) {
tv_name.text = book.name
tv_author.text = book.author
tv_read.text = book.durChapterTitle
tv_last.text = book.latestChapterTitle
val cover = if (isEmpty(book.customCoverUrl)) book.coverUrl else book.customCoverUrl
cover?.let {
if (it.startsWith("http")) {
Glide.with(itemView).load(it)
.placeholder(R.drawable.img_cover_default)
.centerCrop()
.diskCacheStrategy(DiskCacheStrategy.RESOURCE)
.into(iv_cover)
} else {
Glide.with(itemView).load(File(it))
.placeholder(R.drawable.img_cover_default)
.centerCrop()
.diskCacheStrategy(DiskCacheStrategy.RESOURCE)
.into(iv_cover)
}
}
itemView.setOnClickListener { callBack?.open(book) }
}
}
interface CallBack {
fun open(book: Book)
}
}

@ -3,20 +3,56 @@ package io.legado.app.ui.main.bookshelf
import android.os.Bundle import android.os.Bundle
import android.view.Menu import android.view.Menu
import android.view.View import android.view.View
import androidx.core.view.forEach import androidx.lifecycle.LiveData
import androidx.lifecycle.Observer
import androidx.paging.LivePagedListBuilder
import androidx.paging.PagedList
import androidx.recyclerview.widget.LinearLayoutManager
import io.legado.app.App
import io.legado.app.R import io.legado.app.R
import io.legado.app.base.BaseFragment import io.legado.app.base.BaseFragment
import kotlinx.android.synthetic.main.view_titlebar.* import io.legado.app.data.entities.Book
import io.legado.app.data.entities.BookGroup
import kotlinx.android.synthetic.main.fragment_bookshelf.*
import kotlinx.android.synthetic.main.view_title_bar.*
class BookshelfFragment : BaseFragment(R.layout.fragment_bookshelf) { class BookshelfFragment : BaseFragment(R.layout.fragment_bookshelf) {
private lateinit var bookshelfAdapter: BookshelfAdapter
private lateinit var bookGroupAdapter: BookGroupAdapter
private var bookGroupLiveData: LiveData<PagedList<BookGroup>>? = null
private var bookshelfLiveData: LiveData<PagedList<Book>>? = null
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
setSupportToolbar(toolbar) setSupportToolbar(toolbar)
initRecyclerView()
initBookGroupData()
initBookshelfData()
} }
override fun onCompatCreateOptionsMenu(menu: Menu) { override fun onCompatCreateOptionsMenu(menu: Menu) {
menuInflater.inflate(R.menu.bookshelf, menu) menuInflater.inflate(R.menu.bookshelf, menu)
} }
private fun initRecyclerView() {
rv_book_group.layoutManager = LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false)
bookGroupAdapter = BookGroupAdapter()
rv_book_group.adapter = bookGroupAdapter
rv_bookshelf.layoutManager = LinearLayoutManager(context)
bookshelfAdapter = BookshelfAdapter()
rv_bookshelf.adapter = bookshelfAdapter
}
private fun initBookGroupData() {
bookGroupLiveData?.removeObservers(viewLifecycleOwner)
bookGroupLiveData = LivePagedListBuilder(App.db.bookGroupDao().observeAll(), 10).build()
bookGroupLiveData?.observe(viewLifecycleOwner, Observer { bookGroupAdapter.submitList(it) })
}
private fun initBookshelfData() {
bookshelfLiveData?.removeObservers(viewLifecycleOwner)
bookshelfLiveData = LivePagedListBuilder(App.db.bookDao().recentRead(), 20).build()
bookshelfLiveData?.observe(viewLifecycleOwner, Observer { bookshelfAdapter.submitList(it) })
}
} }

@ -1,2 +1,123 @@
package io.legado.app.ui.main.booksource package io.legado.app.ui.main.booksource
import android.view.LayoutInflater
import android.view.Menu
import android.view.View
import android.view.ViewGroup
import android.widget.PopupMenu
import androidx.paging.PagedListAdapter
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView
import io.legado.app.R
import io.legado.app.data.entities.BookSource
import io.legado.app.help.ItemTouchCallback.OnItemTouchCallbackListener
import io.legado.app.lib.theme.ThemeStore
import kotlinx.android.synthetic.main.item_book_source.view.*
import org.jetbrains.anko.sdk27.listeners.onClick
class BookSourceAdapter : PagedListAdapter<BookSource, BookSourceAdapter.MyViewHolder>(DIFF_CALLBACK) {
companion object {
@JvmField
val DIFF_CALLBACK = object : DiffUtil.ItemCallback<BookSource>() {
override fun areItemsTheSame(oldItem: BookSource, newItem: BookSource): Boolean =
oldItem.origin == newItem.origin
override fun areContentsTheSame(oldItem: BookSource, newItem: BookSource): Boolean =
oldItem.origin == newItem.origin
&& oldItem.name == newItem.name
&& oldItem.group == newItem.group
&& oldItem.isEnabled == newItem.isEnabled
}
}
var callBack: CallBack? = null
val checkedList = HashSet<String>()
val itemTouchCallbackListener = object : OnItemTouchCallbackListener {
override fun onSwiped(adapterPosition: Int) {
}
override fun onMove(srcPosition: Int, targetPosition: Int): Boolean {
currentList?.let {
val srcSource = it[srcPosition]
val targetSource = it[targetPosition]
srcSource?.let { a ->
targetSource?.let { b ->
a.customOrder = targetPosition
b.customOrder = srcPosition
callBack?.update(a, b)
}
}
}
return true
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
return MyViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.item_book_source, parent, false))
}
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
getItem(position)?.let { holder.bind(it, checkedList, callBack) }
}
class MyViewHolder(view: View) : RecyclerView.ViewHolder(view) {
init {
itemView.setBackgroundColor(ThemeStore.backgroundColor(itemView.context))
}
fun bind(bookSource: BookSource, checkedList: HashSet<String>, callBack: CallBack?) = with(itemView) {
cb_book_source.text = String.format("%s (%s)", bookSource.name, bookSource.group)
cb_book_source.onClick {
if (cb_book_source.isChecked) {
checkedList.add(bookSource.origin)
} else {
checkedList.remove(bookSource.origin)
}
}
sw_enabled.isChecked = bookSource.isEnabled
sw_enabled.setOnClickListener {
bookSource.isEnabled = sw_enabled.isChecked
callBack?.update(bookSource)
}
iv_more.setOnClickListener {
val popupMenu = PopupMenu(context, iv_more)
popupMenu.menu.add(Menu.NONE, R.id.menu_edit, Menu.NONE, R.string.edit)
popupMenu.menu.add(Menu.NONE, R.id.menu_del, Menu.NONE, R.string.delete)
popupMenu.menu.add(Menu.NONE, R.id.menu_top, Menu.NONE, R.string.to_top)
popupMenu.setOnMenuItemClickListener {
when (it.itemId) {
R.id.menu_edit -> {
callBack?.edit(bookSource)
true
}
R.id.menu_del -> {
callBack?.del(bookSource)
true
}
R.id.menu_top -> {
true
}
else -> {
false
}
}
}
popupMenu.show()
}
}
}
interface CallBack {
fun del(bookSource: BookSource)
fun edit(bookSource: BookSource)
fun update(bookSource: BookSource)
fun update(vararg bookSource: BookSource)
}
}

@ -4,14 +4,37 @@ 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 androidx.appcompat.widget.SearchView
import androidx.lifecycle.LiveData
import androidx.lifecycle.Observer
import androidx.paging.LivePagedListBuilder
import androidx.paging.PagedList
import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.LinearLayoutManager
import io.legado.app.App
import io.legado.app.R import io.legado.app.R
import io.legado.app.base.BaseFragment import io.legado.app.base.BaseFragment
import kotlinx.android.synthetic.main.view_titlebar.* import io.legado.app.data.entities.BookSource
import io.legado.app.help.ItemTouchCallback
import io.legado.app.ui.sourceedit.SourceEditActivity
import kotlinx.android.synthetic.main.fragment_book_source.*
import kotlinx.android.synthetic.main.view_title_bar.*
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import org.jetbrains.anko.startActivity
class BookSourceFragment : BaseFragment(R.layout.fragment_book_source) { class BookSourceFragment : BaseFragment(R.layout.fragment_book_source), BookSourceAdapter.CallBack,
SearchView.OnQueryTextListener {
private lateinit var adapter: BookSourceAdapter
private var bookSourceLiveDate: LiveData<PagedList<BookSource>>? = null
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
setSupportToolbar(toolbar) setSupportToolbar(toolbar)
initRecyclerView()
initDataObserve()
initSearchView()
} }
override fun onCompatCreateOptionsMenu(menu: Menu) { override fun onCompatCreateOptionsMenu(menu: Menu) {
@ -19,8 +42,65 @@ class BookSourceFragment : BaseFragment(R.layout.fragment_book_source) {
} }
override fun onCompatOptionsItemSelected(item: MenuItem) { override fun onCompatOptionsItemSelected(item: MenuItem) {
when (item.itemId) {
R.id.action_add_book_source -> {
context?.startActivity<SourceEditActivity>()
}
}
}
private fun initRecyclerView() {
recycler_view.layoutManager = LinearLayoutManager(context)
recycler_view.addItemDecoration(DividerItemDecoration(context, LinearLayoutManager.VERTICAL))
adapter = BookSourceAdapter()
adapter.callBack = this
recycler_view.adapter = adapter
val itemTouchCallback = ItemTouchCallback()
itemTouchCallback.onItemTouchCallbackListener = adapter.itemTouchCallbackListener
itemTouchCallback.isCanDrag = true
ItemTouchHelper(itemTouchCallback).attachToRecyclerView(recycler_view)
}
private fun initSearchView() {
search_view.visibility = View.VISIBLE
search_view.onActionViewExpanded()
search_view.queryHint = getString(R.string.search_book_source)
search_view.clearFocus()
search_view.setOnQueryTextListener(this)
}
private fun initDataObserve(searchKey: String = "") {
bookSourceLiveDate?.removeObservers(viewLifecycleOwner)
val dataFactory =
if (searchKey.isEmpty()) App.db.bookSourceDao().observeAll() else App.db.bookSourceDao().observeSearch(
searchKey
)
bookSourceLiveDate = LivePagedListBuilder(dataFactory, 30).build()
bookSourceLiveDate?.observe(viewLifecycleOwner, Observer { adapter.submitList(it) })
}
override fun onQueryTextChange(newText: String?): Boolean {
newText?.let { initDataObserve("%$it%") }
return false
} }
override fun onQueryTextSubmit(query: String?): Boolean {
return false
}
override fun del(bookSource: BookSource) {
GlobalScope.launch { App.db.bookSourceDao().delete(bookSource) }
}
override fun update(bookSource: BookSource) {
GlobalScope.launch { App.db.bookSourceDao().update(bookSource) }
}
override fun update(vararg bookSource: BookSource) {
GlobalScope.launch { App.db.bookSourceDao().update(*bookSource) }
}
override fun edit(bookSource: BookSource) {
context?.let { it.startActivity<SourceEditActivity>(Pair("data", bookSource.origin)) }
}
} }

@ -2,11 +2,10 @@ package io.legado.app.ui.main.findbook
import android.os.Bundle import android.os.Bundle
import android.view.Menu import android.view.Menu
import android.view.MenuItem
import android.view.View import android.view.View
import io.legado.app.R import io.legado.app.R
import io.legado.app.base.BaseFragment import io.legado.app.base.BaseFragment
import kotlinx.android.synthetic.main.view_titlebar.* import kotlinx.android.synthetic.main.view_title_bar.*
class FindBookFragment : BaseFragment(R.layout.fragment_find_book) { class FindBookFragment : BaseFragment(R.layout.fragment_find_book) {

@ -7,7 +7,7 @@ import android.view.View
import io.legado.app.R import io.legado.app.R
import io.legado.app.base.BaseFragment import io.legado.app.base.BaseFragment
import io.legado.app.ui.search.SearchActivity import io.legado.app.ui.search.SearchActivity
import kotlinx.android.synthetic.main.view_titlebar.* import kotlinx.android.synthetic.main.view_title_bar.*
import org.jetbrains.anko.startActivity import org.jetbrains.anko.startActivity
class MyConfigFragment : BaseFragment(R.layout.fragment_my_config) { class MyConfigFragment : BaseFragment(R.layout.fragment_my_config) {

@ -47,12 +47,14 @@ class ReplaceRuleActivity : BaseActivity<ReplaceRuleViewModel>() {
updateEnableAll() updateEnableAll()
} }
} }
override fun delete(rule: ReplaceRule) { override fun delete(rule: ReplaceRule) {
doAsync { doAsync {
App.db.replaceRuleDao().delete(rule) App.db.replaceRuleDao().delete(rule)
updateEnableAll() updateEnableAll()
} }
} }
override fun edit(rule: ReplaceRule) { override fun edit(rule: ReplaceRule) {
doAsync { doAsync {
App.db.replaceRuleDao().enableAll(!allEnabled) App.db.replaceRuleDao().enableAll(!allEnabled)
@ -67,7 +69,8 @@ class ReplaceRuleActivity : BaseActivity<ReplaceRuleViewModel>() {
DividerItemDecoration(this, DividerItemDecoration.VERTICAL).apply { DividerItemDecoration(this, DividerItemDecoration.VERTICAL).apply {
ContextCompat.getDrawable(baseContext, R.drawable.ic_divider)?.let { ContextCompat.getDrawable(baseContext, R.drawable.ic_divider)?.let {
Log.e(APP_TAG, it.toString()) Log.e(APP_TAG, it.toString())
this.setDrawable(it) } this.setDrawable(it)
}
}) })
} }

@ -1,7 +1,6 @@
package io.legado.app.ui.replacerule package io.legado.app.ui.replacerule
import android.content.Context import android.content.Context
import android.util.Log
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
@ -10,7 +9,6 @@ import androidx.paging.PagedListAdapter
import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import io.legado.app.R import io.legado.app.R
import io.legado.app.constant.AppConst.APP_TAG
import io.legado.app.data.entities.ReplaceRule import io.legado.app.data.entities.ReplaceRule
import kotlinx.android.synthetic.main.item_relace_rule.view.* import kotlinx.android.synthetic.main.item_relace_rule.view.*
import org.jetbrains.anko.sdk27.listeners.onClick import org.jetbrains.anko.sdk27.listeners.onClick

@ -4,7 +4,6 @@ import android.os.Bundle
import io.legado.app.R import io.legado.app.R
import io.legado.app.base.BaseActivity import io.legado.app.base.BaseActivity
import io.legado.app.utils.getViewModel import io.legado.app.utils.getViewModel
import org.jetbrains.anko.startActivity
class SearchActivity : BaseActivity<SearchViewModel>() { class SearchActivity : BaseActivity<SearchViewModel>() {

@ -1,9 +1,19 @@
package io.legado.app.ui.sourceedit package io.legado.app.ui.sourceedit
import android.os.Bundle import android.os.Bundle
import android.view.Menu
import android.view.MenuItem
import androidx.lifecycle.Observer
import androidx.recyclerview.widget.LinearLayoutManager
import io.legado.app.R import io.legado.app.R
import io.legado.app.base.BaseActivity import io.legado.app.base.BaseActivity
import io.legado.app.data.entities.BookSource
import io.legado.app.utils.getViewModel import io.legado.app.utils.getViewModel
import kotlinx.android.synthetic.main.activity_source_edit.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import org.jetbrains.anko.toast
class SourceEditActivity : BaseActivity<SourceEditViewModel>() { class SourceEditActivity : BaseActivity<SourceEditViewModel>() {
override val viewModel: SourceEditViewModel override val viewModel: SourceEditViewModel
@ -11,9 +21,91 @@ class SourceEditActivity : BaseActivity<SourceEditViewModel>() {
override val layoutID: Int override val layoutID: Int
get() = R.layout.activity_source_edit get() = R.layout.activity_source_edit
private val adapter = SourceEditAdapter()
private val sourceEditEntities: ArrayList<SourceEditEntity> = ArrayList()
override fun onViewModelCreated(viewModel: SourceEditViewModel, savedInstanceState: Bundle?) { override fun onViewModelCreated(viewModel: SourceEditViewModel, savedInstanceState: Bundle?) {
initRecyclerView()
viewModel.sourceLiveData.observe(this, Observer {
upRecyclerView(it)
})
if (viewModel.sourceLiveData.value == null) {
val sourceID = intent.getStringExtra("data")
if (sourceID == null) {
upRecyclerView(null)
} else {
sourceID.let { viewModel.setBookSource(sourceID) }
}
} else {
upRecyclerView(viewModel.sourceLiveData.value)
}
}
override fun onCompatCreateOptionsMenu(menu: Menu): Boolean {
menuInflater.inflate(R.menu.source_edit, menu)
return super.onCompatCreateOptionsMenu(menu)
}
override fun onCompatOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
R.id.action_save -> {
val bookSource = getSource()
if (bookSource == null) {
toast("书源名称和URL不能为空")
} else {
GlobalScope.launch {
viewModel.save(bookSource)
GlobalScope.launch(Dispatchers.Main) { finish() }
}
}
}
}
return super.onCompatOptionsItemSelected(item)
}
private fun initRecyclerView() {
recycler_view.layoutManager = LinearLayoutManager(this)
recycler_view.adapter = adapter
}
private fun upRecyclerView(bookSource: BookSource?) {
bookSource?.let {
cb_is_enable.isChecked = it.isEnabled
cb_is_enable_find.isChecked = it.exploreIsEnabled
}
sourceEditEntities.clear()
sourceEditEntities.add(SourceEditEntity("origin", bookSource?.origin, R.string.book_source_url))
sourceEditEntities.add(SourceEditEntity("name", bookSource?.name, R.string.book_source_name))
sourceEditEntities.add(SourceEditEntity("group", bookSource?.group, R.string.book_source_group))
adapter.sourceEditEntities = sourceEditEntities
adapter.notifyDataSetChanged()
} }
private fun getSource(): BookSource? {
val bookSource = BookSource()
bookSource.isEnabled = cb_is_enable.isChecked
bookSource.exploreIsEnabled = cb_is_enable_find.isChecked
for (entity in adapter.sourceEditEntities) {
when (entity.key) {
"origin" -> {
if (entity.value == null) {
return null
} else {
bookSource.origin = entity.value!!
}
}
"name" -> {
if (entity.value == null) {
return null
} else {
bookSource.name = entity.value!!
}
}
"group" -> bookSource.group = entity.value
}
}
return bookSource
}
class SourceEditEntity(var key: String, var value: String?, var hint: Int)
} }

@ -0,0 +1,72 @@
package io.legado.app.ui.sourceedit
import android.text.Editable
import android.text.TextWatcher
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import io.legado.app.R
import kotlinx.android.synthetic.main.item_source_edit.view.*
class SourceEditAdapter : RecyclerView.Adapter<SourceEditAdapter.MyViewHolder>() {
var sourceEditEntities: ArrayList<SourceEditActivity.SourceEditEntity> = ArrayList()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
return MyViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.item_source_edit, parent, false))
}
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
holder.bind(sourceEditEntities[position])
}
override fun getItemCount(): Int {
return sourceEditEntities.size
}
class MyViewHolder(view: View) : RecyclerView.ViewHolder(view) {
fun bind(sourceEditEntity: SourceEditActivity.SourceEditEntity) = with(itemView) {
if (editText.getTag(R.id.tag1) == null) {
val listener = object : View.OnAttachStateChangeListener {
override fun onViewAttachedToWindow(v: View) {
editText.isCursorVisible = false
editText.isCursorVisible = true
editText.isFocusable = true
editText.isFocusableInTouchMode = true
}
override fun onViewDetachedFromWindow(v: View) {
}
}
editText.addOnAttachStateChangeListener(listener)
editText.setTag(R.id.tag1, listener)
}
editText.getTag(R.id.tag2)?.let {
if (it is TextWatcher) {
editText.removeTextChangedListener(it)
}
}
editText.setText(sourceEditEntity.value)
textInputLayout.hint = context.getString(sourceEditEntity.hint)
val textWatcher = object : TextWatcher {
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {
}
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
}
override fun afterTextChanged(s: Editable?) {
sourceEditEntity.value = (s?.toString())
}
}
editText.addTextChangedListener(textWatcher)
editText.setTag(R.id.tag2, textWatcher)
}
}
}

@ -2,7 +2,30 @@ package io.legado.app.ui.sourceedit
import android.app.Application import android.app.Application
import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.MutableLiveData
import io.legado.app.App
import io.legado.app.data.entities.BookSource
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
class SourceEditViewModel(application: Application) : AndroidViewModel(application) { class SourceEditViewModel(application: Application) : AndroidViewModel(application) {
val sourceLiveData: MutableLiveData<BookSource> = MutableLiveData()
fun setBookSource(key: String) {
GlobalScope.launch {
val source = App.db.bookSourceDao().findByKey(key)
sourceLiveData.postValue(source)
}
}
fun save(bookSource: BookSource) {
val source = App.db.bookSourceDao().findByKey(bookSource.origin)
if (source == null) {
bookSource.customOrder = App.db.bookSourceDao().allCount()
}
App.db.bookSourceDao().insert(bookSource)
}
} }

@ -0,0 +1,233 @@
package io.legado.app.ui.widget
import android.content.Context
import android.graphics.Color
import android.graphics.drawable.ShapeDrawable
import android.graphics.drawable.shapes.RoundRectShape
import android.text.TextUtils
import android.util.AttributeSet
import android.util.TypedValue
import android.view.Gravity
import android.view.View
import android.view.ViewGroup
import android.widget.FrameLayout
import android.widget.FrameLayout.LayoutParams
import android.widget.TabWidget
import androidx.appcompat.widget.AppCompatTextView
import io.legado.app.R
/**
* Created by milad heydari on 5/6/2016.
*/
class BadgeView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyle: Int = android.R.attr.textViewStyle
) : AppCompatTextView(context, attrs, defStyle) {
/**
* @return Returns true if view is hidden on badge value 0 or null;
*/
/**
* @param hideOnNull the hideOnNull to set
*/
var isHideOnNull = true
set(hideOnNull) {
field = hideOnNull
text = text
}
private var radius: Float = 0.toFloat()
val badgeCount: Int?
get() {
if (text == null) {
return null
}
val text = text.toString()
try {
return Integer.parseInt(text)
} catch (e: NumberFormatException) {
return null
}
}
var badgeGravity: Int
get() {
val params = layoutParams as LayoutParams
return params.gravity
}
set(gravity) {
val params = layoutParams as LayoutParams
params.gravity = gravity
layoutParams = params
}
val badgeMargin: IntArray
get() {
val params = layoutParams as LayoutParams
return intArrayOf(params.leftMargin, params.topMargin, params.rightMargin, params.bottomMargin)
}
init {
init()
}
private fun init() {
if (layoutParams !is LayoutParams) {
val layoutParams = LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT,
Gravity.CENTER
)
setLayoutParams(layoutParams)
}
// set default font
setTextColor(Color.WHITE)
//setTypeface(Typeface.DEFAULT_BOLD);
setTextSize(TypedValue.COMPLEX_UNIT_SP, 11f)
setPadding(dip2Px(5f), dip2Px(1f), dip2Px(5f), dip2Px(1f))
radius = 8f
// set default background
setBackground(radius, Color.parseColor("#d3321b"))
gravity = Gravity.CENTER
// default values
isHideOnNull = true
setBadgeCount(0)
minWidth = dip2Px(16f)
minHeight = dip2Px(16f)
}
fun setBackground(dipRadius: Float, badgeColor: Int) {
val radius = dip2Px(dipRadius)
val radiusArray = floatArrayOf(
radius.toFloat(),
radius.toFloat(),
radius.toFloat(),
radius.toFloat(),
radius.toFloat(),
radius.toFloat(),
radius.toFloat(),
radius.toFloat()
)
val roundRect = RoundRectShape(radiusArray, null, null)
val bgDrawable = ShapeDrawable(roundRect)
bgDrawable.paint.color = badgeColor
background = bgDrawable
}
fun setBackground(badgeColor: Int) {
setBackground(radius, badgeColor)
}
/**
* @see android.widget.TextView.setText
*/
override fun setText(text: CharSequence, type: BufferType) {
if (isHideOnNull && TextUtils.isEmpty(text)) {
visibility = View.GONE
} else {
visibility = View.VISIBLE
}
super.setText(text, type)
}
fun setBadgeCount(count: Int) {
text = count.toString()
if (count == 0) {
visibility = View.GONE
}
}
fun setHighlight(highlight: Boolean) {
setBackground(resources.getColor(if (highlight) R.color.highlight else R.color.darker_gray))
}
fun setBadgeMargin(dipMargin: Int) {
setBadgeMargin(dipMargin, dipMargin, dipMargin, dipMargin)
}
fun setBadgeMargin(leftDipMargin: Int, topDipMargin: Int, rightDipMargin: Int, bottomDipMargin: Int) {
val params = layoutParams as LayoutParams
params.leftMargin = dip2Px(leftDipMargin.toFloat())
params.topMargin = dip2Px(topDipMargin.toFloat())
params.rightMargin = dip2Px(rightDipMargin.toFloat())
params.bottomMargin = dip2Px(bottomDipMargin.toFloat())
layoutParams = params
}
fun incrementBadgeCount(increment: Int) {
val count = badgeCount
if (count == null) {
setBadgeCount(increment)
} else {
setBadgeCount(increment + count)
}
}
fun decrementBadgeCount(decrement: Int) {
incrementBadgeCount(-decrement)
}
/**
* Attach the BadgeView to the TabWidget
* @param target the TabWidget to attach the BadgeView
* @param tabIndex index of the tab
*/
fun setTargetView(target: TabWidget, tabIndex: Int) {
val tabView = target.getChildTabViewAt(tabIndex)
setTargetView(tabView)
}
/**
* Attach the BadgeView to the target view
* @param target the view to attach the BadgeView
*/
fun setTargetView(target: View?) {
if (parent != null) {
(parent as ViewGroup).removeView(this)
}
if (target == null) {
return
}
if (target.parent is FrameLayout) {
(target.parent as FrameLayout).addView(this)
} else if (target.parent is ViewGroup) {
// use a new FrameLayout container for adding badge
val parentContainer = target.parent as ViewGroup
val groupIndex = parentContainer.indexOfChild(target)
parentContainer.removeView(target)
val badgeContainer = FrameLayout(context)
val parentLayoutParams = target.layoutParams
badgeContainer.layoutParams = parentLayoutParams
target.layoutParams = ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT
)
parentContainer.addView(badgeContainer, groupIndex, parentLayoutParams)
badgeContainer.addView(target)
badgeContainer.addView(this)
}
}
/**
* converts dip to px
*/
private fun dip2Px(dip: Float): Int {
return (dip * context.resources.displayMetrics.density + 0.5f).toInt()
}
}

@ -0,0 +1,212 @@
package io.legado.app.ui.widget
import android.animation.Animator
import android.animation.AnimatorSet
import android.animation.ObjectAnimator
import android.content.Context
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.graphics.RectF
import android.util.AttributeSet
import android.util.TypedValue
import android.view.View
import android.view.animation.LinearInterpolator
import io.legado.app.R
/**
* RotateLoading
* Created by Victor on 2015/4/28.
*/
class RotateLoading : View {
private var mPaint: Paint? = null
private var loadingRectF: RectF? = null
private var shadowRectF: RectF? = null
private var topDegree = 10
private var bottomDegree = 190
private var arc: Float = 0.toFloat()
private var thisWidth: Int = 0
private var changeBigger = true
private var shadowPosition: Int = 0
var isStart = false
private set
var loadingColor: Int = 0
private var speedOfDegree: Int = 0
private var speedOfArc: Float = 0.toFloat()
constructor(context: Context) : super(context) {
initView(context, null)
}
constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {
initView(context, attrs)
}
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {
initView(context, attrs)
}
private fun initView(context: Context, attrs: AttributeSet?) {
loadingColor = Color.WHITE
thisWidth = dpToPx(context, DEFAULT_WIDTH.toFloat())
shadowPosition = dpToPx(getContext(), DEFAULT_SHADOW_POSITION.toFloat())
speedOfDegree = DEFAULT_SPEED_OF_DEGREE
if (null != attrs) {
val typedArray = context.obtainStyledAttributes(attrs, R.styleable.RotateLoading)
loadingColor = typedArray.getColor(R.styleable.RotateLoading_loading_color, Color.WHITE)
thisWidth = typedArray.getDimensionPixelSize(
R.styleable.RotateLoading_loading_width,
dpToPx(context, DEFAULT_WIDTH.toFloat())
)
shadowPosition = typedArray.getInt(R.styleable.RotateLoading_shadow_position, DEFAULT_SHADOW_POSITION)
speedOfDegree = typedArray.getInt(R.styleable.RotateLoading_loading_speed, DEFAULT_SPEED_OF_DEGREE)
typedArray.recycle()
}
speedOfArc = (speedOfDegree / 4).toFloat()
mPaint = Paint()
mPaint!!.color = loadingColor
mPaint!!.isAntiAlias = true
mPaint!!.style = Paint.Style.STROKE
mPaint!!.strokeWidth = thisWidth.toFloat()
mPaint!!.strokeCap = Paint.Cap.ROUND
}
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
super.onSizeChanged(w, h, oldw, oldh)
arc = 10f
loadingRectF =
RectF(
(2 * thisWidth).toFloat(),
(2 * thisWidth).toFloat(),
(w - 2 * thisWidth).toFloat(),
(h - 2 * thisWidth).toFloat()
)
shadowRectF = RectF(
(2 * thisWidth + shadowPosition).toFloat(),
(2 * thisWidth + shadowPosition).toFloat(),
(w - 2 * thisWidth + shadowPosition).toFloat(),
(h - 2 * thisWidth + shadowPosition).toFloat()
)
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
if (!isStart) {
return
}
mPaint!!.color = Color.parseColor("#1a000000")
canvas.drawArc(shadowRectF!!, topDegree.toFloat(), arc, false, mPaint!!)
canvas.drawArc(shadowRectF!!, bottomDegree.toFloat(), arc, false, mPaint!!)
mPaint!!.color = loadingColor
canvas.drawArc(loadingRectF!!, topDegree.toFloat(), arc, false, mPaint!!)
canvas.drawArc(loadingRectF!!, bottomDegree.toFloat(), arc, false, mPaint!!)
topDegree += speedOfDegree
bottomDegree += speedOfDegree
if (topDegree > 360) {
topDegree = topDegree - 360
}
if (bottomDegree > 360) {
bottomDegree = bottomDegree - 360
}
if (changeBigger) {
if (arc < 160) {
arc += speedOfArc
invalidate()
}
} else {
if (arc > speedOfDegree) {
arc -= 2 * speedOfArc
invalidate()
}
}
if (arc >= 160 || arc <= 10) {
changeBigger = !changeBigger
invalidate()
}
}
fun start() {
startAnimator()
isStart = true
invalidate()
}
fun stop() {
stopAnimator()
invalidate()
}
private fun startAnimator() {
val scaleXAnimator = ObjectAnimator.ofFloat(this, "scaleX", 0.0f, 1f)
val scaleYAnimator = ObjectAnimator.ofFloat(this, "scaleY", 0.0f, 1f)
scaleXAnimator.setDuration(300)
scaleXAnimator.setInterpolator(LinearInterpolator())
scaleYAnimator.setDuration(300)
scaleYAnimator.setInterpolator(LinearInterpolator())
val animatorSet = AnimatorSet()
animatorSet.playTogether(scaleXAnimator, scaleYAnimator)
animatorSet.start()
}
private fun stopAnimator() {
val scaleXAnimator = ObjectAnimator.ofFloat(this, "scaleX", 1f, 0f)
val scaleYAnimator = ObjectAnimator.ofFloat(this, "scaleY", 1f, 0f)
scaleXAnimator.setDuration(300)
scaleXAnimator.setInterpolator(LinearInterpolator())
scaleYAnimator.setDuration(300)
scaleYAnimator.setInterpolator(LinearInterpolator())
val animatorSet = AnimatorSet()
animatorSet.playTogether(scaleXAnimator, scaleYAnimator)
animatorSet.addListener(object : Animator.AnimatorListener {
override fun onAnimationStart(animation: Animator) {
}
override fun onAnimationEnd(animation: Animator) {
isStart = false
}
override fun onAnimationCancel(animation: Animator) {
}
override fun onAnimationRepeat(animation: Animator) {
}
})
animatorSet.start()
}
fun dpToPx(context: Context, dpVal: Float): Int {
return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dpVal, context.resources.displayMetrics).toInt()
}
companion object {
private val DEFAULT_WIDTH = 6
private val DEFAULT_SHADOW_POSITION = 2
private val DEFAULT_SPEED_OF_DEGREE = 10
}
}

@ -46,7 +46,7 @@ class TitleBar(context: Context, attrs: AttributeSet?) : AppBarLayout(context, a
private val attachToActivity: Boolean private val attachToActivity: Boolean
init { init {
inflate(context, R.layout.view_titlebar, this) inflate(context, R.layout.view_title_bar, this)
setBackgroundColor(ThemeStore.primaryColor(context)) setBackgroundColor(ThemeStore.primaryColor(context))
toolbar = findViewById(R.id.toolbar) toolbar = findViewById(R.id.toolbar)
val a = context.obtainStyledAttributes( val a = context.obtainStyledAttributes(

@ -0,0 +1,56 @@
package io.legado.app.ui.widget.image
import android.annotation.SuppressLint
import android.content.Context
import android.graphics.Canvas
import android.graphics.Path
import android.util.AttributeSet
class CoverImageView : androidx.appcompat.widget.AppCompatImageView {
internal var width: Float = 0.toFloat()
internal var height: Float = 0.toFloat()
constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet) : super(context, attrs)
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
super.onLayout(changed, left, top, right, bottom)
width = getWidth().toFloat()
height = getHeight().toFloat()
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
val measuredWidth = MeasureSpec.getSize(widthMeasureSpec)
val measuredHeight = measuredWidth * 7 / 5
super.onMeasure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(measuredHeight, MeasureSpec.EXACTLY))
}
override fun onDraw(canvas: Canvas) {
if (width >= 10 && height > 10) {
@SuppressLint("DrawAllocation")
val path = Path()
//四个圆角
path.moveTo(10f, 0f)
path.lineTo(width - 10, 0f)
path.quadTo(width, 0f, width, 10f)
path.lineTo(width, height - 10)
path.quadTo(width, height, width - 10, height)
path.lineTo(10f, height)
path.quadTo(0f, height, 0f, height - 10)
path.lineTo(0f, 10f)
path.quadTo(0f, 0f, 10f, 0f)
canvas.clipPath(path)
}
super.onDraw(canvas)
}
fun setHeight(height: Int) {
val width = height * 5 / 7
minimumWidth = width
}
}

@ -0,0 +1,92 @@
package io.legado.app.ui.widget.image
import android.annotation.SuppressLint
import android.content.Context
import android.graphics.Canvas
import android.graphics.Path
import android.util.AttributeSet
import androidx.appcompat.widget.AppCompatImageView
import io.legado.app.R
class FilletImageView : AppCompatImageView {
internal var width: Float = 0.toFloat()
internal var height: Float = 0.toFloat()
private var leftTopRadius: Int = 0
private var rightTopRadius: Int = 0
private var rightBottomRadius: Int = 0
private var leftBottomRadius: Int = 0
constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {
init(context, attrs)
}
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {
init(context, attrs)
}
private fun init(context: Context, attrs: AttributeSet) {
// 读取配置
val array = context.obtainStyledAttributes(attrs, R.styleable.FilletImageView)
val defaultRadius = 5
val radius = array.getDimensionPixelOffset(R.styleable.FilletImageView_radius, defaultRadius)
leftTopRadius = array.getDimensionPixelOffset(R.styleable.FilletImageView_left_top_radius, defaultRadius)
rightTopRadius = array.getDimensionPixelOffset(R.styleable.FilletImageView_right_top_radius, defaultRadius)
rightBottomRadius =
array.getDimensionPixelOffset(R.styleable.FilletImageView_right_bottom_radius, defaultRadius)
leftBottomRadius = array.getDimensionPixelOffset(R.styleable.FilletImageView_left_bottom_radius, defaultRadius)
//如果四个角的值没有设置,那么就使用通用的radius的值。
if (defaultRadius == leftTopRadius) {
leftTopRadius = radius
}
if (defaultRadius == rightTopRadius) {
rightTopRadius = radius
}
if (defaultRadius == rightBottomRadius) {
rightBottomRadius = radius
}
if (defaultRadius == leftBottomRadius) {
leftBottomRadius = radius
}
array.recycle()
}
override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
super.onLayout(changed, left, top, right, bottom)
width = getWidth().toFloat()
height = getHeight().toFloat()
}
override fun onDraw(canvas: Canvas) {
//这里做下判断,只有图片的宽高大于设置的圆角距离的时候才进行裁剪
val maxLeft = Math.max(leftTopRadius, leftBottomRadius)
val maxRight = Math.max(rightTopRadius, rightBottomRadius)
val minWidth = maxLeft + maxRight
val maxTop = Math.max(leftTopRadius, rightTopRadius)
val maxBottom = Math.max(leftBottomRadius, rightBottomRadius)
val minHeight = maxTop + maxBottom
if (width >= minWidth && height > minHeight) {
@SuppressLint("DrawAllocation") val path = Path()
//四个角:右上,右下,左下,左上
path.moveTo(leftTopRadius.toFloat(), 0f)
path.lineTo(width - rightTopRadius, 0f)
path.quadTo(width, 0f, width, rightTopRadius.toFloat())
path.lineTo(width, height - rightBottomRadius)
path.quadTo(width, height, width - rightBottomRadius, height)
path.lineTo(leftBottomRadius.toFloat(), height)
path.quadTo(0f, height, 0f, height - leftBottomRadius)
path.lineTo(0f, leftTopRadius.toFloat())
path.quadTo(0f, 0f, leftTopRadius.toFloat(), 0f)
canvas.clipPath(path)
}
super.onDraw(canvas)
}
}

@ -1,10 +1,8 @@
package io.legado.app.utils package io.legado.app.utils
import android.app.Activity
import android.content.res.ColorStateList import android.content.res.ColorStateList
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import android.view.View import android.view.View
import android.view.WindowManager
import androidx.annotation.ColorRes import androidx.annotation.ColorRes
import androidx.annotation.DrawableRes import androidx.annotation.DrawableRes
import androidx.annotation.StringRes import androidx.annotation.StringRes

@ -1,4 +1,5 @@
package io.legado.app.utils package io.legado.app.utils
// import org.apache.commons.text.StringEscapeUtils // import org.apache.commons.text.StringEscapeUtils
fun String?.safeTrim() = if (this.isNullOrBlank()) null else this.trim() fun String?.safeTrim() = if (this.isNullOrBlank()) null else this.trim()

@ -9,4 +9,5 @@ fun <T : ViewModel> AppCompatActivity.getViewModel(clazz: Class<T>) = ViewModelP
fun <T : ViewModel> Fragment.getViewModel(clazz: Class<T>) = ViewModelProviders.of(this).get(clazz) fun <T : ViewModel> Fragment.getViewModel(clazz: Class<T>) = ViewModelProviders.of(this).get(clazz)
fun <T : ViewModel> Fragment.getViewModelOfActivity(clazz: Class<T>) = ViewModelProviders.of(requireActivity()).get(clazz) fun <T : ViewModel> Fragment.getViewModelOfActivity(clazz: Class<T>) =
ViewModelProviders.of(requireActivity()).get(clazz)

@ -1,5 +1,7 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<ripple xmlns:android="http://schemas.android.com/apk/res/android" <ripple xmlns:android="http://schemas.android.com/apk/res/android"
android:color="@color/btn_bg_press_2"> android:color="@color/btn_bg_press_2">
<item android:id="@android:id/mask" android:drawable="@color/btn_bg_press_2"/> <item
android:id="@android:id/mask"
android:drawable="@color/btn_bg_press_2" />
</ripple> </ripple>

@ -1,6 +1,7 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" <shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle"> android:shape="rectangle">
<stroke android:width="1dp" <stroke
android:width="1dp"
android:color="@color/btn_bg_press" /> android:color="@color/btn_bg_press" />
</shape> </shape>

@ -1,10 +1,7 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" <selector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" tools:ignore="PrivateResource">
tools:ignore="PrivateResource"> <item android:drawable="@drawable/abc_textfield_search_activated_mtrl_alpha" android:state_enabled="true" android:state_focused="true" />
<item android:drawable="@drawable/abc_textfield_search_activated_mtrl_alpha" android:state_enabled="true" <item android:drawable="@drawable/abc_textfield_search_activated_mtrl_alpha" android:state_activated="true" android:state_enabled="true" />
android:state_focused="true"/>
<item android:drawable="@drawable/abc_textfield_search_activated_mtrl_alpha" android:state_activated="true"
android:state_enabled="true"/>
<item android:drawable="@drawable/abc_textfield_search_default_mtrl_alpha" android:state_enabled="true" /> <item android:drawable="@drawable/abc_textfield_search_default_mtrl_alpha" android:state_enabled="true" />
<item android:drawable="@drawable/abc_textfield_search_default_mtrl_alpha" /> <item android:drawable="@drawable/abc_textfield_search_default_mtrl_alpha" />
</selector> </selector>

@ -1,5 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?><!--
<!--
~ Copyright 2016 L4 Digital LLC. All rights reserved. ~ Copyright 2016 L4 Digital LLC. All rights reserved.
~ ~
~ Licensed under the Apache License, Version 2.0 (the "License"); ~ Licensed under the Apache License, Version 2.0 (the "License");
@ -15,8 +14,7 @@
~ limitations under the License. ~ limitations under the License.
--> -->
<shape <shape xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:shape="rectangle"> android:shape="rectangle">

@ -1,5 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?><!--
<!--
~ Copyright 2016 L4 Digital LLC. All rights reserved. ~ Copyright 2016 L4 Digital LLC. All rights reserved.
~ ~
~ Licensed under the Apache License, Version 2.0 (the "License"); ~ Licensed under the Apache License, Version 2.0 (the "License");
@ -15,8 +14,7 @@
~ limitations under the License. ~ limitations under the License.
--> -->
<shape <shape xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:shape="rectangle"> android:shape="rectangle">

@ -1,5 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?><!--
<!--
~ Copyright 2016 L4 Digital LLC. All rights reserved. ~ Copyright 2016 L4 Digital LLC. All rights reserved.
~ ~
~ Licensed under the Apache License, Version 2.0 (the "License"); ~ Licensed under the Apache License, Version 2.0 (the "License");
@ -15,8 +14,7 @@
~ limitations under the License. ~ limitations under the License.
--> -->
<shape <shape xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:shape="rectangle"> android:shape="rectangle">

@ -1,19 +1,32 @@
<vector android:autoMirrored="true" android:height="24dp" <vector android:autoMirrored="true"
android:viewportHeight="48" android:viewportWidth="48" android:height="24dp"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android"> android:viewportHeight="48"
<path android:fillColor="#373838" android:viewportWidth="48"
android:width="24dp"
xmlns:android="http://schemas.android.com/apk/res/android">
<path
android:fillColor="#373838"
android:pathData="M20.4,12.2c0,-0.3 -0.1,-0.5 -0.3,-0.7c-0.4,-0.4 -1.1,-0.4 -1.5,0L6.9,23.3c-0.2,0.2 -0.3,0.5 -0.3,0.7c0,0.1 0,0.3 0.1,0.4c0,0.1 0.1,0.2 0.2,0.3c0,0 0,0 0,0.1l0,0c0,0 0,0 0,0c0,0 0,0 0,0l11.8,11.8c0.2,0.2 0.5,0.3 0.7,0.3c0.3,0 0.5,-0.1 0.7,-0.3c0.2,-0.2 0.3,-0.5 0.3,-0.7s-0.1,-0.5 -0.3,-0.7l-11,-11l11,-11C20.3,12.8 20.4,12.5 20.4,12.2z" android:pathData="M20.4,12.2c0,-0.3 -0.1,-0.5 -0.3,-0.7c-0.4,-0.4 -1.1,-0.4 -1.5,0L6.9,23.3c-0.2,0.2 -0.3,0.5 -0.3,0.7c0,0.1 0,0.3 0.1,0.4c0,0.1 0.1,0.2 0.2,0.3c0,0 0,0 0,0.1l0,0c0,0 0,0 0,0c0,0 0,0 0,0l11.8,11.8c0.2,0.2 0.5,0.3 0.7,0.3c0.3,0 0.5,-0.1 0.7,-0.3c0.2,-0.2 0.3,-0.5 0.3,-0.7s-0.1,-0.5 -0.3,-0.7l-11,-11l11,-11C20.3,12.8 20.4,12.5 20.4,12.2z"
android:strokeColor="#39393A" android:strokeWidth="1"/> android:strokeColor="#39393A"
<path android:fillColor="#373838" android:strokeWidth="1" />
<path
android:fillColor="#373838"
android:pathData="M41.1,23.3L41.1,23.3L41.1,23.3L41.1,23.3L29.3,11.5c-0.2,-0.2 -0.5,-0.3 -0.7,-0.3c-0.3,0 -0.5,0.1 -0.7,0.3c-0.2,0.2 -0.3,0.5 -0.3,0.7c0,0.3 0.1,0.5 0.3,0.7l11,11l-11,11c-0.2,0.2 -0.3,0.5 -0.3,0.7c0,0.3 0.1,0.5 0.3,0.7c0.4,0.4 1.1,0.4 1.5,0l11.8,-11.8c0.2,-0.2 0.3,-0.5 0.3,-0.7C41.4,23.7 41.3,23.5 41.1,23.3z" android:pathData="M41.1,23.3L41.1,23.3L41.1,23.3L41.1,23.3L29.3,11.5c-0.2,-0.2 -0.5,-0.3 -0.7,-0.3c-0.3,0 -0.5,0.1 -0.7,0.3c-0.2,0.2 -0.3,0.5 -0.3,0.7c0,0.3 0.1,0.5 0.3,0.7l11,11l-11,11c-0.2,0.2 -0.3,0.5 -0.3,0.7c0,0.3 0.1,0.5 0.3,0.7c0.4,0.4 1.1,0.4 1.5,0l11.8,-11.8c0.2,-0.2 0.3,-0.5 0.3,-0.7C41.4,23.7 41.3,23.5 41.1,23.3z"
android:strokeColor="#39393A" android:strokeWidth="1"/> android:strokeColor="#39393A"
<path android:fillColor="#373838" android:strokeWidth="1" />
<path
android:fillColor="#373838"
android:pathData="M16.2,24c0,0.7 0.6,1.3 1.3,1.3c0.7,0 1.3,-0.6 1.3,-1.3c0,-0.7 -0.6,-1.3 -1.3,-1.3C16.7,22.7 16.2,23.3 16.2,24z" android:pathData="M16.2,24c0,0.7 0.6,1.3 1.3,1.3c0.7,0 1.3,-0.6 1.3,-1.3c0,-0.7 -0.6,-1.3 -1.3,-1.3C16.7,22.7 16.2,23.3 16.2,24z"
android:strokeColor="#39393A" android:strokeWidth="1"/> android:strokeColor="#39393A"
<path android:fillColor="#373838" android:strokeWidth="1" />
<path
android:fillColor="#373838"
android:pathData="M24,22.7c-0.7,0 -1.3,0.6 -1.3,1.3c0,0.7 0.6,1.3 1.3,1.3c0.7,0 1.3,-0.6 1.3,-1.3C25.3,23.3 24.7,22.7 24,22.7z" android:pathData="M24,22.7c-0.7,0 -1.3,0.6 -1.3,1.3c0,0.7 0.6,1.3 1.3,1.3c0.7,0 1.3,-0.6 1.3,-1.3C25.3,23.3 24.7,22.7 24,22.7z"
android:strokeColor="#39393A" android:strokeWidth="1"/> android:strokeColor="#39393A"
<path android:fillColor="#373838" android:strokeWidth="1" />
<path
android:fillColor="#373838"
android:pathData="M32.3,24c0,-0.7 -0.6,-1.3 -1.3,-1.3c-0.7,0 -1.3,0.6 -1.3,1.3c0,0.7 0.6,1.3 1.3,1.3C31.7,25.3 32.3,24.7 32.3,24z" android:pathData="M32.3,24c0,-0.7 -0.6,-1.3 -1.3,-1.3c-0.7,0 -1.3,0.6 -1.3,1.3c0,0.7 0.6,1.3 1.3,1.3C31.7,25.3 32.3,24.7 32.3,24z"
android:strokeColor="#39393A" android:strokeWidth="1"/> android:strokeColor="#39393A"
android:strokeWidth="1" />
</vector> </vector>

@ -1,7 +1,13 @@
<vector android:autoMirrored="true" android:height="24dp" <vector android:autoMirrored="true"
android:viewportHeight="48" android:viewportWidth="48" android:height="24dp"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android"> android:viewportHeight="48"
<path android:fillColor="#39393A" android:fillType="evenOdd" android:viewportWidth="48"
android:width="24dp"
xmlns:android="http://schemas.android.com/apk/res/android">
<path
android:fillColor="#39393A"
android:fillType="evenOdd"
android:pathData="M39.4,11H8.6c-0.7,0 -1.2,-0.5 -1.2,-1.2c0,-0.7 0.5,-1.2 1.2,-1.2h30.8c0.7,0 1.2,0.5 1.2,1.2C40.6,10.4 40.1,11 39.4,11zM8.6,18.1h21.3c0.7,0 1.2,0.5 1.2,1.2c0,0.7 -0.5,1.2 -1.2,1.2H8.6c-0.7,0 -1.2,-0.5 -1.2,-1.2C7.4,18.6 7.9,18.1 8.6,18.1zM8.6,27.6h30.8c0.7,0 1.2,0.5 1.2,1.2c0,0.7 -0.5,1.2 -1.2,1.2H8.6c-0.7,0 -1.2,-0.5 -1.2,-1.2C7.4,28.1 7.9,27.6 8.6,27.6zM8.6,37h17.8c0.7,0 1.2,0.5 1.2,1.2c0,0.7 -0.5,1.2 -1.2,1.2H8.6c-0.7,0 -1.2,-0.5 -1.2,-1.2C7.4,37.6 7.9,37 8.6,37z" android:pathData="M39.4,11H8.6c-0.7,0 -1.2,-0.5 -1.2,-1.2c0,-0.7 0.5,-1.2 1.2,-1.2h30.8c0.7,0 1.2,0.5 1.2,1.2C40.6,10.4 40.1,11 39.4,11zM8.6,18.1h21.3c0.7,0 1.2,0.5 1.2,1.2c0,0.7 -0.5,1.2 -1.2,1.2H8.6c-0.7,0 -1.2,-0.5 -1.2,-1.2C7.4,18.6 7.9,18.1 8.6,18.1zM8.6,27.6h30.8c0.7,0 1.2,0.5 1.2,1.2c0,0.7 -0.5,1.2 -1.2,1.2H8.6c-0.7,0 -1.2,-0.5 -1.2,-1.2C7.4,28.1 7.9,27.6 8.6,27.6zM8.6,37h17.8c0.7,0 1.2,0.5 1.2,1.2c0,0.7 -0.5,1.2 -1.2,1.2H8.6c-0.7,0 -1.2,-0.5 -1.2,-1.2C7.4,37.6 7.9,37 8.6,37z"
android:strokeColor="#39393A" android:strokeWidth="1"/> android:strokeColor="#39393A"
android:strokeWidth="1" />
</vector> </vector>

@ -1,74 +1,170 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<vector <vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:android="http://schemas.android.com/apk/res/android"
android:height="108dp" android:height="108dp"
android:width="108dp" android:width="108dp"
android:viewportHeight="108" android:viewportHeight="108"
android:viewportWidth="108"> android:viewportWidth="108">
<path android:fillColor="#008577" <path
android:fillColor="#008577"
android:pathData="M0,0h108v108h-108z" /> android:pathData="M0,0h108v108h-108z" />
<path android:fillColor="#00000000" android:pathData="M9,0L9,108" <path
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> android:fillColor="#00000000"
<path android:fillColor="#00000000" android:pathData="M19,0L19,108" android:pathData="M9,0L9,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> android:strokeColor="#33FFFFFF"
<path android:fillColor="#00000000" android:pathData="M29,0L29,108" android:strokeWidth="0.8" />
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> <path
<path android:fillColor="#00000000" android:pathData="M39,0L39,108" android:fillColor="#00000000"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> android:pathData="M19,0L19,108"
<path android:fillColor="#00000000" android:pathData="M49,0L49,108" android:strokeColor="#33FFFFFF"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> android:strokeWidth="0.8" />
<path android:fillColor="#00000000" android:pathData="M59,0L59,108" <path
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> android:fillColor="#00000000"
<path android:fillColor="#00000000" android:pathData="M69,0L69,108" android:pathData="M29,0L29,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> android:strokeColor="#33FFFFFF"
<path android:fillColor="#00000000" android:pathData="M79,0L79,108" android:strokeWidth="0.8" />
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> <path
<path android:fillColor="#00000000" android:pathData="M89,0L89,108" android:fillColor="#00000000"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> android:pathData="M39,0L39,108"
<path android:fillColor="#00000000" android:pathData="M99,0L99,108" android:strokeColor="#33FFFFFF"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> android:strokeWidth="0.8" />
<path android:fillColor="#00000000" android:pathData="M0,9L108,9" <path
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> android:fillColor="#00000000"
<path android:fillColor="#00000000" android:pathData="M0,19L108,19" android:pathData="M49,0L49,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> android:strokeColor="#33FFFFFF"
<path android:fillColor="#00000000" android:pathData="M0,29L108,29" android:strokeWidth="0.8" />
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> <path
<path android:fillColor="#00000000" android:pathData="M0,39L108,39" android:fillColor="#00000000"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> android:pathData="M59,0L59,108"
<path android:fillColor="#00000000" android:pathData="M0,49L108,49" android:strokeColor="#33FFFFFF"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> android:strokeWidth="0.8" />
<path android:fillColor="#00000000" android:pathData="M0,59L108,59" <path
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> android:fillColor="#00000000"
<path android:fillColor="#00000000" android:pathData="M0,69L108,69" android:pathData="M69,0L69,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> android:strokeColor="#33FFFFFF"
<path android:fillColor="#00000000" android:pathData="M0,79L108,79" android:strokeWidth="0.8" />
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> <path
<path android:fillColor="#00000000" android:pathData="M0,89L108,89" android:fillColor="#00000000"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> android:pathData="M79,0L79,108"
<path android:fillColor="#00000000" android:pathData="M0,99L108,99" android:strokeColor="#33FFFFFF"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> android:strokeWidth="0.8" />
<path android:fillColor="#00000000" android:pathData="M19,29L89,29" <path
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> android:fillColor="#00000000"
<path android:fillColor="#00000000" android:pathData="M19,39L89,39" android:pathData="M89,0L89,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> android:strokeColor="#33FFFFFF"
<path android:fillColor="#00000000" android:pathData="M19,49L89,49" android:strokeWidth="0.8" />
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> <path
<path android:fillColor="#00000000" android:pathData="M19,59L89,59" android:fillColor="#00000000"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> android:pathData="M99,0L99,108"
<path android:fillColor="#00000000" android:pathData="M19,69L89,69" android:strokeColor="#33FFFFFF"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> android:strokeWidth="0.8" />
<path android:fillColor="#00000000" android:pathData="M19,79L89,79" <path
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> android:fillColor="#00000000"
<path android:fillColor="#00000000" android:pathData="M29,19L29,89" android:pathData="M0,9L108,9"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> android:strokeColor="#33FFFFFF"
<path android:fillColor="#00000000" android:pathData="M39,19L39,89" android:strokeWidth="0.8" />
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> <path
<path android:fillColor="#00000000" android:pathData="M49,19L49,89" android:fillColor="#00000000"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> android:pathData="M0,19L108,19"
<path android:fillColor="#00000000" android:pathData="M59,19L59,89" android:strokeColor="#33FFFFFF"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> android:strokeWidth="0.8" />
<path android:fillColor="#00000000" android:pathData="M69,19L69,89" <path
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> android:fillColor="#00000000"
<path android:fillColor="#00000000" android:pathData="M79,19L79,89" android:pathData="M0,29L108,29"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,39L108,39"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,49L108,49"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,59L108,59"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,69L108,69"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,79L108,79"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,89L108,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,99L108,99"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,29L89,29"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,39L89,39"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,49L89,49"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,59L89,59"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,69L89,69"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,79L89,79"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M29,19L29,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M39,19L39,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M49,19L49,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M59,19L59,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M69,19L69,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M79,19L79,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
</vector> </vector>

@ -1,10 +1,7 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android"> <selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true" <item android:state_pressed="true" android:drawable="@color/btn_bg_press" />
android:drawable="@color/btn_bg_press"/> <item android:state_selected="true" android:drawable="@color/btn_bg_press" />
<item android:state_selected="true"
android:drawable="@color/btn_bg_press"/>
<item <item android:drawable="@color/background" />
android:drawable="@color/background"/>
</selector> </selector>

@ -2,6 +2,7 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android"> <shape xmlns:android="http://schemas.android.com/apk/res/android">
<corners android:radius="1dp" /> <corners android:radius="1dp" />
<solid android:color="#00ffffff" /> <solid android:color="#00ffffff" />
<stroke android:width="1dp" <stroke
android:width="1dp"
android:color="@color/tv_text_secondary" /> android:color="@color/tv_text_secondary" />
</shape> </shape>

@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"

@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
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">

@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
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"

@ -1,9 +1,9 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
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_height="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="io.legado.app.ui.search.SearchActivity"> tools:context="io.legado.app.ui.search.SearchActivity">
<io.legado.app.ui.widget.TitleBar <io.legado.app.ui.widget.TitleBar

@ -1,7 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" <LinearLayout 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:fitsSystemWindows="true"
android:id="@+id/ll_content" android:id="@+id/ll_content"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
@ -26,6 +25,13 @@
android:checked="true" android:checked="true"
android:text="@string/is_enable" /> android:text="@string/is_enable" />
<io.legado.app.lib.theme.view.ATECheckBox
android:id="@+id/cb_is_enable_find"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:checked="true"
android:text="@string/find" />
<io.legado.app.lib.theme.view.ATECheckBox <io.legado.app.lib.theme.view.ATECheckBox
android:id="@+id/cb_is_audio" android:id="@+id/cb_is_audio"
android:layout_width="wrap_content" android:layout_width="wrap_content"

@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
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:orientation="vertical" android:orientation="vertical"
android:layout_width="match_parent" android:layout_width="match_parent"
@ -11,7 +10,11 @@
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:layout_constraintTop_toTopOf="parent"
app:title="@string/book_source" /> app:title="@string/book_source" />
</androidx.constraintlayout.widget.ConstraintLayout> <androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>

@ -1,17 +1,24 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
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:orientation="vertical"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent"
android:orientation="vertical">
<io.legado.app.ui.widget.TitleBar <io.legado.app.ui.widget.TitleBar
android:id="@+id/title_bar" android:id="@+id/title_bar"
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:layout_constraintTop_toTopOf="parent"
app:title="@string/bookshelf" /> app:title="@string/bookshelf" />
</androidx.constraintlayout.widget.ConstraintLayout> <androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_book_group"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_bookshelf"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>

@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
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:orientation="vertical" android:orientation="vertical"
android:layout_width="match_parent" android:layout_width="match_parent"
@ -15,4 +14,4 @@
app:title="@string/find" /> app:title="@string/find" />
</androidx.constraintlayout.widget.ConstraintLayout> </LinearLayout>

@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<LinearLayout <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
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"

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_margin="5dp"
android:layout_width="match_parent"
android:layout_height="match_parent" />

@ -0,0 +1,36 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:background="@color/background"
android:padding="16dp"
android:orientation="horizontal"
android:gravity="center_vertical">
<io.legado.app.lib.theme.view.ATECheckBox
android:id="@+id/cb_book_source"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_weight="1"
android:singleLine="true"
android:text=""
android:textColor="@color/tv_text_default" />
<io.legado.app.lib.theme.view.ATESwitch
android:id="@+id/sw_enabled"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<ImageView
android:id="@+id/iv_more"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_gravity="center"
android:background="@drawable/bg_ib_pre_round"
android:layout_margin="6dp"
android:contentDescription="@string/to_top"
android:src="@drawable/ic_more_vert"
app:tint="@color/tv_text_default" />
</LinearLayout>

@ -0,0 +1,175 @@
<?xml version="1.0" encoding="utf-8"?>
<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"
android:foreground="?android:attr/selectableItemBackground"
tools:ignore="UnusedAttribute">
<io.legado.app.ui.widget.image.CoverImageView
android:id="@+id/iv_cover"
android:layout_width="60dp"
android:layout_height="80dp"
android:layout_margin="8dp"
android:contentDescription="@string/img_cover"
android:scaleType="centerCrop"
android:src="@drawable/img_cover_default"
android:transitionName="img_cover"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:ignore="UnusedAttribute" />
<FrameLayout
android:id="@+id/fl_has_new"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="@id/tv_name">
<io.legado.app.ui.widget.BadgeView
android:id="@+id/bv_unread"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="right"
android:layout_margin="5dp"
android:includeFontPadding="false"
tools:ignore="RtlHardcoded" />
<com.victor.loading.rotate.RotateLoading
android:id="@+id/rl_loading"
android:layout_width="26dp"
android:layout_height="26dp"
android:layout_gravity="right"
android:visibility="invisible"
app:loading_color="@color/colorAccent"
app:loading_width="2dp"
tools:ignore="RtlHardcoded" />
</FrameLayout>
<TextView
android:id="@+id/tv_name"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginLeft="8dp"
android:layout_marginTop="8dp"
android:includeFontPadding="false"
android:paddingLeft="4dp"
android:singleLine="true"
android:text="@string/book_name"
android:textColor="@color/tv_text_default"
android:textSize="16sp"
app:layout_constraintBottom_toTopOf="@+id/tv_author"
app:layout_constraintLeft_toRightOf="@+id/iv_cover"
app:layout_constraintRight_toLeftOf="@id/fl_has_new"
app:layout_constraintTop_toTopOf="parent"
tools:ignore="RtlHardcoded,RtlSymmetry" />
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/iv_author"
android:layout_width="@dimen/desc_icon_size"
android:layout_height="@dimen/desc_icon_size"
android:layout_marginLeft="8dp"
android:contentDescription="@string/author"
android:paddingStart="2dp"
android:paddingEnd="2dp"
android:src="@drawable/ic_author"
app:layout_constraintBottom_toBottomOf="@+id/tv_author"
app:layout_constraintLeft_toRightOf="@+id/iv_cover"
app:layout_constraintTop_toTopOf="@+id/tv_author"
app:tint="@color/tv_text_secondary"
tools:ignore="RtlHardcoded,RtlSymmetry" />
<TextView
android:id="@+id/tv_author"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:includeFontPadding="false"
android:maxLines="1"
android:paddingEnd="6dp"
android:text="@string/author"
android:textColor="@color/tv_text_secondary"
android:textSize="13sp"
app:layout_constraintBottom_toTopOf="@+id/tv_read"
app:layout_constraintLeft_toRightOf="@+id/iv_author"
app:layout_constraintRight_toLeftOf="@id/fl_has_new"
app:layout_constraintTop_toBottomOf="@+id/tv_name"
tools:ignore="RtlSymmetry" />
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/iv_read"
android:layout_width="@dimen/desc_icon_size"
android:layout_height="@dimen/desc_icon_size"
android:layout_marginLeft="8dp"
android:contentDescription="@string/read_dur_progress"
android:paddingStart="2dp"
android:paddingEnd="2dp"
android:src="@drawable/ic_history"
app:layout_constraintBottom_toBottomOf="@+id/tv_read"
app:layout_constraintLeft_toRightOf="@+id/iv_cover"
app:layout_constraintTop_toTopOf="@+id/tv_read"
app:tint="@color/tv_text_secondary"
tools:ignore="RtlHardcoded,RtlSymmetry" />
<TextView
android:id="@+id/tv_read"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:includeFontPadding="false"
android:singleLine="true"
android:text="@string/read_dur_progress"
android:textColor="@color/tv_text_secondary"
android:textSize="13sp"
app:layout_constraintBottom_toTopOf="@id/tv_last"
app:layout_constraintLeft_toRightOf="@+id/iv_read"
app:layout_constraintRight_toLeftOf="@id/fl_has_new"
app:layout_constraintTop_toBottomOf="@+id/tv_author" />
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/iv_last"
android:layout_width="@dimen/desc_icon_size"
android:layout_height="@dimen/desc_icon_size"
android:layout_marginLeft="8dp"
android:contentDescription="@string/book_search_last"
android:paddingStart="2dp"
android:paddingEnd="2dp"
android:src="@drawable/ic_book_last"
app:layout_constraintBottom_toBottomOf="@+id/tv_last"
app:layout_constraintLeft_toRightOf="@+id/iv_cover"
app:layout_constraintTop_toTopOf="@+id/tv_last"
app:tint="@color/tv_text_secondary"
tools:ignore="RtlHardcoded,RtlSymmetry" />
<TextView
android:id="@+id/tv_last"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:singleLine="true"
android:text="@string/book_search_last"
android:textColor="@color/tv_text_secondary"
android:textSize="13sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toRightOf="@+id/iv_last"
app:layout_constraintRight_toLeftOf="@id/fl_has_new"
app:layout_constraintTop_toBottomOf="@+id/tv_read" />
<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" />
<View
android:layout_width="match_parent"
android:layout_height="0.5dp"
android:background="@color/btn_bg_press"
app:layout_constraintBottom_toBottomOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

@ -10,7 +10,9 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="match_parent" android:layout_height="match_parent"
android:adjustViewBounds="true" android:adjustViewBounds="true"
android:padding="6dip"/> android:contentDescription="ICON"
android:padding="6dip"
tools:ignore="HardcodedText" />
<CheckedTextView <CheckedTextView
android:id="@+id/label" android:id="@+id/label"

@ -1,8 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
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"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content"> android:layout_height="wrap_content">

@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
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="wrap_content"> android:layout_height="wrap_content">

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<io.legado.app.lib.theme.view.ATETextInputLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/textInputLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<io.legado.app.lib.theme.view.ATEEditText
android:id="@+id/editText"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</io.legado.app.lib.theme.view.ATETextInputLayout>

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.appcompat.widget.Toolbar xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="?attr/actionBarStyle"
app:popupTheme="@style/AppTheme.PopupOverlay">
<androidx.appcompat.widget.SearchView
android:id="@+id/search_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="gone"
app:defaultQueryHint="搜索" />
</androidx.appcompat.widget.Toolbar>

@ -1,6 +1,7 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android" <menu 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"> xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<item <item
android:id="@+id/action_select_all" android:id="@+id/action_select_all"
@ -29,8 +30,7 @@
android:id="@+id/action_sort" android:id="@+id/action_sort"
android:icon="@drawable/ic_format_line_spacing" android:icon="@drawable/ic_format_line_spacing"
android:title="@string/sort" android:title="@string/sort"
app:showAsAction="always" app:showAsAction="never">
tools:ignore="AlwaysShowAction">
<menu> <menu>

@ -2,7 +2,8 @@
<menu xmlns:android="http://schemas.android.com/apk/res/android" <menu 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">
<item android:id="@+id/action_settings" <item
android:id="@+id/action_settings"
android:title="@string/action_settings" android:title="@string/action_settings"
app:showAsAction="never" /> app:showAsAction="never" />

@ -2,7 +2,8 @@
<menu xmlns:android="http://schemas.android.com/apk/res/android" <menu 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">
<item android:id="@+id/action_settings" <item
android:id="@+id/action_settings"
android:title="@string/help" android:title="@string/help"
android:icon="@drawable/ic_live_help_black_24dp" android:icon="@drawable/ic_live_help_black_24dp"
app:showAsAction="always" /> app:showAsAction="always" />

@ -0,0 +1,51 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/action_save"
android:icon="@drawable/ic_save"
android:title="@string/action_save"
app:showAsAction="ifRoom" />
<item
android:id="@+id/action_login"
android:title="@string/login"
app:showAsAction="never" />
<item
android:id="@+id/action_copy_source"
android:title="@string/copy_source"
app:showAsAction="never" />
<item
android:id="@+id/action_paste_source"
android:title="@string/paste_source"
app:showAsAction="never" />
<item
android:id="@+id/action_qr_code_camera"
android:title="@string/import_by_qr_code"
app:showAsAction="never" />
<item
android:id="@+id/action_share_it"
android:title="@string/qr_share"
app:showAsAction="never" />
<item
android:id="@+id/action_share_wifi"
android:title="@string/wifi_share"
app:showAsAction="never" />
<item
android:id="@+id/action_debug_source"
android:title="@string/debug_source"
app:showAsAction="never" />
<item
android:id="@+id/action_rule_summary"
android:title="@string/rule_summary"
app:showAsAction="never" />
</menu>

@ -101,4 +101,10 @@
<attr name="icons" format="reference" /> <attr name="icons" format="reference" />
</declare-styleable> </declare-styleable>
<declare-styleable name="RotateLoading">
<attr name="loading_width" format="dimension" />
<attr name="loading_color" format="color" />
<attr name="shadow_position" format="integer" />
<attr name="loading_speed" format="integer" />
</declare-styleable>
</resources> </resources>

@ -1,4 +1,10 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<item name="menu_edit" type="id" />
<item name="menu_del" type="id" />
<item name="menu_top" type="id" />
<item name="tag1" type="id" />
<item name="tag2" type="id" />
</resources> </resources>

@ -56,7 +56,8 @@
<item name="textColorAlertDialogListItem">@color/tv_text_default</item> <item name="textColorAlertDialogListItem">@color/tv_text_default</item>
</style> </style>
//**************************************************************System Style******************************************************************************// //**************************************************************System
Style******************************************************************************//
<style name="Style.NavigationView" parent="android:Widget"> <style name="Style.NavigationView" parent="android:Widget">
<item name="android:listPreferredItemHeightSmall">44dp</item> <item name="android:listPreferredItemHeightSmall">44dp</item>
@ -84,7 +85,8 @@
<item name="android:windowAnimationStyle">@null</item> <item name="android:windowAnimationStyle">@null</item>
</style> </style>
//**************************************************************Widget Style******************************************************************************// //**************************************************************Widget
Style******************************************************************************//
<style name="Style.Shadow.Top" parent="android:Widget"> <style name="Style.Shadow.Top" parent="android:Widget">
<item name="android:layout_width">match_parent</item> <item name="android:layout_width">match_parent</item>

@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<androidx.preference.PreferenceScreen <androidx.preference.PreferenceScreen>
>
</androidx.preference.PreferenceScreen> </androidx.preference.PreferenceScreen>

@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<androidx.preference.PreferenceScreen <androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
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">

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<androidx.preference.PreferenceScreen <androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
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">
<androidx.preference.EditTextPreference <androidx.preference.EditTextPreference
android:key="web_dav_url" android:key="web_dav_url"

@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<androidx.preference.PreferenceScreen <androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
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">
<androidx.preference.Preference <androidx.preference.Preference

Loading…
Cancel
Save