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..90c1c013f 100644 --- a/app/src/main/java/io/legado/app/base/BaseActivity.kt +++ b/app/src/main/java/io/legado/app/base/BaseActivity.kt @@ -2,19 +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) } + abstract fun onViewModelCreated(viewModel: VM, savedInstanceState: Bundle?) + override fun onOptionsItemSelected(item: MenuItem?): Boolean { item?.let { if (it.itemId == android.R.id.home) { @@ -22,7 +31,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 +39,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/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/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 6f0dca604..c7def64d4 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -13,4 +13,8 @@ Tools Share Send + + + 点击重试 + 正在加载 diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 16dbab30f..352e2f8cf 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -14,4 +14,28 @@ + + + + + +