commit
c8cae859f8
@ -0,0 +1,2 @@ |
||||
package io.legado.app.data.entities |
||||
|
@ -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,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) |
@ -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> |
Loading…
Reference in new issue