diff --git a/app/src/main/java/io/legado/app/base/BaseActivity.kt b/app/src/main/java/io/legado/app/base/BaseActivity.kt index 45a4679a6..f2f2aab07 100644 --- a/app/src/main/java/io/legado/app/base/BaseActivity.kt +++ b/app/src/main/java/io/legado/app/base/BaseActivity.kt @@ -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 : 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) } } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/data/AppDatabase.kt b/app/src/main/java/io/legado/app/data/AppDatabase.kt index 5fb47987f..327651493 100644 --- a/app/src/main/java/io/legado/app/data/AppDatabase.kt +++ b/app/src/main/java/io/legado/app/data/AppDatabase.kt @@ -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() { diff --git a/app/src/main/java/io/legado/app/data/entities/Book.kt b/app/src/main/java/io/legado/app/data/entities/Book.kt index b929e183c..fc601f9fe 100644 --- a/app/src/main/java/io/legado/app/data/entities/Book.kt +++ b/app/src/main/java/io/legado/app/data/entities/Book.kt @@ -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 + } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/data/entities/ReplaceRule.kt b/app/src/main/java/io/legado/app/data/entities/ReplaceRule.kt index fdfa41d3f..2436fb641 100644 --- a/app/src/main/java/io/legado/app/data/entities/ReplaceRule.kt +++ b/app/src/main/java/io/legado/app/data/entities/ReplaceRule.kt @@ -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 diff --git a/app/src/main/java/io/legado/app/data/entities/SearchBook.kt b/app/src/main/java/io/legado/app/data/entities/SearchBook.kt new file mode 100644 index 000000000..feecd7295 --- /dev/null +++ b/app/src/main/java/io/legado/app/data/entities/SearchBook.kt @@ -0,0 +1,2 @@ +package io.legado.app.data.entities + diff --git a/app/src/main/java/io/legado/app/data/entities/Source.kt b/app/src/main/java/io/legado/app/data/entities/Source.kt index 1c6cfcf18..aa38236b5 100644 --- a/app/src/main/java/io/legado/app/data/entities/Source.kt +++ b/app/src/main/java/io/legado/app/data/entities/Source.kt @@ -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, // 搜索规则 diff --git a/app/src/main/java/io/legado/app/help/LayoutManager.kt b/app/src/main/java/io/legado/app/help/LayoutManager.kt new file mode 100644 index 000000000..9fc551222 --- /dev/null +++ b/app/src/main/java/io/legado/app/help/LayoutManager.kt @@ -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) + } + } + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/main/MainActivity.kt b/app/src/main/java/io/legado/app/ui/main/MainActivity.kt index 2b0156d5d..d04315dd6 100644 --- a/app/src/main/java/io/legado/app/ui/main/MainActivity.kt +++ b/app/src/main/java/io/legado/app/ui/main/MainActivity.kt @@ -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(), 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 { diff --git a/app/src/main/java/io/legado/app/ui/main/MainModel.kt b/app/src/main/java/io/legado/app/ui/main/MainModel.kt deleted file mode 100644 index 86da567f6..000000000 --- a/app/src/main/java/io/legado/app/ui/main/MainModel.kt +++ /dev/null @@ -1,5 +0,0 @@ -package io.legado.app.ui.main - -class MainModel { - -} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/search/SearchActivity.kt b/app/src/main/java/io/legado/app/ui/search/SearchActivity.kt index 84ff4cc97..2d4f64ba2 100644 --- a/app/src/main/java/io/legado/app/ui/search/SearchActivity.kt +++ b/app/src/main/java/io/legado/app/ui/search/SearchActivity.kt @@ -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() { + + 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) } + } diff --git a/app/src/main/java/io/legado/app/ui/search/SearchViewModel.kt b/app/src/main/java/io/legado/app/ui/search/SearchViewModel.kt new file mode 100644 index 000000000..a9a847a5b --- /dev/null +++ b/app/src/main/java/io/legado/app/ui/search/SearchViewModel.kt @@ -0,0 +1,6 @@ +package io.legado.app.ui.search + +import android.app.Application +import androidx.lifecycle.AndroidViewModel + +class SearchViewModel(application: Application) : AndroidViewModel(application) diff --git a/app/src/main/java/io/legado/app/ui/widget/TitleBar.kt b/app/src/main/java/io/legado/app/ui/widget/TitleBar.kt index da7d6ba71..e7df5b914 100644 --- a/app/src/main/java/io/legado/app/ui/widget/TitleBar.kt +++ b/app/src/main/java/io/legado/app/ui/widget/TitleBar.kt @@ -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 + } + } } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/widget/dynamiclayout/DynamicFrameLayout.kt b/app/src/main/java/io/legado/app/ui/widget/dynamiclayout/DynamicFrameLayout.kt new file mode 100644 index 000000000..2b25fe55a --- /dev/null +++ b/app/src/main/java/io/legado/app/ui/widget/dynamiclayout/DynamicFrameLayout.kt @@ -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" + } +} diff --git a/app/src/main/java/io/legado/app/ui/widget/dynamiclayout/ViewSwitcher.kt b/app/src/main/java/io/legado/app/ui/widget/dynamiclayout/ViewSwitcher.kt new file mode 100644 index 000000000..b985d79f8 --- /dev/null +++ b/app/src/main/java/io/legado/app/ui/widget/dynamiclayout/ViewSwitcher.kt @@ -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() + +} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/utils/StringExtensions.kt b/app/src/main/java/io/legado/app/utils/StringExtensions.kt new file mode 100644 index 000000000..23f35a357 --- /dev/null +++ b/app/src/main/java/io/legado/app/utils/StringExtensions.kt @@ -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) diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index ac9a123b3..92789a7fb 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -1,26 +1,29 @@ - - - + + + + - - + tools:openDrawer="start"> + + + + - + + diff --git a/app/src/main/res/layout/activity_search.xml b/app/src/main/res/layout/activity_search.xml index e96e6ba5b..a7dfaad9b 100644 --- a/app/src/main/res/layout/activity_search.xml +++ b/app/src/main/res/layout/activity_search.xml @@ -1,9 +1,40 @@ - - - + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/view_dynamic.xml b/app/src/main/res/layout/view_dynamic.xml new file mode 100644 index 000000000..e94e2eaca --- /dev/null +++ b/app/src/main/res/layout/view_dynamic.xml @@ -0,0 +1,20 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/view_error.xml b/app/src/main/res/layout/view_error.xml new file mode 100644 index 000000000..844a5a969 --- /dev/null +++ b/app/src/main/res/layout/view_error.xml @@ -0,0 +1,27 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/view_loading.xml b/app/src/main/res/layout/view_loading.xml new file mode 100644 index 000000000..db49f3211 --- /dev/null +++ b/app/src/main/res/layout/view_loading.xml @@ -0,0 +1,19 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/view_titlebar.xml b/app/src/main/res/layout/view_titlebar.xml index 6f86e663a..f1befb3b3 100644 --- a/app/src/main/res/layout/view_titlebar.xml +++ b/app/src/main/res/layout/view_titlebar.xml @@ -1,16 +1,8 @@ - - - - - \ No newline at end of file + app:popupTheme="@style/AppTheme.PopupOverlay"/> diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml new file mode 100644 index 000000000..7456589c4 --- /dev/null +++ b/app/src/main/res/values/attrs.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 69b22338c..7e091a230 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -3,4 +3,6 @@ #008577 #00574B #D81B60 + + diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index 4ab4520ff..68b3b25a3 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -5,4 +5,9 @@ 8dp 176dp 16dp + + + 14sp + 16sp + 18sp \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index e2bf2033a..c7def64d4 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -13,5 +13,8 @@ Tools Share Send - Replace with your own action + + + 点击重试 + 正在加载 diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 16dbab30f..09ffdee29 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -1,7 +1,7 @@ - + + + + + +