adapter bug fix, added cursor to twitterlist adapter

This commit is contained in:
nuclearfog 2020-10-22 15:59:56 +02:00
parent 75adacf800
commit a9e1018861
No known key found for this signature in database
GPG Key ID: D5490E4A81F97B14
5 changed files with 324 additions and 131 deletions

View File

@ -6,6 +6,7 @@ import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.TextView;
import androidx.annotation.MainThread;
@ -16,16 +17,16 @@ import androidx.recyclerview.widget.RecyclerView.ViewHolder;
import com.squareup.picasso.Picasso;
import org.nuclearfog.twidda.R;
import org.nuclearfog.twidda.backend.holder.UserListList;
import org.nuclearfog.twidda.backend.items.TwitterList;
import org.nuclearfog.twidda.backend.items.TwitterUser;
import org.nuclearfog.twidda.backend.utils.FontTool;
import org.nuclearfog.twidda.database.GlobalSettings;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.List;
import static android.view.View.GONE;
import static android.view.View.INVISIBLE;
import static android.view.View.VISIBLE;
import static androidx.recyclerview.widget.RecyclerView.NO_POSITION;
import static org.nuclearfog.twidda.backend.utils.TimeString.getTimeString;
@ -35,28 +36,48 @@ import static org.nuclearfog.twidda.backend.utils.TimeString.getTimeString;
*
* @see org.nuclearfog.twidda.fragment.ListFragment
*/
public class ListAdapter extends Adapter<ListAdapter.ListHolder> {
public class ListAdapter extends Adapter<ViewHolder> {
private static final int NO_LOADING = -1;
private static final int ITEM_FOOTER = 0;
private static final int ITEM_LIST = 1;
private final ListClickListener listener;
private final NumberFormat formatter;
private final GlobalSettings settings;
private final List<TwitterList> data;
private final UserListList data;
private int loadingIndex;
public ListAdapter(ListClickListener listener, GlobalSettings settings) {
this.listener = listener;
this.settings = settings;
formatter = NumberFormat.getIntegerInstance();
data = new ArrayList<>();
data = new UserListList();
}
@MainThread
public void setData(List<TwitterList> newData) {
data.clear();
data.addAll(newData);
notifyDataSetChanged();
public void setData(UserListList newData) {
if (data.isEmpty() || !newData.hasPrevious()) {
data.replace(newData);
if (data.hasNext()) {
// Add footer
data.add(null);
}
notifyDataSetChanged();
} else {
int end = data.size() - 1;
if (!data.hasNext()) {
// remove footer
data.remove(end);
notifyItemRemoved(end);
}
data.addListAt(newData, end);
notifyItemRangeInserted(end, newData.size());
}
disableLoading();
}
@ -91,95 +112,145 @@ public class ListAdapter extends Adapter<ListAdapter.ListHolder> {
}
@Override
public int getItemViewType(int position) {
if (data.get(position) == null)
return ITEM_FOOTER;
return ITEM_LIST;
}
@NonNull
@Override
public ListHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_list, parent, false);
final ListHolder vh = new ListHolder(v);
FontTool.setViewFontAndColor(settings, v);
vh.pb_image.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
int position = vh.getLayoutPosition();
if (position != NO_POSITION) {
listener.onClick(data.get(position), ListClickListener.Action.PROFILE);
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
if (viewType == ITEM_LIST) {
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_list, parent, false);
final ListHolder vh = new ListHolder(v);
FontTool.setViewFontAndColor(settings, v);
vh.pb_image.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
int position = vh.getLayoutPosition();
if (position != NO_POSITION) {
listener.onClick(data.get(position), ListClickListener.Action.PROFILE);
}
}
}
});
vh.followList.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
int position = vh.getLayoutPosition();
if (position != NO_POSITION) {
listener.onClick(data.get(position), ListClickListener.Action.FOLLOW);
});
vh.followList.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
int position = vh.getLayoutPosition();
if (position != NO_POSITION) {
listener.onClick(data.get(position), ListClickListener.Action.FOLLOW);
}
}
}
});
vh.deleteList.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
int position = vh.getLayoutPosition();
if (position != NO_POSITION) {
listener.onClick(data.get(position), ListClickListener.Action.DELETE);
});
vh.deleteList.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
int position = vh.getLayoutPosition();
if (position != NO_POSITION) {
listener.onClick(data.get(position), ListClickListener.Action.DELETE);
}
}
}
});
vh.subscriberCount.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
int position = vh.getLayoutPosition();
if (position != NO_POSITION) {
listener.onClick(data.get(position), ListClickListener.Action.SUBSCRIBER);
});
vh.subscriberCount.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
int position = vh.getLayoutPosition();
if (position != NO_POSITION) {
listener.onClick(data.get(position), ListClickListener.Action.SUBSCRIBER);
}
}
}
});
vh.memberCount.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
int position = vh.getLayoutPosition();
if (position != NO_POSITION) {
listener.onClick(data.get(position), ListClickListener.Action.MEMBER);
});
vh.memberCount.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
int position = vh.getLayoutPosition();
if (position != NO_POSITION) {
listener.onClick(data.get(position), ListClickListener.Action.MEMBER);
}
}
}
});
return vh;
});
return vh;
} else {
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_placeholder, parent, false);
final PlaceHolder ph = new PlaceHolder(v);
ph.loadBtn.setTypeface(settings.getFontFace());
ph.loadBtn.setTextColor(settings.getFontColor());
ph.loadBtn.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
int position = ph.getLayoutPosition();
if (position != NO_POSITION) {
listener.onFooterClick(data.getNext());
ph.loadCircle.setVisibility(VISIBLE);
ph.loadBtn.setVisibility(INVISIBLE);
loadingIndex = position;
}
}
});
return ph;
}
}
@Override
public void onBindViewHolder(@NonNull ListHolder vh, int index) {
TwitterList item = data.get(index);
TwitterUser owner = item.getListOwner();
vh.title.setText(item.getTitle());
vh.ownername.setText(owner.getScreenname());
vh.description.setText(item.getDescription());
vh.createdAt.setText(getTimeString(item.getCreatedAt()));
vh.memberCount.setText(formatter.format(item.getMemberCount()));
vh.subscriberCount.setText(formatter.format(item.getSubscriberCount()));
if (settings.getImageLoad()) {
String pbLink = owner.getImageLink();
if (!owner.hasDefaultProfileImage()) {
pbLink += "_mini";
public void onBindViewHolder(@NonNull ViewHolder holder, int index) {
if (holder instanceof ListHolder) {
ListHolder vh = (ListHolder) holder;
TwitterList item = data.get(index);
TwitterUser owner = item.getListOwner();
vh.title.setText(item.getTitle());
vh.ownername.setText(owner.getScreenname());
vh.description.setText(item.getDescription());
vh.createdAt.setText(getTimeString(item.getCreatedAt()));
vh.memberCount.setText(formatter.format(item.getMemberCount()));
vh.subscriberCount.setText(formatter.format(item.getSubscriberCount()));
if (settings.getImageLoad()) {
String pbLink = owner.getImageLink();
if (!owner.hasDefaultProfileImage()) {
pbLink += "_mini";
}
Picasso.get().load(pbLink).error(R.drawable.no_image).into(vh.pb_image);
}
if (item.isFollowing()) {
vh.followList.setText(R.string.user_unfollow);
} else {
vh.followList.setText(R.string.user_follow);
}
if (item.isListOwner()) {
vh.followList.setVisibility(VISIBLE);
vh.deleteList.setVisibility(GONE);
} else {
vh.followList.setVisibility(GONE);
vh.deleteList.setVisibility(VISIBLE);
}
if (item.isPrivate()) {
vh.title.setCompoundDrawablesWithIntrinsicBounds(R.drawable.lock, 0, 0, 0);
} else {
vh.title.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0);
}
} else if (holder instanceof PlaceHolder) {
PlaceHolder placeHolder = (PlaceHolder) holder;
if (loadingIndex != NO_LOADING) {
placeHolder.loadCircle.setVisibility(VISIBLE);
placeHolder.loadBtn.setVisibility(INVISIBLE);
} else {
placeHolder.loadCircle.setVisibility(INVISIBLE);
placeHolder.loadBtn.setVisibility(VISIBLE);
}
Picasso.get().load(pbLink).error(R.drawable.no_image).into(vh.pb_image);
}
if (item.isFollowing()) {
vh.followList.setText(R.string.user_unfollow);
} else {
vh.followList.setText(R.string.user_follow);
}
if (item.isListOwner()) {
vh.followList.setVisibility(VISIBLE);
vh.deleteList.setVisibility(GONE);
} else {
vh.followList.setVisibility(GONE);
vh.deleteList.setVisibility(VISIBLE);
}
if (item.isPrivate()) {
vh.title.setCompoundDrawablesWithIntrinsicBounds(R.drawable.lock, 0, 0, 0);
} else {
vh.title.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0);
}
/**
* disable loading animation in footer
*/
public void disableLoading() {
if (loadingIndex != NO_LOADING) {
int oldIndex = loadingIndex;
loadingIndex = NO_LOADING;
notifyItemChanged(oldIndex);
}
}
@ -204,6 +275,18 @@ public class ListAdapter extends Adapter<ListAdapter.ListHolder> {
}
}
static class PlaceHolder extends ViewHolder {
final ProgressBar loadCircle;
final Button loadBtn;
PlaceHolder(@NonNull View v) {
super(v);
loadCircle = v.findViewById(R.id.placeholder_loading);
loadBtn = v.findViewById(R.id.placeholder_button);
}
}
/**
* Listener for an item
*/
@ -224,5 +307,12 @@ public class ListAdapter extends Adapter<ListAdapter.ListHolder> {
* @param action which button was clicked
*/
void onClick(TwitterList listItem, Action action);
/**
* called when the footer is clicked
*
* @param cursor next cursor of the list
*/
void onFooterClick(long cursor);
}
}

View File

@ -23,9 +23,6 @@ import org.nuclearfog.twidda.backend.items.TwitterUser;
import org.nuclearfog.twidda.backend.utils.FontTool;
import org.nuclearfog.twidda.database.GlobalSettings;
import java.util.ArrayList;
import java.util.List;
import static android.view.View.INVISIBLE;
import static android.view.View.VISIBLE;
import static androidx.recyclerview.widget.RecyclerView.NO_ID;
@ -45,77 +42,58 @@ public class UserAdapter extends Adapter<ViewHolder> {
private final UserClickListener itemClickListener;
private final GlobalSettings settings;
private final List<TwitterUser> users;
private long nextCursor;
private final TwitterUserList data;
private int loadingIndex;
public UserAdapter(UserClickListener itemClickListener, GlobalSettings settings) {
this.itemClickListener = itemClickListener;
this.settings = settings;
users = new ArrayList<>();
data = new TwitterUserList();
loadingIndex = NO_INDEX;
}
@MainThread
public void setData(@NonNull TwitterUserList data) {
if (users.isEmpty() || !data.hasPrevious()) {
if (!users.isEmpty()) {
// replace previous data
users.clear();
}
users.addAll(data);
if (data.hasNext()) {
public void setData(@NonNull TwitterUserList newData) {
if (data.isEmpty() || !newData.hasPrevious()) {
data.replace(newData);
if (newData.hasNext()) {
// add footer
users.add(null);
data.add(null);
}
notifyDataSetChanged();
} else {
int end = users.size() - 1;
if (!data.hasNext()) {
int end = data.size() - 1;
if (!newData.hasNext()) {
// remove footer
users.remove(end);
data.remove(end);
notifyItemRemoved(end);
} else {
disableLoading();
}
users.addAll(end, data);
notifyItemRangeInserted(end, data.size());
}
nextCursor = data.getNext();
loadingIndex = NO_INDEX;
}
/**
* disable loading animation in footer
*/
public void disableLoading() {
if (loadingIndex != NO_INDEX) {
int oldIndex = loadingIndex;
loadingIndex = NO_INDEX;
notifyItemChanged(oldIndex);
data.addListAt(newData, end);
notifyItemRangeInserted(end, newData.size());
}
disableLoading();
}
@Override
public int getItemCount() {
return users.size();
return data.size();
}
@Override
public long getItemId(int index) {
if (users.get(index) != null)
return users.get(index).getId();
if (data.get(index) != null)
return data.get(index).getId();
return NO_ID;
}
@Override
public int getItemViewType(int index) {
if (users.get(index) == null)
if (data.get(index) == null)
return ITEM_GAP;
return ITEM_USER;
}
@ -132,7 +110,7 @@ public class UserAdapter extends Adapter<ViewHolder> {
@Override
public void onClick(View v) {
int position = vh.getLayoutPosition();
TwitterUser user = users.get(position);
TwitterUser user = data.get(position);
if (position != NO_POSITION && user != null) {
itemClickListener.onUserClick(user);
}
@ -149,7 +127,7 @@ public class UserAdapter extends Adapter<ViewHolder> {
public void onClick(View v) {
int position = vh.getLayoutPosition();
if (position != NO_POSITION) {
itemClickListener.onFooterClick(nextCursor);
itemClickListener.onFooterClick(data.getNext());
vh.loadCircle.setVisibility(VISIBLE);
vh.loadBtn.setVisibility(INVISIBLE);
loadingIndex = position;
@ -163,7 +141,7 @@ public class UserAdapter extends Adapter<ViewHolder> {
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int index) {
TwitterUser user = users.get(index);
TwitterUser user = data.get(index);
if (holder instanceof ItemHolder && user != null) {
ItemHolder vh = (ItemHolder) holder;
vh.username.setText(user.getUsername());
@ -191,6 +169,17 @@ public class UserAdapter extends Adapter<ViewHolder> {
}
}
/**
* disable loading animation in footer
*/
public void disableLoading() {
if (loadingIndex != NO_INDEX) {
int oldIndex = loadingIndex;
loadingIndex = NO_INDEX;
notifyItemChanged(oldIndex);
}
}
private void setIcon(TextView tv, @DrawableRes int icon) {
tv.setCompoundDrawablesWithIntrinsicBounds(icon, 0, 0, 0);
@ -234,6 +223,11 @@ public class UserAdapter extends Adapter<ViewHolder> {
*/
void onUserClick(TwitterUser user);
/**
* handle footer click
*
* @param cursor next cursor of the list
*/
void onFooterClick(long cursor);
}
}

View File

@ -11,8 +11,19 @@ import java.util.LinkedList;
*/
public class TwitterUserList extends LinkedList<TwitterUser> {
private final long prevCursor, nextCursor;
private long prevCursor = 0;
private long nextCursor = 0;
public TwitterUserList() {
super();
}
/**
* creates an empty list with defined cursors
*
* @param prevCursor previous cursor of the list
* @param nextCursor next cursor of the list
*/
public TwitterUserList(long prevCursor, long nextCursor) {
super();
this.prevCursor = prevCursor;
@ -46,6 +57,44 @@ public class TwitterUserList extends LinkedList<TwitterUser> {
return nextCursor;
}
/**
* get previous cursor
*
* @return previous cursor
*/
public long getPrev() {
return prevCursor;
}
/**
* replace whole list including cursors
*
* @param list new list
*/
public void replace(TwitterUserList list) {
super.clear();
super.addAll(list);
prevCursor = list.getPrev();
nextCursor = list.getNext();
}
/**
* add a sublist at the bottom of this list including next cursor
*
* @param list new sublist
*/
public void addListAt(TwitterUserList list, int index) {
super.addAll(index, list);
nextCursor = list.getNext();
}
@Override
public void clear() {
prevCursor = 0;
nextCursor = 0;
super.clear();
}
@Override
@NonNull
public String toString() {

View File

@ -14,6 +14,10 @@ public class UserListList extends LinkedList<TwitterList> {
private long prevCursor = 0;
private long nextCursor = 0;
public UserListList() {
super();
}
/**
* @param list single list item
*/
@ -32,6 +36,15 @@ public class UserListList extends LinkedList<TwitterList> {
this.nextCursor = nextCursor;
}
/**
* set next list cursor
*
* @param next
*/
public void setNextCursor(long next) {
nextCursor = next;
}
/**
* check if list is linked to a previous list
@ -51,6 +64,15 @@ public class UserListList extends LinkedList<TwitterList> {
return nextCursor != 0;
}
/**
* get prev link to a list
*
* @return cursor
*/
public long getPrev() {
return prevCursor;
}
/**
* get next link to a list
*
@ -60,6 +82,37 @@ public class UserListList extends LinkedList<TwitterList> {
return nextCursor;
}
/**
* replace whole list including cursors
*
* @param list new list
*/
public void replace(UserListList list) {
super.clear();
super.addAll(list);
prevCursor = list.getPrev();
nextCursor = list.getNext();
}
/**
* add a sublist at the bottom of this list including next cursor
*
* @param list new sublist
* @param index position where to insert at
*/
public void addListAt(UserListList list, int index) {
super.addAll(index, list);
nextCursor = list.getNext();
}
@Override
public void clear() {
// resetting cursors
prevCursor = 0;
nextCursor = 0;
super.clear();
}
@Override
@NonNull
public String toString() {

View File

@ -27,12 +27,11 @@ import org.nuclearfog.twidda.adapter.ListAdapter;
import org.nuclearfog.twidda.adapter.ListAdapter.ListClickListener;
import org.nuclearfog.twidda.backend.TwitterListLoader;
import org.nuclearfog.twidda.backend.engine.EngineException;
import org.nuclearfog.twidda.backend.holder.UserListList;
import org.nuclearfog.twidda.backend.items.TwitterList;
import org.nuclearfog.twidda.backend.utils.ErrorHandler;
import org.nuclearfog.twidda.database.GlobalSettings;
import java.util.List;
import static android.content.DialogInterface.BUTTON_POSITIVE;
import static android.os.AsyncTask.Status.FINISHED;
import static android.os.AsyncTask.Status.RUNNING;
@ -189,6 +188,13 @@ public class ListFragment extends Fragment implements OnRefreshListener, ListCli
}
public void onFooterClick(long cursor) {
if (listTask != null && listTask.getStatus() != RUNNING) {
load(cursor);
}
}
@Override
public void onClick(DialogInterface dialog, int which) {
if (which == BUTTON_POSITIVE) {
@ -222,7 +228,7 @@ public class ListFragment extends Fragment implements OnRefreshListener, ListCli
*
* @param data List of Twitter list data
*/
public void setData(List<TwitterList> data) {
public void setData(UserListList data) {
adapter.setData(data);
setRefresh(false);
}
@ -273,6 +279,7 @@ public class ListFragment extends Fragment implements OnRefreshListener, ListCli
public void onError(@Nullable EngineException error) {
if (getContext() != null && error != null)
ErrorHandler.handleFailure(getContext(), error);
adapter.disableLoading();
setRefresh(false);
}