Add EventHub, add fav, reblog events, improve timelines (#562)

* Add AppStore, add fav, reblog events

* Add events, add handling to Timeline

* Add event handling to Notifications

* Mostly finish events

* Fix unsubscribing

* Cleanup timeline

* Fix newStatusEvent in thread, fix deleteEvent

* Insert new toots only in specific timelines

* Add missing else

* Rename AppStore to EventHub

* Fix tests

* Use DiffUtils for timeline

* Fix empty timeline bug. Improve loading placeholder

* Fix AsyncListDiff, loading indicator, "load more"

* Timeline fixes & improvements.

Fix infinite loading. Remove spinner correctly.
Don't refresh timeline without need.
This commit is contained in:
Ivan Kupalov 2018-05-27 11:22:12 +03:00 committed by GitHub
commit 3756a1fd20
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
31 changed files with 1064 additions and 542 deletions

View file

@ -18,30 +18,35 @@ package com.keylesspalace.tusky.adapter;
import android.support.v7.widget.RecyclerView;
import android.view.View;
import android.widget.Button;
import android.widget.ProgressBar;
import com.keylesspalace.tusky.R;
import com.keylesspalace.tusky.interfaces.StatusActionListener;
public class PlaceholderViewHolder extends RecyclerView.ViewHolder {
public final class PlaceholderViewHolder extends RecyclerView.ViewHolder {
private Button loadMoreButton;
private ProgressBar progressBar;
PlaceholderViewHolder(View itemView) {
super(itemView);
loadMoreButton = itemView.findViewById(R.id.button_load_more);
progressBar = itemView.findViewById(R.id.progress_bar);
}
public void setup(boolean enabled, final StatusActionListener listener){
public void setup(boolean enabled, final StatusActionListener listener) {
this.setup(enabled, listener, false);
}
public void setup(boolean enabled, final StatusActionListener listener, boolean progress) {
loadMoreButton.setVisibility(progress ? View.GONE : View.VISIBLE);
progressBar.setVisibility(progress ? View.VISIBLE : View.GONE);
loadMoreButton.setEnabled(enabled);
if(enabled) {
loadMoreButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
loadMoreButton.setEnabled(false);
listener.onLoadMore(getAdapterPosition());
}
if (enabled) {
loadMoreButton.setOnClickListener(v -> {
loadMoreButton.setEnabled(false);
listener.onLoadMore(getAdapterPosition());
});
}
}

View file

@ -118,6 +118,11 @@ public class ThreadAdapter extends RecyclerView.Adapter {
notifyItemRangeInserted(end, statuses.size());
}
public void removeItem(int position) {
statuses.remove(position);
notifyItemRemoved(position);
}
public void clear() {
statuses.clear();
detailedStatusPosition = RecyclerView.NO_POSITION;

View file

@ -15,7 +15,7 @@
package com.keylesspalace.tusky.adapter;
import android.support.annotation.Nullable;
import android.support.annotation.NonNull;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
@ -25,29 +25,32 @@ import com.keylesspalace.tusky.R;
import com.keylesspalace.tusky.interfaces.StatusActionListener;
import com.keylesspalace.tusky.viewdata.StatusViewData;
import java.util.ArrayList;
import java.util.List;
public final class TimelineAdapter extends RecyclerView.Adapter {
public interface AdapterDataSource<T> {
int getItemCount();
T getItemAt(int pos);
}
public class TimelineAdapter extends RecyclerView.Adapter {
private static final int VIEW_TYPE_STATUS = 0;
private static final int VIEW_TYPE_FOOTER = 1;
private static final int VIEW_TYPE_PLACEHOLDER = 2;
private List<StatusViewData> statuses;
private StatusActionListener statusListener;
private FooterViewHolder.State footerState;
private final AdapterDataSource<StatusViewData> dataSource;
private final StatusActionListener statusListener;
private boolean mediaPreviewEnabled;
public TimelineAdapter(StatusActionListener statusListener) {
public TimelineAdapter(AdapterDataSource<StatusViewData> dataSource,
StatusActionListener statusListener) {
super();
statuses = new ArrayList<>();
this.dataSource = dataSource;
this.statusListener = statusListener;
footerState = FooterViewHolder.State.END;
mediaPreviewEnabled = true;
}
@NonNull
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) {
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int viewType) {
switch (viewType) {
default:
case VIEW_TYPE_STATUS: {
@ -55,11 +58,6 @@ public class TimelineAdapter extends RecyclerView.Adapter {
.inflate(R.layout.item_status, viewGroup, false);
return new StatusViewHolder(view);
}
case VIEW_TYPE_FOOTER: {
View view = LayoutInflater.from(viewGroup.getContext())
.inflate(R.layout.item_footer, viewGroup, false);
return new FooterViewHolder(view);
}
case VIEW_TYPE_PLACEHOLDER: {
View view = LayoutInflater.from(viewGroup.getContext())
.inflate(R.layout.item_status_placeholder, viewGroup, false);
@ -69,76 +67,39 @@ public class TimelineAdapter extends RecyclerView.Adapter {
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int position) {
if (position < statuses.size()) {
StatusViewData status = statuses.get(position);
if (status instanceof StatusViewData.Placeholder) {
PlaceholderViewHolder holder = (PlaceholderViewHolder) viewHolder;
holder.setup(!((StatusViewData.Placeholder) status).isLoading(), statusListener);
} else {
StatusViewHolder holder = (StatusViewHolder) viewHolder;
holder.setupWithStatus((StatusViewData.Concrete) status,
statusListener, mediaPreviewEnabled);
}
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder viewHolder, int position) {
StatusViewData status = dataSource.getItemAt(position);
if (status instanceof StatusViewData.Placeholder) {
PlaceholderViewHolder holder = (PlaceholderViewHolder) viewHolder;
holder.setup(!((StatusViewData.Placeholder) status).isLoading(),
statusListener, ((StatusViewData.Placeholder) status).isLoading());
} else {
FooterViewHolder holder = (FooterViewHolder) viewHolder;
holder.setState(footerState);
StatusViewHolder holder = (StatusViewHolder) viewHolder;
holder.setupWithStatus((StatusViewData.Concrete) status,
statusListener, mediaPreviewEnabled);
}
}
@Override
public int getItemCount() {
return statuses.size() + 1;
return dataSource.getItemCount();
}
@Override
public int getItemViewType(int position) {
if (position == statuses.size()) {
return VIEW_TYPE_FOOTER;
if (dataSource.getItemAt(position) instanceof StatusViewData.Placeholder) {
return VIEW_TYPE_PLACEHOLDER;
} else {
if (statuses.get(position) instanceof StatusViewData.Placeholder) {
return VIEW_TYPE_PLACEHOLDER;
} else {
return VIEW_TYPE_STATUS;
}
}
}
public void update(@Nullable List<StatusViewData> newStatuses) {
if (newStatuses == null || newStatuses.isEmpty()) {
return;
}
statuses.clear();
statuses.addAll(newStatuses);
notifyDataSetChanged();
}
public void addItems(List<StatusViewData> newStatuses) {
statuses.addAll(newStatuses);
notifyItemRangeInserted(statuses.size(), newStatuses.size());
}
public void changeItem(int position, StatusViewData newData, boolean notifyAdapter) {
statuses.set(position, newData);
if (notifyAdapter) notifyItemChanged(position);
}
public void clear() {
statuses.clear();
notifyDataSetChanged();
}
public void setFooterState(FooterViewHolder.State newFooterState) {
FooterViewHolder.State oldValue = footerState;
footerState = newFooterState;
if (footerState != oldValue) {
notifyItemChanged(statuses.size());
return VIEW_TYPE_STATUS;
}
}
public void setMediaPreviewEnabled(boolean enabled) {
mediaPreviewEnabled = enabled;
}
@Override
public long getItemId(int position) {
return dataSource.getItemAt(position).getViewDataId();
}
}