Merge pull request #30 from gedoor/master

update
pull/481/head
Antecer 6 years ago committed by GitHub
commit c8cae859f8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 43
      app/src/main/java/io/legado/app/base/BaseActivity.kt
  2. 2
      app/src/main/java/io/legado/app/data/AppDatabase.kt
  3. 17
      app/src/main/java/io/legado/app/data/entities/Book.kt
  4. 4
      app/src/main/java/io/legado/app/data/entities/ReplaceRule.kt
  5. 2
      app/src/main/java/io/legado/app/data/entities/SearchBook.kt
  6. 4
      app/src/main/java/io/legado/app/data/entities/Source.kt
  7. 67
      app/src/main/java/io/legado/app/help/LayoutManager.kt
  8. 32
      app/src/main/java/io/legado/app/ui/main/MainActivity.kt
  9. 5
      app/src/main/java/io/legado/app/ui/main/MainModel.kt
  10. 18
      app/src/main/java/io/legado/app/ui/search/SearchActivity.kt
  11. 6
      app/src/main/java/io/legado/app/ui/search/SearchViewModel.kt
  12. 67
      app/src/main/java/io/legado/app/ui/widget/TitleBar.kt
  13. 221
      app/src/main/java/io/legado/app/ui/widget/dynamiclayout/DynamicFrameLayout.kt
  14. 29
      app/src/main/java/io/legado/app/ui/widget/dynamiclayout/ViewSwitcher.kt
  15. 6
      app/src/main/java/io/legado/app/utils/StringExtensions.kt
  16. 45
      app/src/main/res/layout/activity_main.xml
  17. 47
      app/src/main/res/layout/activity_search.xml
  18. 20
      app/src/main/res/layout/view_dynamic.xml
  19. 27
      app/src/main/res/layout/view_error.xml
  20. 19
      app/src/main/res/layout/view_loading.xml
  21. 14
      app/src/main/res/layout/view_titlebar.xml
  22. 40
      app/src/main/res/values/attrs.xml
  23. 2
      app/src/main/res/values/colors.xml
  24. 5
      app/src/main/res/values/dimens.xml
  25. 5
      app/src/main/res/values/strings.xml
  26. 26
      app/src/main/res/values/styles.xml

@ -2,17 +2,28 @@ package io.legado.app.base
import android.os.Bundle
import android.view.MenuItem
import androidx.annotation.LayoutRes
import androidx.appcompat.app.AppCompatActivity
import androidx.databinding.DataBindingUtil
import androidx.databinding.ViewDataBinding
import androidx.lifecycle.ViewModel
abstract class BaseActivity : AppCompatActivity() {
abstract class BaseActivity<BD : ViewDataBinding, VM : ViewModel> : AppCompatActivity() {
@LayoutRes
abstract fun getLayoutID(): Int
protected lateinit var dataBinding: BD
private set
protected abstract val viewModel: VM
protected abstract val layoutID: Int
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(getLayoutID())
dataBinding = DataBindingUtil.setContentView(this, layoutID)
onViewModelCreated(viewModel, savedInstanceState)
}
open fun onViewModelCreated(viewModel: VM, savedInstanceState: Bundle?){
}
override fun onOptionsItemSelected(item: MenuItem?): Boolean {
@ -22,7 +33,7 @@ abstract class BaseActivity : AppCompatActivity() {
return true
}
}
return if (item == null) true else onCompatOptionsItemSelected(item)
return if (item == null) false else onCompatOptionsItemSelected(item)
}
open fun onCompatOptionsItemSelected(item: MenuItem): Boolean {
@ -30,26 +41,18 @@ abstract class BaseActivity : AppCompatActivity() {
}
override fun setTitle(title: CharSequence?) {
supportActionBar?.let {
it.title = title
}
supportActionBar?.title = title
}
override fun setTitle(titleId: Int) {
supportActionBar?.let {
it.setTitle(titleId)
}
supportActionBar?.setTitle(titleId)
}
fun setSubTitle(subtitle: CharSequence?){
supportActionBar?.let {
it.subtitle = subtitle;
}
fun setSubTitle(subtitle: CharSequence?) {
supportActionBar?.subtitle = subtitle
}
fun setSubTitle(subtitleId: Int){
supportActionBar?.let {
it.setSubtitle(subtitleId)
}
fun setSubTitle(subtitleId: Int) {
supportActionBar?.setSubtitle(subtitleId)
}
}

@ -13,7 +13,7 @@ import io.legado.app.data.entities.ReplaceRule
import javax.xml.transform.Source
@Database(entities = [Book::class, Chapter::class, ReplaceRule::class, Source::class], version = 1, exportSchema = true)
@Database(entities = [Book::class, Chapter::class, ReplaceRule::class], version = 1, exportSchema = true)
// @TypeConverters(Converters::class)
abstract class AppDatabase : RoomDatabase() {

@ -2,15 +2,17 @@ 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 customName: String = "", // 书籍名称(用户修改)
var customName: String? = null, // 书籍名称(用户修改)
var author: String? = null, // 作者名称(书源获取)
var customAuthor: String? = null, // 作者名称(用户修改)
var tag: String? = null, // 分类信息(书源获取)
@ -22,13 +24,12 @@ data class Book(@PrimaryKey
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, // 最近一次阅读书籍的时间(打开正文的时间)
@ -38,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

@ -0,0 +1,2 @@
package io.legado.app.data.entities

@ -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, // 搜索规则

@ -0,0 +1,67 @@
package io.legado.app.help
import androidx.annotation.IntDef
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.StaggeredGridLayoutManager
class LayoutManager private constructor() {
interface LayoutManagerFactory {
fun create(recyclerView: RecyclerView): RecyclerView.LayoutManager
}
@IntDef(LinearLayoutManager.HORIZONTAL, LinearLayoutManager.VERTICAL)
@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(@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, @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)
}
}
}
}
}

@ -1,28 +1,29 @@
package io.legado.app.ui.main
import android.content.Intent
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.ViewModelProviders
import androidx.lifecycle.ViewModelProvider
import com.google.android.material.navigation.NavigationView
import io.legado.app.R
import io.legado.app.utils.longSnackbar
import io.legado.app.base.BaseActivity
import io.legado.app.ui.search.SearchActivity
import kotlinx.android.synthetic.main.activity_main.*
import kotlinx.android.synthetic.main.app_bar_main.*
class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelectedListener {
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 onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
override fun onViewModelCreated(viewModel: MainViewModel, savedInstanceState: Bundle?) {
setSupportActionBar(toolbar)
fab.setOnClickListener { it.longSnackbar(R.string.app_name) }
fab.setOnClickListener { startActivity(Intent(this, SearchActivity::class.java)) }
val toggle = ActionBarDrawerToggle(
this, drawer_layout, toolbar,
@ -33,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() {
@ -51,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 {
}

@ -1,13 +1,21 @@
package io.legado.app.ui.search
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.ViewModelProvider
import io.legado.app.R
import io.legado.app.base.BaseActivity
class SearchActivity : BaseActivity<SearchDataBinding, SearchViewModel>() {
override val viewModel: SearchViewModel
get() = ViewModelProvider.AndroidViewModelFactory.getInstance(application).create(SearchViewModel::class.java)
override val layoutID: Int
get() = R.layout.activity_search
override fun onViewModelCreated(viewModel: SearchViewModel, savedInstanceState: Bundle?) {
class SearchActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_search)
}
}

@ -0,0 +1,6 @@
package io.legado.app.ui.search
import android.app.Application
import androidx.lifecycle.AndroidViewModel
class SearchViewModel(application: Application) : AndroidViewModel(application)

@ -1,24 +1,46 @@
package io.legado.app.ui.widget
import android.content.Context
import android.content.res.ColorStateList
import android.graphics.PorterDuff
import android.graphics.drawable.Drawable
import android.util.AttributeSet
import android.widget.FrameLayout
import androidx.appcompat.app.AppCompatActivity
import androidx.core.graphics.drawable.DrawableCompat
import com.google.android.material.appbar.AppBarLayout
import io.legado.app.R
import kotlinx.android.synthetic.main.view_titlebar.view.*
class TitleBar(context: Context, attrs: AttributeSet?) : FrameLayout(context, attrs) {
class TitleBar(context: Context, attrs: AttributeSet?) : AppBarLayout(context, attrs) {
init {
inflate(context, R.layout.view_titlebar, this)
}
val a = context.obtainStyledAttributes(
attrs, R.styleable.TitleBar,
0, 0
)
val navigationIcon = a.getDrawable(R.styleable.TitleBar_navigationIcon)
val navigationContentDescription = a.getText(R.styleable.TitleBar_navigationContentDescription)
val navigationIconTint = a.getColorStateList(R.styleable.TitleBar_navigationIconTint)
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)
a.recycle()
override fun onAttachedToWindow() {
super.onAttachedToWindow()
attachToActivity()
if (showNavigationIcon) {
toolbar.apply {
this.navigationIcon = navigationIcon
this.navigationContentDescription = navigationContentDescription
wrapDrawableTint(this.navigationIcon, navigationIconTint, navigationIconTintMode)
}
}
if (attachToActivity) {
attachToActivity(context)
}
}
private fun attachToActivity(){
private fun attachToActivity(context: Context) {
val activity = getCompatActivity(context)
activity?.let {
activity.setSupportActionBar(toolbar)
@ -37,4 +59,35 @@ class TitleBar(context: Context, attrs: AttributeSet?) : FrameLayout(context, at
else -> null
}
}
private fun wrapDrawableTint(drawable: Drawable?, tintList: ColorStateList?, tintMode: Int) {
if (drawable == null || tintList == null) return
val wrappedDrawable = DrawableCompat.wrap(drawable.mutate())
DrawableCompat.setTintList(wrappedDrawable, tintList)
DrawableCompat.setTintMode(wrappedDrawable, intToMode(tintMode))
}
private fun intToMode(`val`: Int): PorterDuff.Mode {
when (`val`) {
0 -> return PorterDuff.Mode.CLEAR
1 -> return PorterDuff.Mode.SRC
2 -> return PorterDuff.Mode.DST
3 -> return PorterDuff.Mode.SRC_OVER
4 -> return PorterDuff.Mode.DST_OVER
5 -> return PorterDuff.Mode.SRC_IN
6 -> return PorterDuff.Mode.DST_IN
7 -> return PorterDuff.Mode.SRC_OUT
8 -> return PorterDuff.Mode.DST_OUT
9 -> return PorterDuff.Mode.SRC_ATOP
10 -> return PorterDuff.Mode.DST_ATOP
11 -> return PorterDuff.Mode.XOR
16 -> return PorterDuff.Mode.DARKEN
17 -> return PorterDuff.Mode.LIGHTEN
13 -> return PorterDuff.Mode.MULTIPLY
14 -> return PorterDuff.Mode.SCREEN
12 -> return PorterDuff.Mode.ADD
15 -> return PorterDuff.Mode.OVERLAY
else -> return PorterDuff.Mode.CLEAR
}
}
}

@ -0,0 +1,221 @@
package io.legado.app.ui.widget.dynamiclayout
import android.content.Context
import android.graphics.drawable.Drawable
import android.util.AttributeSet
import android.view.View
import android.view.ViewGroup
import android.widget.FrameLayout
import android.widget.ProgressBar
import androidx.appcompat.widget.AppCompatButton
import androidx.appcompat.widget.AppCompatImageView
import androidx.appcompat.widget.AppCompatTextView
import io.legado.app.R
import kotlinx.android.synthetic.main.view_dynamic.view.*
class DynamicFrameLayout(context: Context, attrs: AttributeSet?) : FrameLayout(context, attrs), ViewSwitcher {
private var errorView: View? = null
private var errorImage: AppCompatImageView? = null
private var errorTextView: AppCompatTextView? = null
private var actionBtn: AppCompatButton? = null
private var progressView: View? = null
private var progressBar: ProgressBar? = null
private var contentView: View? = null
private var errorIcon: Drawable? = null
private var emptyIcon: Drawable? = null
private var errorActionDescription: CharSequence? = null
private var emptyActionDescription: CharSequence? = null
private var emptyDescription: CharSequence? = null
private var errorAction: Action? = null
private var emptyAction: Action? = null
private var changeListener: OnVisibilityChangeListener? = null
init {
View.inflate(context, R.layout.view_dynamic, this)
val a = context.obtainStyledAttributes(attrs, R.styleable.DynamicFrameLayout)
errorIcon = a.getDrawable(R.styleable.DynamicFrameLayout_errorSrc)
emptyIcon = a.getDrawable(R.styleable.DynamicFrameLayout_emptySrc)
emptyActionDescription = a.getText(R.styleable.DynamicFrameLayout_emptyActionDescription)
emptyDescription = a.getText(R.styleable.DynamicFrameLayout_emptyDescription)
errorActionDescription = a.getText(R.styleable.DynamicFrameLayout_errorActionDescription)
if (errorActionDescription == null) {
errorActionDescription = context.getString(R.string.dynamic_click_retry)
}
a.recycle()
}
override fun onFinishInflate() {
super.onFinishInflate()
if (childCount > 2) {
contentView = getChildAt(2)
}
}
override fun addView(child: View) {
if (childCount > 2) {
throw IllegalStateException("DynamicFrameLayout can host only one direct child")
}
super.addView(child)
}
override fun addView(child: View, index: Int) {
if (childCount > 2) {
throw IllegalStateException("DynamicFrameLayout can host only one direct child")
}
super.addView(child, index)
}
override fun addView(child: View, params: ViewGroup.LayoutParams) {
if (childCount > 2) {
throw IllegalStateException("DynamicFrameLayout can host only one direct child")
}
super.addView(child, params)
}
override fun addView(child: View, index: Int, params: ViewGroup.LayoutParams) {
if (childCount > 2) {
throw IllegalStateException("DynamicFrameLayout can host only one direct child")
}
super.addView(child, index, params)
}
override fun showErrorView(message: CharSequence) {
ensureErrorView()
setViewVisible(errorView, true)
setViewVisible(contentView, false)
setViewVisible(progressView, false)
errorTextView?.text = message
errorImage?.setImageDrawable(errorIcon)
actionBtn?.let {
it.tag = ACTION_WHEN_ERROR
it.visibility = View.VISIBLE
if (errorActionDescription != null) {
it.text = errorActionDescription
}
}
dispatchVisibilityChanged(ViewSwitcher.SHOW_ERROR_VIEW)
}
override fun showErrorView(messageId: Int) {
showErrorView(resources.getText(messageId))
}
override fun showEmptyView() {
ensureErrorView()
setViewVisible(errorView, true)
setViewVisible(contentView, false)
setViewVisible(progressView, false)
errorTextView?.text = emptyDescription
errorImage?.setImageDrawable(emptyIcon)
actionBtn?.let {
it.tag = ACTION_WHEN_EMPTY
if (errorActionDescription != null) {
it.visibility = View.VISIBLE
it.text = errorActionDescription
} else {
it.visibility = View.INVISIBLE
}
}
dispatchVisibilityChanged(ViewSwitcher.SHOW_EMPTY_VIEW)
}
override fun showProgressView() {
ensureProgressView()
setViewVisible(errorView, false)
setViewVisible(contentView, false)
setViewVisible(progressView, true)
dispatchVisibilityChanged(ViewSwitcher.SHOW_PROGRESS_VIEW)
}
override fun showContentView() {
setViewVisible(errorView, false)
setViewVisible(contentView, true)
setViewVisible(progressView, false)
dispatchVisibilityChanged(ViewSwitcher.SHOW_CONTENT_VIEW)
}
fun setOnVisibilityChangeListener(listener: OnVisibilityChangeListener) {
changeListener = listener
}
fun setErrorAction(action: Action) {
errorAction = action
}
fun setEmptyAction(action: Action) {
emptyAction = action
}
private fun setViewVisible(view: View?, visible: Boolean) {
view?.let {
it.visibility = if (visible) View.VISIBLE else View.INVISIBLE
}
}
private fun ensureErrorView() {
if (errorView == null) {
errorView = errorViewStub.inflate()
errorImage = errorView?.findViewById(R.id.iv_error_image)
errorTextView = errorView?.findViewById(R.id.tv_error_message)
actionBtn = errorView?.findViewById(R.id.btn_error_retry)
actionBtn?.setOnClickListener {
when (it.tag) {
ACTION_WHEN_EMPTY -> emptyAction?.onAction(this@DynamicFrameLayout)
ACTION_WHEN_ERROR -> errorAction?.onAction(this@DynamicFrameLayout)
}
}
}
}
private fun ensureProgressView() {
if (progressView == null) {
progressView = progressViewStub.inflate()
progressBar = progressView?.findViewById(R.id.loading_progress)
}
}
private fun dispatchVisibilityChanged(@ViewSwitcher.Visibility visibility: Int) {
changeListener?.onVisibilityChanged(visibility)
}
interface Action {
fun onAction(switcher: ViewSwitcher)
}
interface OnVisibilityChangeListener {
fun onVisibilityChanged(@ViewSwitcher.Visibility visibility: Int)
}
companion object {
private const val ACTION_WHEN_ERROR = "ACTION_WHEN_ERROR"
private const val ACTION_WHEN_EMPTY = "ACTION_WHEN_EMPTY"
}
}

@ -0,0 +1,29 @@
package io.legado.app.ui.widget.dynamiclayout
import androidx.annotation.IntDef
import androidx.annotation.StringRes
interface ViewSwitcher {
companion object{
const val SHOW_CONTENT_VIEW = 0
const val SHOW_ERROR_VIEW = 1
const val SHOW_EMPTY_VIEW = 2
const val SHOW_PROGRESS_VIEW = 3
}
@Retention(AnnotationRetention.SOURCE)
@IntDef(SHOW_CONTENT_VIEW, SHOW_ERROR_VIEW, SHOW_EMPTY_VIEW, SHOW_PROGRESS_VIEW)
annotation class Visibility
fun showErrorView(message: CharSequence)
fun showErrorView(@StringRes messageId: Int)
fun showEmptyView()
fun showProgressView()
fun showContentView()
}

@ -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,9 +1,40 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="io.legado.app.ui.search.SearchActivity">
</androidx.constraintlayout.widget.ConstraintLayout>
<layout xmlns:app="http://schemas.android.com/apk/res-auto">
<data class=".ui.search.SearchDataBinding">
<variable name="SearchViewModel" type="io.legado.app.ui.search.SearchViewModel"/>
</data>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="io.legado.app.ui.search.SearchActivity">
<io.legado.app.ui.widget.TitleBar
android:id="@+id/titleBar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent"
/>
<io.legado.app.ui.widget.dynamiclayout.DynamicFrameLayout
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@id/titleBar">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/searchList"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</io.legado.app.ui.widget.dynamiclayout.DynamicFrameLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ViewStub
android:id="@+id/errorViewStub"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout="@layout/view_error" />
<ViewStub
android:id="@+id/progressViewStub"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout="@layout/view_loading" />
</merge>

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:orientation="vertical">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/iv_error_image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/tv_error_message"
style="@style/Style.Text.Primary.Normal"
android:paddingTop="16dp"
android:paddingBottom="16dp" />
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/btn_error_retry"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:backgroundTint="@color/colorAccent"
android:minWidth="96dp"
android:text="@string/dynamic_click_retry" />
</LinearLayout>

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:orientation="vertical">
<androidx.core.widget.ContentLoadingProgressBar
android:id="@+id/loading_progress"
android:layout_width="48dp"
android:layout_height="48dp"
style="@style/Widget.AppCompat.ProgressBar"/>
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/tv_loading_message"
style="@style/Style.Text.Second.Normal.Wrap"
android:paddingTop="16dp"
android:text="@string/dynamic_loading"/>
</LinearLayout>

@ -1,16 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.appbar.AppBarLayout
<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/appBar"
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/AppTheme.AppBarOverlay">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:popupTheme="@style/AppTheme.PopupOverlay"/>
</com.google.android.material.appbar.AppBarLayout>
app:popupTheme="@style/AppTheme.PopupOverlay"/>

@ -0,0 +1,40 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="TitleBar">
<attr name="attachToActivity" format="boolean" />
<attr name="showNavigationIcon" format="boolean" />
<attr name="navigationIcon" format="reference" />
<attr name="navigationContentDescription" format="reference|string" />
<attr name="navigationIconTint" format="color|reference" />
<attr name="navigationIconTintMode" format="enum">
<enum name="clear" value="0" />
<enum name="src" value="1" />
<enum name="dst" value="2" />
<enum name="src_over" value="3" />
<enum name="dst_over" value="4" />
<enum name="src_in" value="5" />
<enum name="dst_in" value="6" />
<enum name="src_out" value="7" />
<enum name="dst_out" value="8" />
<enum name="src_atop" value="9" />
<enum name="dst_atop" value="10" />
<enum name="xor" value="11" />
<enum name="darken" value="16" />
<enum name="lighten" value="17" />
<enum name="multiply" value="13" />
<enum name="screen" value="14" />
<enum name="add" value="12" />
<enum name="overlay" value="15" />
</attr>
</declare-styleable>
<declare-styleable name="DynamicFrameLayout">
<attr name="errorSrc" format="reference" />
<attr name="emptySrc" format="reference" />
<attr name="errorActionDescription" format="string|reference" />
<attr name="emptyActionDescription" format="string|reference" />
<attr name="emptyDescription" format="string|reference" />
</declare-styleable>
</resources>

@ -3,4 +3,6 @@
<color name="colorPrimary">#008577</color>
<color name="colorPrimaryDark">#00574B</color>
<color name="colorAccent">#D81B60</color>
</resources>

@ -5,4 +5,9 @@
<dimen name="nav_header_vertical_spacing">8dp</dimen>
<dimen name="nav_header_height">176dp</dimen>
<dimen name="fab_margin">16dp</dimen>
<dimen name="font_size_normal">14sp</dimen>
<dimen name="font_size_middle">16sp</dimen>
<dimen name="font_size_large">18sp</dimen>
</resources>

@ -13,5 +13,8 @@
<string name="menu_tools">Tools</string>
<string name="menu_share">Share</string>
<string name="menu_send">Send</string>
<string name="action">Replace with your own action</string>
<string name="dynamic_click_retry">点击重试</string>
<string name="dynamic_loading">正在加载</string>
</resources>

@ -1,7 +1,7 @@
<resources>
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
<!-- Customize your theme here. -->
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
@ -14,4 +14,28 @@
<style name="AppTheme.AppBarOverlay" parent="ThemeOverlay.AppCompat.Dark.ActionBar"/>
<style name="AppTheme.PopupOverlay" parent="ThemeOverlay.AppCompat.Light"/>
<style name="Style.Text.Primary.Normal" parent="android:Widget">
<item name="android:layout_width">match_parent</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:textSize">@dimen/font_size_normal</item>
<item name="android:textColor">#000</item>
<item name="android:includeFontPadding">false</item>
</style>
<style name="Style.Text.Primary.Normal.Wrap">
<item name="android:layout_width">wrap_content</item>
</style>
<style name="Style.Text.Second.Normal" parent="android:Widget">
<item name="android:layout_width">match_parent</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:textSize">@dimen/font_size_normal</item>
<item name="android:textColor">#676767</item>
<item name="android:includeFontPadding">false</item>
</style>
<style name="Style.Text.Second.Normal.Wrap">
<item name="android:layout_width">wrap_content</item>
</style>
</resources>

Loading…
Cancel
Save