Error artwork (#1000)
* Add new Elephant Friend images. Use them in ListsActivity. * Add error images to AccountListFragment * Add error images to Timeline & Notifications fragment. Needs rework. * Introduce BackgroundMessageView. Use it in AccountList. * Use correct button style for BackgroundMessageView Co-Authored-By: charlag <charlag@tutanota.com> * Use BackgroundMessageView * Add BackgroundMessageView docs * Re-color and document elephants * Apply feedback, disable refresh when error is shown * Fix string typo
This commit is contained in:
parent
6e610b1d9d
commit
c0c73f5c06
13 changed files with 652 additions and 188 deletions
|
@ -16,24 +16,19 @@
|
|||
package com.keylesspalace.tusky.fragment
|
||||
|
||||
import android.os.Bundle
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import androidx.recyclerview.widget.DividerItemDecoration
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import android.util.Log
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
|
||||
import androidx.recyclerview.widget.DividerItemDecoration
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import com.keylesspalace.tusky.AccountActivity
|
||||
import com.keylesspalace.tusky.AccountListActivity.Type
|
||||
import com.keylesspalace.tusky.BaseActivity
|
||||
import com.keylesspalace.tusky.R
|
||||
import com.keylesspalace.tusky.adapter.AccountAdapter
|
||||
import com.keylesspalace.tusky.adapter.BlocksAdapter
|
||||
import com.keylesspalace.tusky.adapter.FollowAdapter
|
||||
import com.keylesspalace.tusky.adapter.FollowRequestsAdapter
|
||||
import com.keylesspalace.tusky.adapter.MutesAdapter
|
||||
import com.keylesspalace.tusky.adapter.*
|
||||
import com.keylesspalace.tusky.di.Injectable
|
||||
import com.keylesspalace.tusky.entity.Account
|
||||
import com.keylesspalace.tusky.entity.Relationship
|
||||
|
@ -41,14 +36,15 @@ import com.keylesspalace.tusky.interfaces.AccountActionListener
|
|||
import com.keylesspalace.tusky.network.MastodonApi
|
||||
import com.keylesspalace.tusky.util.HttpHeaderLink
|
||||
import com.keylesspalace.tusky.util.ThemeUtils
|
||||
import com.keylesspalace.tusky.util.hide
|
||||
import com.keylesspalace.tusky.util.show
|
||||
import com.keylesspalace.tusky.view.EndlessOnScrollListener
|
||||
import kotlinx.android.synthetic.main.fragment_account_list.*
|
||||
|
||||
import javax.inject.Inject
|
||||
|
||||
import retrofit2.Call
|
||||
import retrofit2.Callback
|
||||
import retrofit2.Response
|
||||
import java.io.IOException
|
||||
import javax.inject.Inject
|
||||
|
||||
class AccountListFragment : BaseFragment(), AccountActionListener, Injectable {
|
||||
|
||||
|
@ -83,7 +79,7 @@ class AccountListFragment : BaseFragment(), AccountActionListener, Injectable {
|
|||
divider.setDrawable(drawable)
|
||||
recyclerView.addItemDecoration(divider)
|
||||
|
||||
adapter = when(type) {
|
||||
adapter = when (type) {
|
||||
Type.BLOCKS -> BlocksAdapter(this)
|
||||
Type.MUTES -> MutesAdapter(this)
|
||||
Type.FOLLOW_REQUESTS -> FollowRequestsAdapter(this)
|
||||
|
@ -143,7 +139,7 @@ class AccountListFragment : BaseFragment(), AccountActionListener, Injectable {
|
|||
val mutesAdapter = adapter as MutesAdapter
|
||||
val unmutedUser = mutesAdapter.removeItem(position)
|
||||
|
||||
if(unmutedUser != null) {
|
||||
if (unmutedUser != null) {
|
||||
Snackbar.make(recyclerView, R.string.confirmation_unmuted, Snackbar.LENGTH_LONG)
|
||||
.setAction(R.string.action_undo) {
|
||||
mutesAdapter.addItem(unmutedUser, position)
|
||||
|
@ -193,7 +189,7 @@ class AccountListFragment : BaseFragment(), AccountActionListener, Injectable {
|
|||
val blocksAdapter = adapter as BlocksAdapter
|
||||
val unblockedUser = blocksAdapter.removeItem(position)
|
||||
|
||||
if(unblockedUser != null) {
|
||||
if (unblockedUser != null) {
|
||||
Snackbar.make(recyclerView, R.string.confirmation_unblocked, Snackbar.LENGTH_LONG)
|
||||
.setAction(R.string.action_undo) {
|
||||
blocksAdapter.addItem(unblockedUser, position)
|
||||
|
@ -311,11 +307,36 @@ class AccountListFragment : BaseFragment(), AccountActionListener, Injectable {
|
|||
|
||||
fetching = false
|
||||
|
||||
if (adapter.itemCount == 0) {
|
||||
messageView.show()
|
||||
messageView.setup(
|
||||
R.drawable.elephant_friend_empty,
|
||||
R.string.message_empty,
|
||||
null
|
||||
)
|
||||
} else {
|
||||
messageView.hide()
|
||||
}
|
||||
}
|
||||
|
||||
private fun onFetchAccountsFailure(exception: Exception) {
|
||||
fetching = false
|
||||
Log.e(TAG, "Fetch failure", exception)
|
||||
|
||||
if (adapter.itemCount == 0) {
|
||||
messageView.show()
|
||||
if (exception is IOException) {
|
||||
messageView.setup(R.drawable.elephant_offline, R.string.error_network) {
|
||||
messageView.hide()
|
||||
this.fetchAccounts(null)
|
||||
}
|
||||
} else {
|
||||
messageView.setup(R.drawable.elephant_error, R.string.error_generic) {
|
||||
messageView.hide()
|
||||
this.fetchAccounts(null)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
|
|
@ -16,31 +16,19 @@
|
|||
package com.keylesspalace.tusky.fragment;
|
||||
|
||||
import android.app.Activity;
|
||||
import androidx.arch.core.util.Function;
|
||||
import androidx.lifecycle.Lifecycle;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Bundle;
|
||||
import android.preference.PreferenceManager;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
||||
import com.google.android.material.tabs.TabLayout;
|
||||
import androidx.core.util.Pair;
|
||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
|
||||
import androidx.appcompat.content.res.AppCompatResources;
|
||||
import androidx.recyclerview.widget.DividerItemDecoration;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import androidx.recyclerview.widget.SimpleItemAnimator;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
||||
import com.google.android.material.tabs.TabLayout;
|
||||
import com.keylesspalace.tusky.MainActivity;
|
||||
import com.keylesspalace.tusky.R;
|
||||
import com.keylesspalace.tusky.adapter.NotificationsAdapter;
|
||||
|
@ -64,10 +52,12 @@ import com.keylesspalace.tusky.util.ListUtils;
|
|||
import com.keylesspalace.tusky.util.PairedList;
|
||||
import com.keylesspalace.tusky.util.ThemeUtils;
|
||||
import com.keylesspalace.tusky.util.ViewDataUtils;
|
||||
import com.keylesspalace.tusky.view.BackgroundMessageView;
|
||||
import com.keylesspalace.tusky.view.EndlessOnScrollListener;
|
||||
import com.keylesspalace.tusky.viewdata.NotificationViewData;
|
||||
import com.keylesspalace.tusky.viewdata.StatusViewData;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.math.BigInteger;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
|
@ -76,7 +66,18 @@ import java.util.Objects;
|
|||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.arch.core.util.Function;
|
||||
import androidx.core.util.Pair;
|
||||
import androidx.lifecycle.Lifecycle;
|
||||
import androidx.recyclerview.widget.DividerItemDecoration;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import androidx.recyclerview.widget.SimpleItemAnimator;
|
||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||
import kotlin.Unit;
|
||||
import kotlin.collections.CollectionsKt;
|
||||
import retrofit2.Call;
|
||||
import retrofit2.Callback;
|
||||
|
@ -125,7 +126,7 @@ public class NotificationsFragment extends SFragment implements
|
|||
private SwipeRefreshLayout swipeRefreshLayout;
|
||||
private RecyclerView recyclerView;
|
||||
private ProgressBar progressBar;
|
||||
private TextView nothingMessageView;
|
||||
private BackgroundMessageView statusView;
|
||||
|
||||
private LinearLayoutManager layoutManager;
|
||||
private EndlessOnScrollListener scrollListener;
|
||||
|
@ -177,7 +178,7 @@ public class NotificationsFragment extends SFragment implements
|
|||
swipeRefreshLayout = rootView.findViewById(R.id.swipe_refresh_layout);
|
||||
recyclerView = rootView.findViewById(R.id.recycler_view);
|
||||
progressBar = rootView.findViewById(R.id.progress_bar);
|
||||
nothingMessageView = rootView.findViewById(R.id.nothing_message);
|
||||
statusView = rootView.findViewById(R.id.statusView);
|
||||
|
||||
swipeRefreshLayout.setOnRefreshListener(this);
|
||||
swipeRefreshLayout.setColorSchemeResources(R.color.tusky_blue);
|
||||
|
@ -208,20 +209,12 @@ public class NotificationsFragment extends SFragment implements
|
|||
bottomId = null;
|
||||
|
||||
((SimpleItemAnimator) recyclerView.getItemAnimator()).setSupportsChangeAnimations(false);
|
||||
setupNothingView();
|
||||
|
||||
sendFetchNotificationsRequest(null, null, FetchEnd.BOTTOM, -1);
|
||||
|
||||
return rootView;
|
||||
}
|
||||
|
||||
private void setupNothingView() {
|
||||
Drawable top = AppCompatResources.getDrawable(Objects.requireNonNull(getContext()),
|
||||
R.drawable.elephant_friend_empty);
|
||||
nothingMessageView.setCompoundDrawablesWithIntrinsicBounds(null, top, null, null);
|
||||
nothingMessageView.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
private void handleFavEvent(FavoriteEvent event) {
|
||||
Pair<Integer, Notification> posAndNotification =
|
||||
findReplyPosition(event.getStatusId());
|
||||
|
@ -332,6 +325,8 @@ public class NotificationsFragment extends SFragment implements
|
|||
|
||||
@Override
|
||||
public void onRefresh() {
|
||||
swipeRefreshLayout.setEnabled(true);
|
||||
this.statusView.setVisibility(View.GONE);
|
||||
Either<Placeholder, Notification> first = CollectionsKt.firstOrNull(this.notifications);
|
||||
String topId;
|
||||
if (first != null && first.isRight()) {
|
||||
|
@ -721,9 +716,9 @@ public class NotificationsFragment extends SFragment implements
|
|||
}
|
||||
|
||||
if (notifications.size() == 0 && adapter.getItemCount() == 0) {
|
||||
nothingMessageView.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
nothingMessageView.setVisibility(View.GONE);
|
||||
this.statusView.setVisibility(View.VISIBLE);
|
||||
this.statusView.setup(R.drawable.elephant_friend_empty, R.string.message_empty, null);
|
||||
|
||||
}
|
||||
swipeRefreshLayout.setRefreshing(false);
|
||||
progressBar.setVisibility(View.GONE);
|
||||
|
@ -736,6 +731,22 @@ public class NotificationsFragment extends SFragment implements
|
|||
new NotificationViewData.Placeholder(false);
|
||||
notifications.setPairedItem(position, placeholderVD);
|
||||
adapter.updateItemWithNotify(position, placeholderVD, true);
|
||||
} else if (this.notifications.isEmpty()) {
|
||||
this.statusView.setVisibility(View.VISIBLE);
|
||||
swipeRefreshLayout.setEnabled(false);
|
||||
if (exception instanceof IOException) {
|
||||
this.statusView.setup(R.drawable.elephant_offline, R.string.error_network, __ -> {
|
||||
this.progressBar.setVisibility(View.VISIBLE);
|
||||
this.onRefresh();
|
||||
return Unit.INSTANCE;
|
||||
});
|
||||
} else {
|
||||
this.statusView.setup(R.drawable.elephant_error, R.string.error_generic, __ -> {
|
||||
this.progressBar.setVisibility(View.VISIBLE);
|
||||
this.onRefresh();
|
||||
return Unit.INSTANCE;
|
||||
});
|
||||
}
|
||||
}
|
||||
Log.e(TAG, "Fetch failure: " + exception.getMessage());
|
||||
progressBar.setVisibility(View.GONE);
|
||||
|
|
|
@ -25,7 +25,6 @@ import android.view.LayoutInflater;
|
|||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
||||
import com.google.android.material.tabs.TabLayout;
|
||||
|
@ -56,9 +55,11 @@ import com.keylesspalace.tusky.util.ListUtils;
|
|||
import com.keylesspalace.tusky.util.PairedList;
|
||||
import com.keylesspalace.tusky.util.ThemeUtils;
|
||||
import com.keylesspalace.tusky.util.ViewDataUtils;
|
||||
import com.keylesspalace.tusky.view.BackgroundMessageView;
|
||||
import com.keylesspalace.tusky.view.EndlessOnScrollListener;
|
||||
import com.keylesspalace.tusky.viewdata.StatusViewData;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.math.BigInteger;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
@ -71,7 +72,6 @@ import javax.inject.Inject;
|
|||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.content.res.AppCompatResources;
|
||||
import androidx.arch.core.util.Function;
|
||||
import androidx.core.util.Pair;
|
||||
import androidx.lifecycle.Lifecycle;
|
||||
|
@ -86,6 +86,7 @@ import androidx.recyclerview.widget.SimpleItemAnimator;
|
|||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
|
||||
import at.connyduck.sparkbutton.helpers.Utils;
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||
import kotlin.Unit;
|
||||
import kotlin.collections.CollectionsKt;
|
||||
import retrofit2.Call;
|
||||
import retrofit2.Callback;
|
||||
|
@ -137,7 +138,7 @@ public class TimelineFragment extends SFragment implements
|
|||
private SwipeRefreshLayout swipeRefreshLayout;
|
||||
private RecyclerView recyclerView;
|
||||
private ProgressBar progressBar;
|
||||
private TextView nothingMessageView;
|
||||
private BackgroundMessageView statusView;
|
||||
|
||||
private TimelineAdapter adapter;
|
||||
private Kind kind;
|
||||
|
@ -220,13 +221,12 @@ public class TimelineFragment extends SFragment implements
|
|||
recyclerView = rootView.findViewById(R.id.recycler_view);
|
||||
swipeRefreshLayout = rootView.findViewById(R.id.swipe_refresh_layout);
|
||||
progressBar = rootView.findViewById(R.id.progress_bar);
|
||||
nothingMessageView = rootView.findViewById(R.id.nothing_message);
|
||||
statusView = rootView.findViewById(R.id.statusView);
|
||||
|
||||
setupSwipeRefreshLayout();
|
||||
setupRecyclerView();
|
||||
updateAdapter();
|
||||
setupTimelinePreferences();
|
||||
setupNothingView();
|
||||
|
||||
if (statuses.isEmpty()) {
|
||||
progressBar.setVisibility(View.VISIBLE);
|
||||
|
@ -376,10 +376,15 @@ public class TimelineFragment extends SFragment implements
|
|||
}
|
||||
}
|
||||
if (statuses.size() == 0) {
|
||||
nothingMessageView.setVisibility(View.VISIBLE);
|
||||
showNothing();
|
||||
}
|
||||
}
|
||||
|
||||
private void showNothing() {
|
||||
statusView.setVisibility(View.VISIBLE);
|
||||
statusView.setup(R.drawable.elephant_friend_empty, R.string.message_empty, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
|
@ -502,14 +507,10 @@ public class TimelineFragment extends SFragment implements
|
|||
super.onDestroyView();
|
||||
}
|
||||
|
||||
private void setupNothingView() {
|
||||
Drawable top = AppCompatResources.getDrawable(requireContext(), R.drawable.elephant_friend_empty);
|
||||
nothingMessageView.setCompoundDrawablesWithIntrinsicBounds(null, top, null, null);
|
||||
nothingMessageView.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRefresh() {
|
||||
swipeRefreshLayout.setEnabled(true);
|
||||
this.statusView.setVisibility(View.GONE);
|
||||
if (this.initialUpdateFailed) {
|
||||
updateCurrent();
|
||||
} else {
|
||||
|
@ -863,7 +864,6 @@ public class TimelineFragment extends SFragment implements
|
|||
|
||||
private void sendFetchTimelineRequest(@Nullable String fromId, @Nullable String uptoId,
|
||||
final FetchEnd fetchEnd, final int pos) {
|
||||
|
||||
if (kind == Kind.HOME) {
|
||||
TimelineRequestMode mode;
|
||||
// allow getting old statuses/fallbacks for network only for for bottom loading
|
||||
|
@ -872,13 +872,13 @@ public class TimelineFragment extends SFragment implements
|
|||
} else {
|
||||
mode = TimelineRequestMode.NETWORK;
|
||||
}
|
||||
timelineRepo.getStatuses(fromId, uptoId, LOAD_AT_ONCE, mode)
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.as(autoDisposable(from(this, Lifecycle.Event.ON_DESTROY)))
|
||||
.subscribe(
|
||||
(result) -> onFetchTimelineSuccess(result, fetchEnd, pos),
|
||||
(err) -> onFetchTimelineFailure(new Exception(err), fetchEnd, pos)
|
||||
);
|
||||
timelineRepo.getStatuses(fromId, uptoId, LOAD_AT_ONCE, mode)
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.as(autoDisposable(from(this, Lifecycle.Event.ON_DESTROY)))
|
||||
.subscribe(
|
||||
(result) -> onFetchTimelineSuccess(result, fetchEnd, pos),
|
||||
(err) -> onFetchTimelineFailure(new Exception(err), fetchEnd, pos)
|
||||
);
|
||||
} else {
|
||||
Callback<List<Status>> callback = new Callback<List<Status>>() {
|
||||
@Override
|
||||
|
@ -946,10 +946,11 @@ public class TimelineFragment extends SFragment implements
|
|||
updateBottomLoadingState(fetchEnd);
|
||||
progressBar.setVisibility(View.GONE);
|
||||
swipeRefreshLayout.setRefreshing(false);
|
||||
swipeRefreshLayout.setEnabled(true);
|
||||
if (this.statuses.size() == 0) {
|
||||
nothingMessageView.setVisibility(View.VISIBLE);
|
||||
this.showNothing();
|
||||
} else {
|
||||
nothingMessageView.setVisibility(View.GONE);
|
||||
this.statusView.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -968,6 +969,22 @@ public class TimelineFragment extends SFragment implements
|
|||
newViewData = new StatusViewData.Placeholder(placeholder.getId(), false);
|
||||
statuses.setPairedItem(position, newViewData);
|
||||
updateAdapter();
|
||||
} else if (this.statuses.isEmpty()) {
|
||||
swipeRefreshLayout.setEnabled(false);
|
||||
this.statusView.setVisibility(View.VISIBLE);
|
||||
if (exception instanceof IOException) {
|
||||
this.statusView.setup(R.drawable.elephant_offline, R.string.error_network, __ -> {
|
||||
this.progressBar.setVisibility(View.VISIBLE);
|
||||
this.onRefresh();
|
||||
return Unit.INSTANCE;
|
||||
});
|
||||
} else {
|
||||
this.statusView.setup(R.drawable.elephant_error, R.string.error_generic, __ -> {
|
||||
this.progressBar.setVisibility(View.VISIBLE);
|
||||
this.onRefresh();
|
||||
return Unit.INSTANCE;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Log.e(TAG, "Fetch Failure: " + exception.getMessage());
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue