Merge remote-tracking branch 'origin/desirepath' into desirepath

pull/31/head
Invinciblelee 6 years ago
commit 31d0b722e2
  1. 398
      app/src/main/java/io/legado/app/base/adapter/CommonRecyclerAdapter.kt
  2. 32
      app/src/main/java/io/legado/app/base/adapter/InfiniteScrollListener.kt
  3. 91
      app/src/main/java/io/legado/app/base/adapter/ItemAnimation.kt
  4. 16
      app/src/main/java/io/legado/app/base/adapter/ItemViewDelegate.kt
  5. 9
      app/src/main/java/io/legado/app/base/adapter/ItemViewHolder.kt
  6. 26
      app/src/main/java/io/legado/app/base/adapter/SimpleRecyclerAdapter.kt
  7. 17
      app/src/main/java/io/legado/app/base/adapter/animations/AlphaInAnimation.kt
  8. 13
      app/src/main/java/io/legado/app/base/adapter/animations/BaseAnimation.kt
  9. 20
      app/src/main/java/io/legado/app/base/adapter/animations/ScaleInAnimation.kt
  10. 13
      app/src/main/java/io/legado/app/base/adapter/animations/SlideInBottomAnimation.kt
  11. 14
      app/src/main/java/io/legado/app/base/adapter/animations/SlideInLeftAnimation.kt
  12. 14
      app/src/main/java/io/legado/app/base/adapter/animations/SlideInRightAnimation.kt
  13. 31
      app/src/main/java/io/legado/app/data/entities/Book.kt
  14. 4
      app/src/main/java/io/legado/app/data/entities/ReplaceRule.kt
  15. 1
      app/src/main/java/io/legado/app/data/entities/SearchBook.kt
  16. 4
      app/src/main/java/io/legado/app/data/entities/Source.kt
  17. 54
      app/src/main/java/io/legado/app/help/LayoutManager.kt
  18. 32
      app/src/main/java/io/legado/app/ui/main/MainActivity.kt
  19. 5
      app/src/main/java/io/legado/app/ui/main/MainModel.kt
  20. 5
      app/src/main/java/io/legado/app/ui/search/SearchActivity.kt
  21. 34
      app/src/main/java/io/legado/app/ui/search/SearchAdapter.kt
  22. 83
      app/src/main/java/io/legado/app/ui/widget/TitleBar.kt
  23. 6
      app/src/main/java/io/legado/app/utils/StringExtensions.kt
  24. 45
      app/src/main/res/layout/activity_main.xml
  25. 4
      app/src/main/res/layout/activity_search.xml
  26. 15
      app/src/main/res/layout/app_bar_main.xml
  27. 19
      app/src/main/res/layout/item_search.xml
  28. 1
      app/src/main/res/layout/view_titlebar.xml
  29. 8
      app/src/main/res/values/attrs.xml

@ -0,0 +1,398 @@
package io.legado.app.base.adapter
import android.content.Context
import android.util.SparseArray
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
import java.util.*
/**
* Created by Invincible on 2017/11/24.
*
* 通用的adapter 可添加headerfooter以及不同类型item
*/
abstract class CommonRecyclerAdapter<ITEM>(protected val context: Context) : RecyclerView.Adapter<ItemViewHolder>() {
constructor(context: Context, vararg delegates: Pair<Int, ItemViewDelegate<ITEM>>) : this(context) {
addItemViewDelegates(*delegates)
}
constructor(context: Context, vararg delegates: ItemViewDelegate<ITEM>) : this(context) {
addItemViewDelegates(*delegates)
}
private val inflater: LayoutInflater = LayoutInflater.from(context)
private var headerItems: SparseArray<View>? = null
private var footerItems: SparseArray<View>? = null
private val itemDelegates: HashMap<Int, ItemViewDelegate<ITEM>> = hashMapOf()
private val items: MutableList<ITEM> = mutableListOf()
private val lock = Object()
private var itemClickListener: ((holder: ItemViewHolder, item: ITEM) -> Unit)? = null
private var itemLongClickListener: ((holder: ItemViewHolder, item: ITEM) -> Boolean)? = null
private var itemAnimation: ItemAnimation? = null
fun setOnItemClickListener(listener: (holder: ItemViewHolder, item: ITEM) -> Unit) {
itemClickListener = listener
}
fun setOnItemLongClickListener(listener: (holder: ItemViewHolder, item: ITEM) -> Boolean) {
itemLongClickListener = listener
}
fun bindToRecyclerView(recyclerView: RecyclerView) {
recyclerView.adapter = this
}
fun <DELEGATE : ItemViewDelegate<ITEM>> addItemViewDelegate(viewType: Int, delegate: DELEGATE) {
itemDelegates.put(viewType, delegate)
}
fun <DELEGATE : ItemViewDelegate<ITEM>> addItemViewDelegate(delegate: DELEGATE) {
itemDelegates.put(itemDelegates.size, delegate)
}
fun <DELEGATE : ItemViewDelegate<ITEM>> addItemViewDelegates(vararg delegates: DELEGATE) {
delegates.forEach {
addItemViewDelegate(it)
}
}
fun addItemViewDelegates(vararg delegates: Pair<Int, ItemViewDelegate<ITEM>>) {
delegates.forEach {
addItemViewDelegate(it.first, it.second)
}
}
fun addHeaderView(header: View) {
synchronized(lock) {
if (headerItems == null) {
headerItems = SparseArray()
}
headerItems?.let {
val index = it.size()
it.put(TYPE_HEADER_VIEW + it.size(), header)
notifyItemInserted(index)
}
}
}
fun addFooterView(footer: View) {
synchronized(lock) {
if (footerItems == null) {
footerItems = SparseArray()
}
footerItems?.let {
val index = getActualItemCount() + it.size()
it.put(TYPE_FOOTER_VIEW + it.size(), footer)
notifyItemInserted(index)
}
}
}
fun removeHeaderView(header: View) {
synchronized(lock) {
headerItems?.let {
val index = it.indexOfValue(header)
if (index >= 0) {
it.remove(index)
notifyItemRemoved(index)
}
}
}
}
fun removeFooterView(footer: View) {
synchronized(lock) {
footerItems?.let {
val index = it.indexOfValue(footer)
if (index >= 0) {
it.remove(index)
notifyItemRemoved(getActualItemCount() + index - 2)
}
}
}
}
fun setItems(items: List<ITEM>?) {
synchronized(lock) {
if (this.items.isNotEmpty()) {
this.items.clear()
}
if (items != null) {
this.items.addAll(items)
}
notifyDataSetChanged()
}
}
fun setItem(position: Int, item: ITEM) {
synchronized(lock) {
val oldSize = getActualItemCount()
if (position in 0 until oldSize) {
this.items[position] = item
notifyItemChanged(position + getHeaderCount())
}
}
}
fun addItem(item: ITEM) {
synchronized(lock) {
val oldSize = getActualItemCount()
if (this.items.add(item)) {
if (oldSize == 0) {
notifyDataSetChanged()
} else {
notifyItemInserted(oldSize + getHeaderCount())
}
}
}
}
fun addItems(position: Int, newItems: List<ITEM>) {
synchronized(lock) {
val oldSize = getActualItemCount()
if (position in 0 until oldSize) {
if (if (oldSize == 0) this.items.addAll(newItems) else this.items.addAll(position, newItems)) {
if (oldSize == 0) {
notifyDataSetChanged()
} else {
notifyItemRangeChanged(position + getHeaderCount(), newItems.size)
}
}
}
}
}
fun addItems(newItems: List<ITEM>) {
synchronized(lock) {
val oldSize = getActualItemCount()
if (this.items.addAll(newItems)) {
if (oldSize == 0) {
notifyDataSetChanged()
} else {
notifyItemRangeChanged(oldSize + getHeaderCount(), newItems.size)
}
}
}
}
fun removeItem(position: Int) {
synchronized(lock) {
if (this.items.removeAt(position) != null)
notifyItemRemoved(position + getHeaderCount())
}
}
fun removeItem(item: ITEM) {
synchronized(lock) {
if (this.items.remove(item))
notifyItemRemoved(this.items.indexOf(item) + getHeaderCount())
}
}
fun removeItems(items: List<ITEM>) {
synchronized(lock) {
if (this.items.removeAll(items))
notifyDataSetChanged()
}
}
fun swapItem(oldPosition: Int, newPosition: Int) {
synchronized(lock) {
val size = getActualItemCount()
if (oldPosition in 0 until size && newPosition in 0 until size) {
Collections.swap(this.items, oldPosition + getHeaderCount(), newPosition + getHeaderCount())
notifyDataSetChanged()
}
}
}
fun updateItem(position: Int, payload: Any) {
synchronized(lock) {
val size = getActualItemCount()
if (position in 0 until size) {
notifyItemChanged(position + getHeaderCount(), payload)
}
}
}
fun updateItems(fromPosition: Int, toPosition: Int, payloads: Any) {
synchronized(lock) {
val size = getActualItemCount()
if (fromPosition in 0 until size && toPosition in 0 until size) {
notifyItemRangeChanged(fromPosition + getHeaderCount(), toPosition - fromPosition + 1, payloads)
}
}
}
fun isEmpty(): Boolean {
return items.isEmpty()
}
fun isNotEmpty(): Boolean {
return items.isNotEmpty()
}
/**
* 除去header和footer
*/
fun getActualItemCount(): Int {
return items.size
}
fun getHeaderCount(): Int {
return headerItems?.size() ?: 0
}
fun getFooterCount(): Int {
return footerItems?.size() ?: 0
}
fun getItem(position: Int): ITEM = items[position % items.size]
fun getItems(): List<ITEM> = items
protected open fun getItemViewType(item: ITEM, position: Int): Int {
return 0
}
/**
* grid 模式下使用
*/
protected open fun getSpanSize(item: ITEM, viewType: Int, position: Int): Int {
return 1
}
final override fun getItemCount(): Int {
return getActualItemCount() + getHeaderCount() + getFooterCount()
}
final override fun getItemViewType(position: Int): Int {
return when {
isHeader(position) -> TYPE_HEADER_VIEW + position
isFooter(position) -> TYPE_FOOTER_VIEW + position - getActualItemCount() - getHeaderCount()
else -> getItemViewType(getItem(getRealPosition(position)), getRealPosition(position))
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ItemViewHolder {
return when {
viewType < TYPE_HEADER_VIEW + getHeaderCount() -> {
ItemViewHolder(headerItems!!.get(viewType))
}
viewType >= TYPE_FOOTER_VIEW -> {
ItemViewHolder(footerItems!!.get(viewType))
}
else -> {
val holder = ItemViewHolder(inflater.inflate(itemDelegates.getValue(viewType).layoutID, parent, false))
if (itemClickListener != null) {
holder.itemView.setOnClickListener {
itemClickListener!!.invoke(holder, getItem(holder.layoutPosition))
}
}
if (itemLongClickListener != null) {
holder.itemView.setOnLongClickListener {
itemLongClickListener!!.invoke(holder, getItem(holder.layoutPosition))
}
}
holder
}
}
}
override fun onBindViewHolder(holder: ItemViewHolder, position: Int) {
onBindViewHolder(holder, position, mutableListOf())
}
override fun onBindViewHolder(holder: ItemViewHolder, position: Int, payloads: MutableList<Any>) {
if (!isHeader(holder.layoutPosition) && !isFooter(holder.layoutPosition)) {
itemDelegates.getValue(getItemViewType(holder.layoutPosition))
.convert(holder, getItem(holder.layoutPosition), payloads)
}
}
override fun onViewAttachedToWindow(holder: ItemViewHolder) {
super.onViewAttachedToWindow(holder)
if (!isHeader(holder.layoutPosition) && !isFooter(holder.layoutPosition)) {
addAnimation(holder)
}
}
override fun onAttachedToRecyclerView(recyclerView: RecyclerView) {
super.onAttachedToRecyclerView(recyclerView)
val manager = recyclerView.layoutManager
if (manager is GridLayoutManager) {
manager.spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() {
override fun getSpanSize(position: Int): Int {
return if (isHeader(position) || isFooter(position)) manager.spanCount else getSpanSize(
getItem(position), getItemViewType(position), position
)
}
}
}
}
fun setAnimationConfig(item: ItemAnimation) {
itemAnimation = item
}
private fun isHeader(position: Int): Boolean {
return position < getHeaderCount()
}
private fun isFooter(position: Int): Boolean {
return position >= getActualItemCount() + getHeaderCount()
}
private fun getRealPosition(position: Int): Int {
return position - getHeaderCount()
}
private fun addAnimation(holder: ItemViewHolder) {
if (itemAnimation == null) {
itemAnimation = ItemAnimation.create().enabled(true)
}
itemAnimation?.let {
if (it.itemAnimEnabled) {
if (!it.itemAnimFirstOnly || holder.layoutPosition > it.itemAnimStartPosition) {
startAnimation(holder, it)
it.itemAnimStartPosition = holder.layoutPosition
}
}
}
}
protected open fun startAnimation(holder: ItemViewHolder, item: ItemAnimation) {
for (anim in item.itemAnimation.getAnimators(holder.itemView)) {
anim.setDuration(item.itemAnimDuration).start()
anim.interpolator = item.itemAnimInterpolator
}
}
companion object {
private const val TYPE_HEADER_VIEW = Int.MIN_VALUE
private const val TYPE_FOOTER_VIEW = Int.MAX_VALUE - 999
}
}

@ -0,0 +1,32 @@
package io.legado.app.base.adapter
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
/**
* Created by Invincible on 2017/12/15.
*
* 上拉加载更多
*/
abstract class InfiniteScrollListener() : RecyclerView.OnScrollListener() {
private val loadMoreRunnable = Runnable { onLoadMore() }
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
// if (dy < 0 || dataLoading.isDataLoading()) return
val layoutManager:LinearLayoutManager = recyclerView.layoutManager as LinearLayoutManager
val visibleItemCount = recyclerView.childCount
val totalItemCount = layoutManager.itemCount
val firstVisibleItem = layoutManager.findFirstVisibleItemPosition()
if (totalItemCount - visibleItemCount <= firstVisibleItem + VISIBLE_THRESHOLD) {
recyclerView.post(loadMoreRunnable)
}
}
abstract fun onLoadMore()
companion object {
private const val VISIBLE_THRESHOLD = 5
}
}

@ -0,0 +1,91 @@
package io.legado.app.base.adapter
import android.view.animation.Interpolator
import android.view.animation.LinearInterpolator
import io.legado.app.base.adapter.animations.AlphaInAnimation
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.
*/
class ItemAnimation private constructor() {
var itemAnimEnabled = false
var itemAnimFirstOnly = true
var itemAnimation: BaseAnimation = SlideInBottomAnimation()
var itemAnimInterpolator: Interpolator = LinearInterpolator()
var itemAnimDuration: Long = 300L
var itemAnimStartPosition: Int = -1
fun interpolator(interpolator: Interpolator): ItemAnimation {
itemAnimInterpolator = interpolator
return this
}
fun duration(duration: Long): ItemAnimation {
itemAnimDuration = duration
return this
}
fun startPostion(startPos: Int): ItemAnimation {
itemAnimStartPosition = startPos
return this
}
fun animation(animationType: Int = FADE_IN, animation: BaseAnimation? = null): ItemAnimation {
if (animation != null) {
itemAnimation = animation
} else {
when (animationType) {
FADE_IN -> itemAnimation = AlphaInAnimation()
SCALE_IN -> itemAnimation = ScaleInAnimation()
BOTTOM_SLIDE_IN -> itemAnimation = SlideInBottomAnimation()
LEFT_SLIDE_IN -> itemAnimation = SlideInLeftAnimation()
RIGHT_SLIDE_IN -> itemAnimation = SlideInRightAnimation()
}
}
return this
}
fun enabled(enabled: Boolean): ItemAnimation {
itemAnimEnabled = enabled
return this
}
fun firstOnly(firstOnly: Boolean): ItemAnimation {
itemAnimFirstOnly = firstOnly
return this
}
companion object {
/**
* Use with [.openLoadAnimation]
*/
const val FADE_IN: Int = 0x00000001
/**
* Use with [.openLoadAnimation]
*/
const val SCALE_IN: Int = 0x00000002
/**
* Use with [.openLoadAnimation]
*/
const val BOTTOM_SLIDE_IN: Int = 0x00000003
/**
* Use with [.openLoadAnimation]
*/
const val LEFT_SLIDE_IN: Int = 0x00000004
/**
* Use with [.openLoadAnimation]
*/
const val RIGHT_SLIDE_IN: Int = 0x00000005
fun create(): ItemAnimation {
return ItemAnimation()
}
}
}

@ -0,0 +1,16 @@
package io.legado.app.base.adapter
import android.content.Context
/**
* Created by Invincible on 2017/11/24.
*
* item代理
*/
abstract class ItemViewDelegate<in ITEM>(protected val context: Context) {
abstract val layoutID: Int
abstract fun convert(holder: ItemViewHolder, item: ITEM, payloads: MutableList<Any>)
}

@ -0,0 +1,9 @@
package io.legado.app.base.adapter
import android.view.View
import androidx.recyclerview.widget.RecyclerView
/**
* Created by Invincible on 2017/11/28.
*/
class ItemViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView)

@ -0,0 +1,26 @@
package io.legado.app.base.adapter
import android.content.Context
/**
* Created by Invincible on 2017/12/15.
*/
abstract class SimpleRecyclerAdapter<ITEM>(context: Context) : CommonRecyclerAdapter<ITEM>(context) {
init {
addItemViewDelegate(object : ItemViewDelegate<ITEM>(context) {
override val layoutID: Int
get() = this@SimpleRecyclerAdapter.layoutID
override fun convert(holder: ItemViewHolder, item: ITEM, payloads: MutableList<Any>) {
this@SimpleRecyclerAdapter.convert(holder, item, payloads)
}
})
}
abstract val layoutID: Int
abstract fun convert(holder: ItemViewHolder, item: ITEM, payloads: MutableList<Any>)
}

@ -0,0 +1,17 @@
package io.legado.app.base.adapter.animations
import android.animation.Animator
import android.animation.ObjectAnimator
import android.view.View
class AlphaInAnimation @JvmOverloads constructor(private val mFrom: Float = DEFAULT_ALPHA_FROM) : BaseAnimation {
override fun getAnimators(view: View): Array<Animator> =
arrayOf(ObjectAnimator.ofFloat(view, "alpha", mFrom, 1f))
companion object {
private const val DEFAULT_ALPHA_FROM = 0f
}
}

@ -0,0 +1,13 @@
package io.legado.app.base.adapter.animations
import android.animation.Animator
import android.view.View
/**
* adapter item 动画
*/
interface BaseAnimation {
fun getAnimators(view: View): Array<Animator>
}

@ -0,0 +1,20 @@
package io.legado.app.base.adapter.animations
import android.animation.Animator
import android.animation.ObjectAnimator
import android.view.View
class ScaleInAnimation @JvmOverloads constructor(private val mFrom: Float = DEFAULT_SCALE_FROM) : BaseAnimation {
override fun getAnimators(view: View): Array<Animator> {
val scaleX = ObjectAnimator.ofFloat(view, "scaleX", mFrom, 1f)
val scaleY = ObjectAnimator.ofFloat(view, "scaleY", mFrom, 1f)
return arrayOf(scaleX, scaleY)
}
companion object {
private const val DEFAULT_SCALE_FROM = .5f
}
}

@ -0,0 +1,13 @@
package io.legado.app.base.adapter.animations
import android.animation.Animator
import android.animation.ObjectAnimator
import android.view.View
import io.legado.app.base.adapter.animations.BaseAnimation
class SlideInBottomAnimation : BaseAnimation {
override fun getAnimators(view: View): Array<Animator> =
arrayOf(ObjectAnimator.ofFloat(view, "translationY", view.measuredHeight.toFloat(), 0f))
}

@ -0,0 +1,14 @@
package io.legado.app.base.adapter.animations
import android.animation.Animator
import android.animation.ObjectAnimator
import android.view.View
import io.legado.app.base.adapter.animations.BaseAnimation
class SlideInLeftAnimation : BaseAnimation {
override fun getAnimators(view: View): Array<Animator> =
arrayOf(ObjectAnimator.ofFloat(view, "translationX", -view.rootView.width.toFloat(), 0f))
}

@ -0,0 +1,14 @@
package io.legado.app.base.adapter.animations
import android.animation.Animator
import android.animation.ObjectAnimator
import android.view.View
import io.legado.app.base.adapter.animations.BaseAnimation
class SlideInRightAnimation : BaseAnimation {
override fun getAnimators(view: View): Array<Animator> =
arrayOf(ObjectAnimator.ofFloat(view, "translationX", view.rootView.width.toFloat(), 0f))
}

@ -2,29 +2,34 @@ package io.legado.app.data.entities
import android.os.Parcelable
import androidx.room.*
import io.legado.app.utils.strim
import kotlinx.android.parcel.Parcelize
@Parcelize
@Entity(tableName = "books", indices = [(Index(value = ["descUrl"]))])
@Entity(tableName = "books", indices = [(Index(value = ["descUrl"], unique = true))])
data class Book(@PrimaryKey
var descUrl: String = "", // 详情页Url(本地书源存储完整文件路径)
var tocUrl: String = "", // 目录页Url (toc=table of Contents)
var sourceId: Int = -1, // 书源规则id(默认-1,表示本地书籍)
var name: String = "", // 书籍名称(允许用户修改,适用于本地书籍)
var author: String? = null, // 作者名称(允许用户修改,适用于本地书籍)
var tag: String? = null, // 分类信息(允许用户修改,适用于本地书籍)
var coverUrl: String? = null, // 封面Url
var customCoverUrl: String? = null, // 自定义封面Url(允许用户修改,适用于网络和本地书籍)
var description: String? = null, // 简介内容(允许用户修改,适用于网络和本地书籍)
var name: String = "", // 书籍名称(书源获取)
var customName: String? = null, // 书籍名称(用户修改)
var author: String? = null, // 作者名称(书源获取)
var customAuthor: String? = null, // 作者名称(用户修改)
var tag: String? = null, // 分类信息(书源获取)
var customTag: String? = null, // 分类信息(用户修改)
var coverUrl: String? = null, // 封面Url(书源获取)
var customCoverUrl: String? = null, // 封面Url(用户修改)
var description: String? = null, // 简介内容(书源获取)
var customDescription: String? = null, // 简介内容(用户修改)
var charset: String? = null, // 自定义字符集名称(仅适用于本地书籍)
var type: Int = 0, // 0: 文本读物, 1: 有声读物
var group: Int = 0, // 自定义分组索引号
var tocUrl: String = "", // 目录页Url (toc=table of Contents)
var latestChapterTitle: String? = null, // 最新章节标题
var latestChapterTime: Long = 0, // 最新章节标题更新时间
var lastCheckTime: Long = 0, // 最近一次更新书籍信息的时间
var lastCheckCount: Int = 0, // 最近一次发现新章节的数量
var totalChapterNum: Int = 0, // 书籍目录总数
var durChapterTitle: String = "", // 当前章节名称
var durChapterTitle: String? = null, // 当前章节名称
var durChapterIndex: Int = 0, // 当前章节索引
var durChapterPos: Int = 0, // 当前阅读的进度(首行字符的索引位置)
var durChapterTime: Long = 0, // 最近一次阅读书籍的时间(打开正文的时间)
@ -34,4 +39,12 @@ data class Book(@PrimaryKey
fun getUnreadChapterNum() = Math.max(totalChapterNum - durChapterIndex - 1, 0)
fun getDisplayName() = customName.strim() ?: name
fun getDisplayAuthor() = customAuthor.strim() ?: author
fun getDisplayCover() = customCoverUrl.strim() ?: coverUrl
fun getDisplayDescription() = customDescription.strim() ?: description
}

@ -16,8 +16,8 @@ data class ReplaceRule(
val pattern: String? = null,
val replacement: String? = null,
val scope: String? = null,
val isEnabled: Boolean? = null,
val isRegex: Boolean? = null,
val isEnabled: Boolean = true,
val isRegex: Boolean = true,
val order: Int = 0
) : Parcelable

@ -1,2 +1,3 @@
package io.legado.app.data.entities
class SearchBook

@ -14,14 +14,14 @@ data class Source(@PrimaryKey(autoGenerate = true)
@ColumnInfo(name = "sourceId")
var id: Int = 0, // 编号
var name: String = "", // 名称
var origin: String = "", // 地址,包括 http/https
var origin: String = "", // 地址,包括 http/https
var type: Int = 0, // 类型,0 文本,1 音频
var group: String? = null, // 分组
var header: String? = null, // header
var loginUrl: String? = null, // 登录地址
var isEnabled: Boolean = true, // 是否启用
var lastUpdateTime: Long = 0, // 最后更新时间,用于排序
var serialNumber: Int = 0, // 手动排序编号
var customOrder: Int = 0, // 手动排序编号
var weight: Int = 0, // 智能排序的权重
var exploreRule: String? = null, // 发现规则
var searchRule: String? = null, // 搜索规则

@ -6,7 +6,7 @@ import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.StaggeredGridLayoutManager
class LayoutManager private constructor() {
object LayoutManager {
interface LayoutManagerFactory {
fun create(recyclerView: RecyclerView): RecyclerView.LayoutManager
@ -16,50 +16,46 @@ class LayoutManager private constructor() {
@Retention(AnnotationRetention.SOURCE)
annotation class Orientation
companion object {
fun linear(): LayoutManagerFactory {
return object : LayoutManagerFactory {
override fun create(recyclerView: RecyclerView): RecyclerView.LayoutManager {
return LinearLayoutManager(recyclerView.context)
}
fun linear(): LayoutManagerFactory {
return object : LayoutManagerFactory {
override fun create(recyclerView: RecyclerView): RecyclerView.LayoutManager {
return LinearLayoutManager(recyclerView.context)
}
}
}
fun linear(@Orientation orientation: Int, reverseLayout: Boolean): LayoutManagerFactory {
return object : LayoutManagerFactory {
override fun create(recyclerView: RecyclerView): RecyclerView.LayoutManager {
return LinearLayoutManager(recyclerView.context, orientation, reverseLayout)
}
fun linear(@Orientation orientation: Int, reverseLayout: Boolean): LayoutManagerFactory {
return object : LayoutManagerFactory {
override fun create(recyclerView: RecyclerView): RecyclerView.LayoutManager {
return LinearLayoutManager(recyclerView.context, orientation, reverseLayout)
}
}
}
fun grid(spanCount: Int): LayoutManagerFactory {
return object : LayoutManagerFactory {
override fun create(recyclerView: RecyclerView): RecyclerView.LayoutManager {
return GridLayoutManager(recyclerView.context, spanCount)
}
fun grid(spanCount: Int): LayoutManagerFactory {
return object : LayoutManagerFactory {
override fun create(recyclerView: RecyclerView): RecyclerView.LayoutManager {
return GridLayoutManager(recyclerView.context, spanCount)
}
}
}
fun grid(spanCount: Int, @Orientation orientation: Int, reverseLayout: Boolean): LayoutManagerFactory {
return object : LayoutManagerFactory {
override fun create(recyclerView: RecyclerView): RecyclerView.LayoutManager {
return GridLayoutManager(recyclerView.context, spanCount, orientation, reverseLayout)
}
fun grid(spanCount: Int, @Orientation orientation: Int, reverseLayout: Boolean): LayoutManagerFactory {
return object : LayoutManagerFactory {
override fun create(recyclerView: RecyclerView): RecyclerView.LayoutManager {
return GridLayoutManager(recyclerView.context, spanCount, orientation, reverseLayout)
}
}
}
fun staggeredGrid(spanCount: Int, @Orientation orientation: Int): LayoutManagerFactory {
return object : LayoutManagerFactory {
override fun create(recyclerView: RecyclerView): RecyclerView.LayoutManager {
return StaggeredGridLayoutManager(spanCount, orientation)
}
fun staggeredGrid(spanCount: Int, @Orientation orientation: Int): LayoutManagerFactory {
return object : LayoutManagerFactory {
override fun create(recyclerView: RecyclerView): RecyclerView.LayoutManager {
return StaggeredGridLayoutManager(spanCount, orientation)
}
}
}

@ -5,31 +5,28 @@ import android.os.Bundle
import android.view.Menu
import android.view.MenuItem
import androidx.appcompat.app.ActionBarDrawerToggle
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.GravityCompat
import androidx.drawerlayout.widget.DrawerLayout
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.ViewModelProviders
import com.google.android.material.navigation.NavigationView
import io.legado.app.R
import io.legado.app.base.BaseActivity
import io.legado.app.ui.search.SearchActivity
import io.legado.app.utils.longSnackbar
import kotlinx.android.synthetic.main.activity_main.*
import kotlinx.android.synthetic.main.app_bar_main.*
import org.jetbrains.anko.startActivity
import kotlinx.android.synthetic.main.view_titlebar.*
class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelectedListener {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
setSupportActionBar(toolbar)
class MainActivity : BaseActivity<MainDataBinding, MainViewModel>(), NavigationView.OnNavigationItemSelectedListener {
override val viewModel: MainViewModel
get() = ViewModelProvider.AndroidViewModelFactory.getInstance(application).create(MainViewModel::class.java)
override val layoutID: Int
get() = R.layout.activity_main
override fun onViewModelCreated(viewModel: MainViewModel, savedInstanceState: Bundle?) {
fab.setOnClickListener { startActivity(Intent(this, SearchActivity::class.java)) }
val toggle = ActionBarDrawerToggle(
this, drawer_layout, toolbar,
this, drawer_layout, titleBar.toolbar,
R.string.navigation_drawer_open,
R.string.navigation_drawer_close
)
@ -37,7 +34,6 @@ class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelecte
toggle.syncState()
nav_view.setNavigationItemSelectedListener(this)
val mainViewModel = ViewModelProviders.of(this).get(MainViewModel::class.java)
}
override fun onBackPressed() {
@ -55,14 +51,8 @@ class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelecte
return true
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
return when (item.itemId) {
R.id.action_settings -> true
else -> super.onOptionsItemSelected(item)
}
override fun onCompatOptionsItemSelected(item: MenuItem): Boolean {
return super.onCompatOptionsItemSelected(item)
}
override fun onNavigationItemSelected(item: MenuItem): Boolean {

@ -1,5 +0,0 @@
package io.legado.app.ui.main
class MainModel {
}

@ -4,7 +4,7 @@ import android.os.Bundle
import androidx.lifecycle.ViewModelProvider
import io.legado.app.R
import io.legado.app.base.BaseActivity
import io.legado.app.search.SearchDataBinding
import io.legado.app.help.LayoutManager
class SearchActivity : BaseActivity<SearchDataBinding, SearchViewModel>() {
@ -15,9 +15,6 @@ class SearchActivity : BaseActivity<SearchDataBinding, SearchViewModel>() {
get() = R.layout.activity_search
override fun onViewModelCreated(viewModel: SearchViewModel, savedInstanceState: Bundle?) {
dataBinding.searchViewModel = viewModel
}
}

@ -0,0 +1,34 @@
package io.legado.app.ui.search
import android.content.Context
import io.legado.app.R
import io.legado.app.base.adapter.ItemViewDelegate
import io.legado.app.base.adapter.ItemViewHolder
import io.legado.app.base.adapter.SimpleRecyclerAdapter
import io.legado.app.data.entities.SearchBook
import kotlinx.android.synthetic.main.item_search.view.*
class SearchAdapter(context: Context) : SimpleRecyclerAdapter<SearchBook>(context) {
init {
addItemViewDelegate(TestItemDelegate(context))
}
override val layoutID: Int
get() = R.layout.item_search
override fun convert(holder: ItemViewHolder, item: SearchBook, payloads: MutableList<Any>) {
holder.itemView.bookName.text = "我欲封天"
}
internal class TestItemDelegate(context: Context) : ItemViewDelegate<SearchBook>(context){
override val layoutID: Int
get() = TODO("not implemented") //To change initializer of created properties use File | Settings | File Templates.
override fun convert(holder: ItemViewHolder, item: SearchBook, payloads: MutableList<Any>) {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
}
}

@ -5,7 +5,12 @@ import android.content.res.ColorStateList
import android.graphics.PorterDuff
import android.graphics.drawable.Drawable
import android.util.AttributeSet
import android.view.Menu
import android.view.View
import androidx.annotation.ColorInt
import androidx.annotation.StyleRes
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.Toolbar
import androidx.core.graphics.drawable.DrawableCompat
import com.google.android.material.appbar.AppBarLayout
import io.legado.app.R
@ -13,11 +18,16 @@ import kotlinx.android.synthetic.main.view_titlebar.view.*
class TitleBar(context: Context, attrs: AttributeSet?) : AppBarLayout(context, attrs) {
val toolbar: Toolbar
val menu: Menu
get() = toolbar.menu
init {
inflate(context, R.layout.view_titlebar, this)
toolbar = findViewById(R.id.toolbar)
val a = context.obtainStyledAttributes(
attrs, R.styleable.TitleBar,
0, 0
R.attr.titleBarStyle, 0
)
val navigationIcon = a.getDrawable(R.styleable.TitleBar_navigationIcon)
val navigationContentDescription = a.getText(R.styleable.TitleBar_navigationContentDescription)
@ -25,28 +35,89 @@ class TitleBar(context: Context, attrs: AttributeSet?) : AppBarLayout(context, a
val navigationIconTintMode = a.getInt(R.styleable.TitleBar_navigationIconTintMode, 9)
val showNavigationIcon = a.getBoolean(R.styleable.TitleBar_showNavigationIcon, true)
val attachToActivity = a.getBoolean(R.styleable.TitleBar_attachToActivity, true)
val titleText = a.getString(R.styleable.TitleBar_title)
val subtitleText = a.getString(R.styleable.TitleBar_subtitle)
a.recycle()
if (showNavigationIcon) {
toolbar.apply {
toolbar.apply {
if(showNavigationIcon){
this.navigationIcon = navigationIcon
this.navigationContentDescription = navigationContentDescription
wrapDrawableTint(this.navigationIcon, navigationIconTint, navigationIconTintMode)
}
if (a.hasValue(R.styleable.TitleBar_titleTextAppearance)) {
this.setTitleTextAppearance(context, a.getResourceId(R.styleable.TitleBar_titleTextAppearance, 0))
}
if (a.hasValue(R.styleable.TitleBar_titleTextColor)) {
this.setTitleTextColor(a.getColor(R.styleable.TitleBar_titleTextColor, -0x1))
}
if (a.hasValue(R.styleable.TitleBar_subtitleTextAppearance)) {
this.setSubtitleTextAppearance(context, a.getResourceId(R.styleable.TitleBar_subtitleTextAppearance, 0))
}
if (a.hasValue(R.styleable.TitleBar_subtitleTextColor)) {
this.setSubtitleTextColor(a.getColor(R.styleable.TitleBar_subtitleTextColor, -0x1))
}
if(!titleText.isNullOrBlank()){
this.title = titleText
}
if(!subtitleText.isNullOrBlank()){
this.subtitle = subtitleText
}
}
if (attachToActivity) {
attachToActivity(context)
}
}
fun setNavigationOnClickListener(clickListener: ((View) -> Unit)){
toolbar.setNavigationOnClickListener(clickListener)
}
fun setTitle(title: CharSequence?) {
toolbar.title = title
}
fun setTitle(titleId: Int) {
toolbar.setTitle(titleId)
}
fun setSubTitle(subtitle: CharSequence?) {
toolbar.subtitle = subtitle
}
fun setSubTitle(subtitleId: Int) {
toolbar.setSubtitle(subtitleId)
}
fun setTitleTextColor(@ColorInt color: Int){
toolbar.setTitleTextColor(color)
}
fun setTitleTextAppearance(@StyleRes resId: Int){
toolbar.setTitleTextAppearance(context, resId)
}
fun setSubTitleTextColor(@ColorInt color: Int){
toolbar.setSubtitleTextColor(color)
}
fun setSubTitleTextAppearance(@StyleRes resId: Int){
toolbar.setSubtitleTextAppearance(context, resId)
}
private fun attachToActivity(context: Context) {
val activity = getCompatActivity(context)
activity?.let {
activity.setSupportActionBar(toolbar)
activity.supportActionBar?.let {
it.setDisplayHomeAsUpEnabled(true)
}
activity.supportActionBar?.setDisplayHomeAsUpEnabled(true)
}
}

@ -0,0 +1,6 @@
package io.legado.app.utils
fun String?.strim() = if (this.isNullOrBlank()) null else this.trim()
fun String.isAbsUrl() = this.startsWith("http://", true)
|| this.startsWith("https://", true)

@ -1,26 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.drawerlayout.widget.DrawerLayout
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/drawer_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
tools:openDrawer="start">
<include
layout="@layout/app_bar_main"
<layout 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">
<data class=".ui.main.MainDataBinding">
<variable name="MainViewModel" type="io.legado.app.ui.main.MainViewModel"/>
</data>
<androidx.drawerlayout.widget.DrawerLayout
android:id="@+id/drawer_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
<com.google.android.material.navigation.NavigationView
android:id="@+id/nav_view"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="start"
android:fitsSystemWindows="true"
app:headerLayout="@layout/nav_header_main"
app:menu="@menu/activity_main_drawer"/>
tools:openDrawer="start">
<include
layout="@layout/app_bar_main"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
<com.google.android.material.navigation.NavigationView
android:id="@+id/nav_view"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="start"
android:fitsSystemWindows="true"
app:headerLayout="@layout/nav_header_main"
app:menu="@menu/activity_main_drawer"/>
</androidx.drawerlayout.widget.DrawerLayout>
</androidx.drawerlayout.widget.DrawerLayout>
</layout>

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:app="http://schemas.android.com/apk/res-auto">
<data class=".search.SearchDataBinding">
<data class=".ui.search.SearchDataBinding">
<variable name="SearchViewModel" type="io.legado.app.ui.search.SearchViewModel"/>
</data>
@ -17,7 +17,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent"
/>
app:title="搜索"/>
<io.legado.app.ui.widget.dynamiclayout.DynamicFrameLayout
android:layout_width="match_parent"

@ -7,19 +7,10 @@
android:layout_height="match_parent"
tools:context=".ui.main.MainActivity">
<com.google.android.material.appbar.AppBarLayout
android:layout_height="wrap_content"
<io.legado.app.ui.widget.TitleBar
android:id="@+id/titleBar"
android:layout_width="match_parent"
android:theme="@style/AppTheme.AppBarOverlay">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:popupTheme="@style/AppTheme.PopupOverlay"/>
</com.google.android.material.appbar.AppBarLayout>
android:layout_height="wrap_content"/>
<include layout="@layout/content_main"/>

@ -0,0 +1,19 @@
<?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"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/bookName"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent"
android:text="测试"/>
</androidx.constraintlayout.widget.ConstraintLayout>

@ -5,4 +5,5 @@
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/AppTheme.AppBarOverlay"
app:popupTheme="@style/AppTheme.PopupOverlay"/>

@ -2,6 +2,12 @@
<resources>
<declare-styleable name="TitleBar">
<attr name="title" />
<attr name="subtitle" />
<attr name="titleTextAppearance" />
<attr name="titleTextColor" />
<attr name="subtitleTextAppearance" />
<attr name="subtitleTextColor" />
<attr name="attachToActivity" format="boolean" />
<attr name="showNavigationIcon" format="boolean" />
<attr name="navigationIcon" format="reference" />
@ -29,6 +35,8 @@
</attr>
</declare-styleable>
<attr name="titleBarStyle" format="reference" />
<declare-styleable name="DynamicFrameLayout">
<attr name="errorSrc" format="reference" />
<attr name="emptySrc" format="reference" />

Loading…
Cancel
Save