3430: Make list refresh/retry consistent (#3474)
* 3430: Make list refresh/retry consistent * 3430: Add swipe-to-refresh and use states in filter lists
This commit is contained in:
parent
57ed98f305
commit
23381d45d7
9 changed files with 117 additions and 59 deletions
|
@ -101,6 +101,9 @@ class ListsActivity : BaseActivity(), Injectable, HasAndroidInjector {
|
||||||
DividerItemDecoration(this, DividerItemDecoration.VERTICAL)
|
DividerItemDecoration(this, DividerItemDecoration.VERTICAL)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
binding.swipeRefreshLayout.setOnRefreshListener { viewModel.retryLoading() }
|
||||||
|
binding.swipeRefreshLayout.setColorSchemeResources(R.color.tusky_blue)
|
||||||
|
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
viewModel.state.collect(this@ListsActivity::update)
|
viewModel.state.collect(this@ListsActivity::update)
|
||||||
}
|
}
|
||||||
|
@ -166,6 +169,7 @@ class ListsActivity : BaseActivity(), Injectable, HasAndroidInjector {
|
||||||
private fun update(state: ListsViewModel.State) {
|
private fun update(state: ListsViewModel.State) {
|
||||||
adapter.submitList(state.lists)
|
adapter.submitList(state.lists)
|
||||||
binding.progressBar.visible(state.loadingState == LOADING)
|
binding.progressBar.visible(state.loadingState == LOADING)
|
||||||
|
binding.swipeRefreshLayout.isRefreshing = state.loadingState == LOADING
|
||||||
when (state.loadingState) {
|
when (state.loadingState) {
|
||||||
INITIAL, LOADING -> binding.messageView.hide()
|
INITIAL, LOADING -> binding.messageView.hide()
|
||||||
ERROR_NETWORK -> {
|
ERROR_NETWORK -> {
|
||||||
|
|
|
@ -89,9 +89,11 @@ class AccountListFragment : Fragment(R.layout.fragment_account_list), AccountAct
|
||||||
val layoutManager = LinearLayoutManager(view.context)
|
val layoutManager = LinearLayoutManager(view.context)
|
||||||
binding.recyclerView.layoutManager = layoutManager
|
binding.recyclerView.layoutManager = layoutManager
|
||||||
(binding.recyclerView.itemAnimator as SimpleItemAnimator).supportsChangeAnimations = false
|
(binding.recyclerView.itemAnimator as SimpleItemAnimator).supportsChangeAnimations = false
|
||||||
|
|
||||||
binding.recyclerView.addItemDecoration(DividerItemDecoration(view.context, DividerItemDecoration.VERTICAL))
|
binding.recyclerView.addItemDecoration(DividerItemDecoration(view.context, DividerItemDecoration.VERTICAL))
|
||||||
|
|
||||||
|
binding.swipeRefreshLayout.setOnRefreshListener { fetchAccounts() }
|
||||||
|
binding.swipeRefreshLayout.setColorSchemeResources(R.color.tusky_blue)
|
||||||
|
|
||||||
val pm = PreferenceManager.getDefaultSharedPreferences(view.context)
|
val pm = PreferenceManager.getDefaultSharedPreferences(view.context)
|
||||||
val animateAvatar = pm.getBoolean(PrefKeys.ANIMATE_GIF_AVATARS, false)
|
val animateAvatar = pm.getBoolean(PrefKeys.ANIMATE_GIF_AVATARS, false)
|
||||||
val animateEmojis = pm.getBoolean(PrefKeys.ANIMATE_CUSTOM_EMOJIS, false)
|
val animateEmojis = pm.getBoolean(PrefKeys.ANIMATE_CUSTOM_EMOJIS, false)
|
||||||
|
@ -287,6 +289,7 @@ class AccountListFragment : Fragment(R.layout.fragment_account_list), AccountAct
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
fetching = true
|
fetching = true
|
||||||
|
binding.swipeRefreshLayout.isRefreshing = true
|
||||||
|
|
||||||
if (fromId != null) {
|
if (fromId != null) {
|
||||||
binding.recyclerView.post { adapter.setBottomLoading(true) }
|
binding.recyclerView.post { adapter.setBottomLoading(true) }
|
||||||
|
@ -295,6 +298,7 @@ class AccountListFragment : Fragment(R.layout.fragment_account_list), AccountAct
|
||||||
viewLifecycleOwner.lifecycleScope.launch {
|
viewLifecycleOwner.lifecycleScope.launch {
|
||||||
try {
|
try {
|
||||||
val response = getFetchCallByListType(fromId)
|
val response = getFetchCallByListType(fromId)
|
||||||
|
|
||||||
if (!response.isSuccessful) {
|
if (!response.isSuccessful) {
|
||||||
onFetchAccountsFailure(Exception(response.message()))
|
onFetchAccountsFailure(Exception(response.message()))
|
||||||
return@launch
|
return@launch
|
||||||
|
@ -317,6 +321,7 @@ class AccountListFragment : Fragment(R.layout.fragment_account_list), AccountAct
|
||||||
|
|
||||||
private fun onFetchAccountsSuccess(accounts: List<TimelineAccount>, linkHeader: String?) {
|
private fun onFetchAccountsSuccess(accounts: List<TimelineAccount>, linkHeader: String?) {
|
||||||
adapter.setBottomLoading(false)
|
adapter.setBottomLoading(false)
|
||||||
|
binding.swipeRefreshLayout.isRefreshing = false
|
||||||
|
|
||||||
val links = HttpHeaderLink.parse(linkHeader)
|
val links = HttpHeaderLink.parse(linkHeader)
|
||||||
val next = HttpHeaderLink.findByRelationType(links, "next")
|
val next = HttpHeaderLink.findByRelationType(links, "next")
|
||||||
|
@ -366,6 +371,7 @@ class AccountListFragment : Fragment(R.layout.fragment_account_list), AccountAct
|
||||||
|
|
||||||
private fun onFetchAccountsFailure(throwable: Throwable) {
|
private fun onFetchAccountsFailure(throwable: Throwable) {
|
||||||
fetching = false
|
fetching = false
|
||||||
|
binding.swipeRefreshLayout.isRefreshing = false
|
||||||
Log.e(TAG, "Fetch failure", throwable)
|
Log.e(TAG, "Fetch failure", throwable)
|
||||||
|
|
||||||
if (adapter.itemCount == 0) {
|
if (adapter.itemCount == 0) {
|
||||||
|
|
|
@ -141,9 +141,13 @@ class ConversationsFragment :
|
||||||
binding.statusView.show()
|
binding.statusView.show()
|
||||||
|
|
||||||
if ((loadState.refresh as LoadState.Error).error is IOException) {
|
if ((loadState.refresh as LoadState.Error).error is IOException) {
|
||||||
binding.statusView.setup(R.drawable.elephant_offline, R.string.error_network, null)
|
binding.statusView.setup(R.drawable.elephant_offline, R.string.error_network) {
|
||||||
|
refreshContent()
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
binding.statusView.setup(R.drawable.elephant_error, R.string.error_generic, null)
|
binding.statusView.setup(R.drawable.elephant_error, R.string.error_generic) {
|
||||||
|
refreshContent()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
is LoadState.Loading -> {
|
is LoadState.Loading -> {
|
||||||
|
|
|
@ -12,8 +12,8 @@ import com.keylesspalace.tusky.entity.Filter
|
||||||
import com.keylesspalace.tusky.util.hide
|
import com.keylesspalace.tusky.util.hide
|
||||||
import com.keylesspalace.tusky.util.show
|
import com.keylesspalace.tusky.util.show
|
||||||
import com.keylesspalace.tusky.util.viewBinding
|
import com.keylesspalace.tusky.util.viewBinding
|
||||||
|
import com.keylesspalace.tusky.util.visible
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import java.io.IOException
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class FiltersActivity : BaseActivity(), FiltersListener {
|
class FiltersActivity : BaseActivity(), FiltersListener {
|
||||||
|
@ -33,10 +33,14 @@ class FiltersActivity : BaseActivity(), FiltersListener {
|
||||||
setDisplayHomeAsUpEnabled(true)
|
setDisplayHomeAsUpEnabled(true)
|
||||||
setDisplayShowHomeEnabled(true)
|
setDisplayShowHomeEnabled(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.addFilterButton.setOnClickListener {
|
binding.addFilterButton.setOnClickListener {
|
||||||
launchEditFilterActivity()
|
launchEditFilterActivity()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
binding.swipeRefreshLayout.setOnRefreshListener { loadFilters() }
|
||||||
|
binding.swipeRefreshLayout.setColorSchemeResources(R.color.tusky_blue)
|
||||||
|
|
||||||
setTitle(R.string.pref_title_timeline_filters)
|
setTitle(R.string.pref_title_timeline_filters)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -48,41 +52,46 @@ class FiltersActivity : BaseActivity(), FiltersListener {
|
||||||
|
|
||||||
private fun observeViewModel() {
|
private fun observeViewModel() {
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
viewModel.filters.collect { filters ->
|
viewModel.state.collect { state ->
|
||||||
binding.filtersView.show()
|
binding.progressBar.visible(state.loadingState == FiltersViewModel.LoadingState.LOADING)
|
||||||
binding.addFilterButton.show()
|
binding.swipeRefreshLayout.isRefreshing = state.loadingState == FiltersViewModel.LoadingState.LOADING
|
||||||
binding.filterProgressBar.hide()
|
binding.addFilterButton.visible(state.loadingState == FiltersViewModel.LoadingState.LOADED)
|
||||||
refreshFilterDisplay(filters)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
lifecycleScope.launch {
|
when (state.loadingState) {
|
||||||
viewModel.error.collect { error ->
|
FiltersViewModel.LoadingState.INITIAL, FiltersViewModel.LoadingState.LOADING -> binding.messageView.hide()
|
||||||
if (error is IOException) {
|
FiltersViewModel.LoadingState.ERROR_NETWORK -> {
|
||||||
binding.filterMessageView.setup(
|
binding.messageView.setup(R.drawable.elephant_offline, R.string.error_network) {
|
||||||
R.drawable.elephant_offline,
|
loadFilters()
|
||||||
R.string.error_network
|
}
|
||||||
) { loadFilters() }
|
binding.messageView.show()
|
||||||
} else {
|
}
|
||||||
binding.filterMessageView.setup(
|
FiltersViewModel.LoadingState.ERROR_OTHER -> {
|
||||||
R.drawable.elephant_error,
|
binding.messageView.setup(R.drawable.elephant_error, R.string.error_generic) {
|
||||||
R.string.error_generic
|
loadFilters()
|
||||||
) { loadFilters() }
|
}
|
||||||
|
binding.messageView.show()
|
||||||
|
}
|
||||||
|
FiltersViewModel.LoadingState.LOADED -> {
|
||||||
|
if (state.filters.isEmpty()) {
|
||||||
|
binding.messageView.setup(
|
||||||
|
R.drawable.elephant_friend_empty,
|
||||||
|
R.string.message_empty,
|
||||||
|
null
|
||||||
|
)
|
||||||
|
binding.messageView.show()
|
||||||
|
} else {
|
||||||
|
binding.messageView.hide()
|
||||||
|
binding.filtersList.adapter = FiltersAdapter(this@FiltersActivity, state.filters)
|
||||||
|
binding.filtersList.show()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun refreshFilterDisplay(filters: List<Filter>) {
|
|
||||||
binding.filtersView.adapter = FiltersAdapter(this, filters)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun loadFilters() {
|
private fun loadFilters() {
|
||||||
binding.filterMessageView.hide()
|
binding.filtersList.hide()
|
||||||
binding.filtersView.hide()
|
|
||||||
binding.addFilterButton.hide()
|
|
||||||
binding.filterProgressBar.show()
|
|
||||||
|
|
||||||
viewModel.load()
|
viewModel.load()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,7 @@ import com.keylesspalace.tusky.appstore.EventHub
|
||||||
import com.keylesspalace.tusky.appstore.PreferenceChangedEvent
|
import com.keylesspalace.tusky.appstore.PreferenceChangedEvent
|
||||||
import com.keylesspalace.tusky.entity.Filter
|
import com.keylesspalace.tusky.entity.Filter
|
||||||
import com.keylesspalace.tusky.network.MastodonApi
|
import com.keylesspalace.tusky.network.MastodonApi
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import retrofit2.HttpException
|
import retrofit2.HttpException
|
||||||
|
@ -18,27 +19,39 @@ class FiltersViewModel @Inject constructor(
|
||||||
private val api: MastodonApi,
|
private val api: MastodonApi,
|
||||||
private val eventHub: EventHub
|
private val eventHub: EventHub
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
val filters: MutableStateFlow<List<Filter>> = MutableStateFlow(listOf())
|
|
||||||
val error: MutableStateFlow<Throwable?> = MutableStateFlow(null)
|
enum class LoadingState {
|
||||||
|
INITIAL, LOADING, LOADED, ERROR_NETWORK, ERROR_OTHER
|
||||||
|
}
|
||||||
|
|
||||||
|
data class State(val filters: List<Filter>, val loadingState: LoadingState)
|
||||||
|
|
||||||
|
val state: Flow<State> get() = _state
|
||||||
|
private val _state = MutableStateFlow(State(emptyList(), LoadingState.INITIAL))
|
||||||
|
|
||||||
fun load() {
|
fun load() {
|
||||||
|
this@FiltersViewModel._state.value = _state.value.copy(loadingState = LoadingState.LOADING)
|
||||||
|
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
api.getFilters().fold(
|
api.getFilters().fold(
|
||||||
{ filters ->
|
{ filters ->
|
||||||
this@FiltersViewModel.filters.value = filters
|
this@FiltersViewModel._state.value = State(filters, LoadingState.LOADED)
|
||||||
},
|
},
|
||||||
{ throwable ->
|
{ throwable ->
|
||||||
if (throwable is HttpException && throwable.code() == 404) {
|
if (throwable is HttpException && throwable.code() == 404) {
|
||||||
api.getFiltersV1().fold(
|
api.getFiltersV1().fold(
|
||||||
{ filters ->
|
{ filters ->
|
||||||
this@FiltersViewModel.filters.value = filters.map { it.toFilter() }
|
this@FiltersViewModel._state.value = State(filters.map { it.toFilter() }, LoadingState.LOADED)
|
||||||
},
|
},
|
||||||
{ throwable ->
|
{ throwable ->
|
||||||
error.value = throwable
|
// TODO log errors (also below)
|
||||||
|
|
||||||
|
this@FiltersViewModel._state.value = _state.value.copy(loadingState = LoadingState.ERROR_OTHER)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
this@FiltersViewModel._state.value = _state.value.copy(loadingState = LoadingState.ERROR_OTHER)
|
||||||
} else {
|
} else {
|
||||||
error.value = throwable
|
this@FiltersViewModel._state.value = _state.value.copy(loadingState = LoadingState.ERROR_NETWORK)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ -49,7 +62,7 @@ class FiltersViewModel @Inject constructor(
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
api.deleteFilter(filter.id).fold(
|
api.deleteFilter(filter.id).fold(
|
||||||
{
|
{
|
||||||
filters.value = filters.value.filter { it.id != filter.id }
|
this@FiltersViewModel._state.value = State(this@FiltersViewModel._state.value.filters.filter { it.id != filter.id }, LoadingState.LOADED)
|
||||||
for (context in filter.context) {
|
for (context in filter.context) {
|
||||||
eventHub.dispatch(PreferenceChangedEvent(context))
|
eventHub.dispatch(PreferenceChangedEvent(context))
|
||||||
}
|
}
|
||||||
|
@ -58,7 +71,7 @@ class FiltersViewModel @Inject constructor(
|
||||||
if (throwable is HttpException && throwable.code() == 404) {
|
if (throwable is HttpException && throwable.code() == 404) {
|
||||||
api.deleteFilterV1(filter.id).fold(
|
api.deleteFilterV1(filter.id).fold(
|
||||||
{
|
{
|
||||||
filters.value = filters.value.filter { it.id != filter.id }
|
this@FiltersViewModel._state.value = State(this@FiltersViewModel._state.value.filters.filter { it.id != filter.id }, LoadingState.LOADED)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Snackbar.make(parent, "Error deleting filter '${filter.title}'", Snackbar.LENGTH_SHORT).show()
|
Snackbar.make(parent, "Error deleting filter '${filter.title}'", Snackbar.LENGTH_SHORT).show()
|
||||||
|
|
|
@ -245,9 +245,13 @@ class TimelineFragment :
|
||||||
binding.statusView.show()
|
binding.statusView.show()
|
||||||
|
|
||||||
if ((loadState.refresh as LoadState.Error).error is IOException) {
|
if ((loadState.refresh as LoadState.Error).error is IOException) {
|
||||||
binding.statusView.setup(R.drawable.elephant_offline, R.string.error_network)
|
binding.statusView.setup(R.drawable.elephant_offline, R.string.error_network) {
|
||||||
|
onRefresh()
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
binding.statusView.setup(R.drawable.elephant_error, R.string.error_generic)
|
binding.statusView.setup(R.drawable.elephant_error, R.string.error_generic) {
|
||||||
|
onRefresh()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
is LoadState.Loading -> {
|
is LoadState.Loading -> {
|
||||||
|
|
|
@ -10,21 +10,27 @@
|
||||||
android:id="@+id/includedToolbar"
|
android:id="@+id/includedToolbar"
|
||||||
layout="@layout/toolbar_basic" />
|
layout="@layout/toolbar_basic" />
|
||||||
|
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||||
android:id="@+id/filtersView"
|
android:id="@+id/swipeRefreshLayout"
|
||||||
|
android:layout_marginTop="?attr/actionBarSize"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent">
|
||||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
|
||||||
/>
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/filtersList"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />
|
||||||
|
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
|
||||||
|
|
||||||
<com.keylesspalace.tusky.view.BackgroundMessageView
|
<com.keylesspalace.tusky.view.BackgroundMessageView
|
||||||
android:id="@+id/filterMessageView"
|
android:id="@+id/messageView"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_gravity="center" />
|
android:layout_gravity="center" />
|
||||||
|
|
||||||
<ProgressBar
|
<ProgressBar
|
||||||
android:id="@+id/filterProgressBar"
|
android:id="@+id/progressBar"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_gravity="center" />
|
android:layout_gravity="center" />
|
||||||
|
@ -36,8 +42,7 @@
|
||||||
android:layout_margin="16dp"
|
android:layout_margin="16dp"
|
||||||
android:contentDescription="@string/filter_addition_title"
|
android:contentDescription="@string/filter_addition_title"
|
||||||
android:src="@drawable/ic_plus_24dp"
|
android:src="@drawable/ic_plus_24dp"
|
||||||
app:layout_anchor="@id/filtersView"
|
app:layout_anchor="@id/filtersList"
|
||||||
app:layout_anchorGravity="bottom|end" />
|
app:layout_anchorGravity="bottom|end" />
|
||||||
|
|
||||||
|
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
|
||||||
|
|
|
@ -13,14 +13,20 @@
|
||||||
android:id="@+id/includedToolbar"
|
android:id="@+id/includedToolbar"
|
||||||
layout="@layout/toolbar_basic" />
|
layout="@layout/toolbar_basic" />
|
||||||
|
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||||
android:id="@+id/listsRecycler"
|
android:id="@+id/swipeRefreshLayout"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="0dp"
|
android:layout_height="0dp"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintLeft_toLeftOf="parent"
|
app:layout_constraintLeft_toLeftOf="parent"
|
||||||
app:layout_constraintRight_toRightOf="parent"
|
app:layout_constraintRight_toRightOf="parent"
|
||||||
app:layout_constraintTop_toBottomOf="@id/includedToolbar" />
|
app:layout_constraintTop_toBottomOf="@id/includedToolbar">
|
||||||
|
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/listsRecycler"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent" />
|
||||||
|
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
|
||||||
|
|
||||||
<ProgressBar
|
<ProgressBar
|
||||||
android:id="@+id/progressBar"
|
android:id="@+id/progressBar"
|
||||||
|
|
|
@ -4,10 +4,17 @@
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent">
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||||
android:id="@+id/recyclerView"
|
android:id="@+id/swipeRefreshLayout"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent" />
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/recyclerView"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent" />
|
||||||
|
|
||||||
|
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
|
||||||
|
|
||||||
<com.keylesspalace.tusky.view.BackgroundMessageView
|
<com.keylesspalace.tusky.view.BackgroundMessageView
|
||||||
android:id="@+id/messageView"
|
android:id="@+id/messageView"
|
||||||
|
@ -16,4 +23,4 @@
|
||||||
android:layout_gravity="center"
|
android:layout_gravity="center"
|
||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
tools:visibility="visible" />
|
tools:visibility="visible" />
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
|
|
Loading…
Reference in a new issue