Jumping to top capability and a progress/retry footer added to timelines.
This commit is contained in:
parent
6b684bceff
commit
2106d7a53c
9 changed files with 300 additions and 74 deletions
|
@ -4,6 +4,7 @@ import android.Manifest;
|
||||||
import android.app.ProgressDialog;
|
import android.app.ProgressDialog;
|
||||||
import android.content.ContentResolver;
|
import android.content.ContentResolver;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.content.DialogInterface;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
|
@ -55,6 +56,7 @@ import java.io.InputStream;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
|
@ -196,21 +198,11 @@ public class ComposeActivity extends AppCompatActivity {
|
||||||
VolleySingleton.getInstance(this).addToRequestQueue(request);
|
VolleySingleton.getInstance(this).addToRequestQueue(request);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onReadyFailure(Exception exception, final String content,
|
|
||||||
final String visibility, final boolean sensitive) {
|
|
||||||
doErrorDialog(R.string.error_media_upload_sending, R.string.action_retry,
|
|
||||||
new View.OnClickListener() {
|
|
||||||
@Override
|
|
||||||
public void onClick(View v) {
|
|
||||||
readyStatus(content, visibility, sensitive);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void readyStatus(final String content, final String visibility,
|
private void readyStatus(final String content, final String visibility,
|
||||||
final boolean sensitive) {
|
final boolean sensitive) {
|
||||||
final ProgressDialog dialog = ProgressDialog.show(this, "Finishing Media Upload",
|
final ProgressDialog dialog = ProgressDialog.show(this, "Finishing Media Upload",
|
||||||
"Uploading...", true);
|
"Uploading...", true, true);
|
||||||
|
final AsyncTask<Void, Void, Boolean> waitForMediaTask =
|
||||||
new AsyncTask<Void, Void, Boolean>() {
|
new AsyncTask<Void, Void, Boolean>() {
|
||||||
private Exception exception;
|
private Exception exception;
|
||||||
|
|
||||||
|
@ -235,7 +227,34 @@ public class ComposeActivity extends AppCompatActivity {
|
||||||
onReadyFailure(exception, content, visibility, sensitive);
|
onReadyFailure(exception, content, visibility, sensitive);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}.execute();
|
|
||||||
|
@Override
|
||||||
|
protected void onCancelled() {
|
||||||
|
removeAllMediaFromQueue();
|
||||||
|
super.onCancelled();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
dialog.setOnCancelListener(new DialogInterface.OnCancelListener() {
|
||||||
|
@Override
|
||||||
|
public void onCancel(DialogInterface dialog) {
|
||||||
|
/* Generating an interrupt by passing true here is important because an interrupt
|
||||||
|
* exception is the only thing that will kick the latch out of its waiting loop
|
||||||
|
* early. */
|
||||||
|
waitForMediaTask.cancel(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
waitForMediaTask.execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onReadyFailure(Exception exception, final String content, final String visibility,
|
||||||
|
final boolean sensitive) {
|
||||||
|
doErrorDialog(R.string.error_media_upload_sending, R.string.action_retry,
|
||||||
|
new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
readyStatus(content, visibility, sensitive);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -439,16 +458,26 @@ public class ComposeActivity extends AppCompatActivity {
|
||||||
cancelReadyingMedia(item);
|
cancelReadyingMedia(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void removeAllMediaFromQueue() {
|
||||||
|
for (Iterator<QueuedMedia> it = mediaQueued.iterator(); it.hasNext();) {
|
||||||
|
QueuedMedia item = it.next();
|
||||||
|
it.remove();
|
||||||
|
removeMediaFromQueue(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void downsizeMedia(final QueuedMedia item) {
|
private void downsizeMedia(final QueuedMedia item) {
|
||||||
item.setReadyStage(QueuedMedia.ReadyStage.DOWNSIZING);
|
item.setReadyStage(QueuedMedia.ReadyStage.DOWNSIZING);
|
||||||
InputStream stream;
|
InputStream stream = null;
|
||||||
try {
|
try {
|
||||||
stream = getContentResolver().openInputStream(item.getUri());
|
stream = getContentResolver().openInputStream(item.getUri());
|
||||||
} catch (FileNotFoundException e) {
|
} catch (FileNotFoundException e) {
|
||||||
|
IOUtils.closeQuietly(stream);
|
||||||
onMediaDownsizeFailure(item);
|
onMediaDownsizeFailure(item);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Bitmap bitmap = BitmapFactory.decodeStream(stream);
|
Bitmap bitmap = BitmapFactory.decodeStream(stream);
|
||||||
|
IOUtils.closeQuietly(stream);
|
||||||
new DownsizeImageTask(STATUS_MEDIA_SIZE_LIMIT, new DownsizeImageTask.Listener() {
|
new DownsizeImageTask(STATUS_MEDIA_SIZE_LIMIT, new DownsizeImageTask.Listener() {
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(List<byte[]> contentList) {
|
public void onSuccess(List<byte[]> contentList) {
|
||||||
|
@ -538,13 +567,15 @@ public class ComposeActivity extends AppCompatActivity {
|
||||||
public DataItem getData() {
|
public DataItem getData() {
|
||||||
byte[] content = item.getContent();
|
byte[] content = item.getContent();
|
||||||
if (content == null) {
|
if (content == null) {
|
||||||
InputStream stream;
|
InputStream stream = null;
|
||||||
try {
|
try {
|
||||||
stream = getContentResolver().openInputStream(item.getUri());
|
stream = getContentResolver().openInputStream(item.getUri());
|
||||||
} catch (FileNotFoundException e) {
|
} catch (FileNotFoundException e) {
|
||||||
|
IOUtils.closeQuietly(stream);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
content = inputStreamGetBytes(stream);
|
content = inputStreamGetBytes(stream);
|
||||||
|
IOUtils.closeQuietly(stream);
|
||||||
if (content == null) {
|
if (content == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -607,10 +638,11 @@ public class ComposeActivity extends AppCompatActivity {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "image": {
|
case "image": {
|
||||||
InputStream stream;
|
InputStream stream = null;
|
||||||
try {
|
try {
|
||||||
stream = contentResolver.openInputStream(uri);
|
stream = contentResolver.openInputStream(uri);
|
||||||
} catch (FileNotFoundException e) {
|
} catch (FileNotFoundException e) {
|
||||||
|
IOUtils.closeQuietly(stream);
|
||||||
displayTransientError(R.string.error_media_upload_opening);
|
displayTransientError(R.string.error_media_upload_opening);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -618,7 +650,9 @@ public class ComposeActivity extends AppCompatActivity {
|
||||||
Bitmap bitmap = ThumbnailUtils.extractThumbnail(source, 96, 96);
|
Bitmap bitmap = ThumbnailUtils.extractThumbnail(source, 96, 96);
|
||||||
source.recycle();
|
source.recycle();
|
||||||
try {
|
try {
|
||||||
|
if (stream != null) {
|
||||||
stream.close();
|
stream.close();
|
||||||
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
bitmap.recycle();
|
bitmap.recycle();
|
||||||
displayTransientError(R.string.error_media_upload_opening);
|
displayTransientError(R.string.error_media_upload_opening);
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
package com.keylesspalace.tusky;
|
||||||
|
|
||||||
|
public interface FooterActionListener {
|
||||||
|
void onLoadMore();
|
||||||
|
}
|
18
app/src/main/java/com/keylesspalace/tusky/IOUtils.java
Normal file
18
app/src/main/java/com/keylesspalace/tusky/IOUtils.java
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
package com.keylesspalace.tusky;
|
||||||
|
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
|
||||||
|
public class IOUtils {
|
||||||
|
public static void closeQuietly(@Nullable InputStream stream) {
|
||||||
|
try {
|
||||||
|
if (stream != null) {
|
||||||
|
stream.close();
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
// intentionally unhandled
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -9,8 +9,11 @@ import android.text.style.ImageSpan;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.Button;
|
||||||
import android.widget.ImageButton;
|
import android.widget.ImageButton;
|
||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
|
import android.widget.LinearLayout;
|
||||||
|
import android.widget.ProgressBar;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import com.android.volley.toolbox.ImageLoader;
|
import com.android.volley.toolbox.ImageLoader;
|
||||||
|
@ -21,25 +24,42 @@ import java.util.Date;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class TimelineAdapter extends RecyclerView.Adapter {
|
public class TimelineAdapter extends RecyclerView.Adapter {
|
||||||
private List<Status> statuses = new ArrayList<>();
|
private static final int VIEW_TYPE_STATUS = 0;
|
||||||
|
private static final int VIEW_TYPE_FOOTER = 1;
|
||||||
|
|
||||||
StatusActionListener listener;
|
private List<Status> statuses;
|
||||||
|
private StatusActionListener statusListener;
|
||||||
|
private FooterActionListener footerListener;
|
||||||
|
|
||||||
public TimelineAdapter(StatusActionListener listener) {
|
public TimelineAdapter(StatusActionListener statusListener,
|
||||||
|
FooterActionListener footerListener) {
|
||||||
super();
|
super();
|
||||||
this.listener = listener;
|
statuses = new ArrayList<>();
|
||||||
|
this.statusListener = statusListener;
|
||||||
|
this.footerListener = footerListener;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) {
|
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) {
|
||||||
View v = LayoutInflater.from(viewGroup.getContext())
|
switch (viewType) {
|
||||||
|
default:
|
||||||
|
case VIEW_TYPE_STATUS: {
|
||||||
|
View view = LayoutInflater.from(viewGroup.getContext())
|
||||||
.inflate(R.layout.item_status, viewGroup, false);
|
.inflate(R.layout.item_status, viewGroup, false);
|
||||||
return new ViewHolder(v);
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int position) {
|
public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int position) {
|
||||||
ViewHolder holder = (ViewHolder) viewHolder;
|
if (position < statuses.size()) {
|
||||||
|
StatusViewHolder holder = (StatusViewHolder) viewHolder;
|
||||||
Status status = statuses.get(position);
|
Status status = statuses.get(position);
|
||||||
holder.setDisplayName(status.getDisplayName());
|
holder.setDisplayName(status.getDisplayName());
|
||||||
holder.setUsername(status.getUsername());
|
holder.setUsername(status.getUsername());
|
||||||
|
@ -57,35 +77,48 @@ public class TimelineAdapter extends RecyclerView.Adapter {
|
||||||
}
|
}
|
||||||
Status.MediaAttachment[] attachments = status.getAttachments();
|
Status.MediaAttachment[] attachments = status.getAttachments();
|
||||||
boolean sensitive = status.getSensitive();
|
boolean sensitive = status.getSensitive();
|
||||||
holder.setMediaPreviews(attachments, sensitive, listener);
|
holder.setMediaPreviews(attachments, sensitive, statusListener);
|
||||||
/* A status without attachments is sometimes still marked sensitive, so it's necessary
|
/* A status without attachments is sometimes still marked sensitive, so it's necessary
|
||||||
* to check both whether there are any attachments and if it's marked sensitive. */
|
* to check both whether there are any attachments and if it's marked sensitive. */
|
||||||
if (!sensitive || attachments.length == 0) {
|
if (!sensitive || attachments.length == 0) {
|
||||||
holder.hideSensitiveMediaWarning();
|
holder.hideSensitiveMediaWarning();
|
||||||
}
|
}
|
||||||
holder.setupButtons(listener, position);
|
holder.setupButtons(statusListener, position);
|
||||||
if (status.getVisibility() == Status.Visibility.PRIVATE) {
|
if (status.getVisibility() == Status.Visibility.PRIVATE) {
|
||||||
holder.disableReblogging();
|
holder.disableReblogging();
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
FooterViewHolder holder = (FooterViewHolder) viewHolder;
|
||||||
|
holder.setupButton(footerListener);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getItemCount() {
|
public int getItemCount() {
|
||||||
return statuses.size();
|
return statuses.size() + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int update(List<Status> new_statuses) {
|
@Override
|
||||||
|
public int getItemViewType(int position) {
|
||||||
|
if (position == statuses.size()) {
|
||||||
|
return VIEW_TYPE_FOOTER;
|
||||||
|
} else {
|
||||||
|
return VIEW_TYPE_STATUS;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int update(List<Status> newStatuses) {
|
||||||
int scrollToPosition;
|
int scrollToPosition;
|
||||||
if (statuses == null || statuses.isEmpty()) {
|
if (statuses == null || statuses.isEmpty()) {
|
||||||
statuses = new_statuses;
|
statuses = newStatuses;
|
||||||
scrollToPosition = 0;
|
scrollToPosition = 0;
|
||||||
} else {
|
} else {
|
||||||
int index = new_statuses.indexOf(statuses.get(0));
|
int index = newStatuses.indexOf(statuses.get(0));
|
||||||
if (index == -1) {
|
if (index == -1) {
|
||||||
statuses.addAll(0, new_statuses);
|
statuses.addAll(0, newStatuses);
|
||||||
scrollToPosition = 0;
|
scrollToPosition = 0;
|
||||||
} else {
|
} else {
|
||||||
statuses.addAll(0, new_statuses.subList(0, index));
|
statuses.addAll(0, newStatuses.subList(0, index));
|
||||||
scrollToPosition = index;
|
scrollToPosition = index;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -93,10 +126,10 @@ public class TimelineAdapter extends RecyclerView.Adapter {
|
||||||
return scrollToPosition;
|
return scrollToPosition;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addItems(List<Status> new_statuses) {
|
public void addItems(List<Status> newStatuses) {
|
||||||
int end = statuses.size();
|
int end = statuses.size();
|
||||||
statuses.addAll(new_statuses);
|
statuses.addAll(newStatuses);
|
||||||
notifyItemRangeInserted(end, new_statuses.size());
|
notifyItemRangeInserted(end, newStatuses.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void removeItem(int position) {
|
public void removeItem(int position) {
|
||||||
|
@ -104,11 +137,14 @@ public class TimelineAdapter extends RecyclerView.Adapter {
|
||||||
notifyItemRemoved(position);
|
notifyItemRemoved(position);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Status getItem(int position) {
|
public @Nullable Status getItem(int position) {
|
||||||
|
if (position >= 0 && position < statuses.size()) {
|
||||||
return statuses.get(position);
|
return statuses.get(position);
|
||||||
}
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
public static class ViewHolder extends RecyclerView.ViewHolder {
|
public static class StatusViewHolder extends RecyclerView.ViewHolder {
|
||||||
private TextView displayName;
|
private TextView displayName;
|
||||||
private TextView username;
|
private TextView username;
|
||||||
private TextView sinceCreated;
|
private TextView sinceCreated;
|
||||||
|
@ -128,7 +164,7 @@ public class TimelineAdapter extends RecyclerView.Adapter {
|
||||||
private NetworkImageView mediaPreview3;
|
private NetworkImageView mediaPreview3;
|
||||||
private View sensitiveMediaWarning;
|
private View sensitiveMediaWarning;
|
||||||
|
|
||||||
public ViewHolder(View itemView) {
|
public StatusViewHolder(View itemView) {
|
||||||
super(itemView);
|
super(itemView);
|
||||||
displayName = (TextView) itemView.findViewById(R.id.status_display_name);
|
displayName = (TextView) itemView.findViewById(R.id.status_display_name);
|
||||||
username = (TextView) itemView.findViewById(R.id.status_username);
|
username = (TextView) itemView.findViewById(R.id.status_username);
|
||||||
|
@ -331,4 +367,37 @@ public class TimelineAdapter extends RecyclerView.Adapter {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static class FooterViewHolder extends RecyclerView.ViewHolder {
|
||||||
|
private LinearLayout retryBar;
|
||||||
|
private Button retry;
|
||||||
|
private ProgressBar progressBar;
|
||||||
|
|
||||||
|
public FooterViewHolder(View itemView) {
|
||||||
|
super(itemView);
|
||||||
|
retryBar = (LinearLayout) itemView.findViewById(R.id.footer_retry_bar);
|
||||||
|
retry = (Button) itemView.findViewById(R.id.footer_retry_button);
|
||||||
|
progressBar = (ProgressBar) itemView.findViewById(R.id.footer_progress_bar);
|
||||||
|
progressBar.setIndeterminate(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setupButton(final FooterActionListener listener) {
|
||||||
|
retry.setOnClickListener(new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
listener.onLoadMore();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void showRetry(boolean show) {
|
||||||
|
if (!show) {
|
||||||
|
retryBar.setVisibility(View.GONE);
|
||||||
|
progressBar.setVisibility(View.VISIBLE);
|
||||||
|
} else {
|
||||||
|
retryBar.setVisibility(View.VISIBLE);
|
||||||
|
progressBar.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ import android.content.SharedPreferences;
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
|
import android.support.design.widget.TabLayout;
|
||||||
import android.support.v4.app.Fragment;
|
import android.support.v4.app.Fragment;
|
||||||
import android.support.v4.app.FragmentManager;
|
import android.support.v4.app.FragmentManager;
|
||||||
import android.support.v4.content.ContextCompat;
|
import android.support.v4.content.ContextCompat;
|
||||||
|
@ -18,7 +19,6 @@ import android.view.LayoutInflater;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.Toast;
|
|
||||||
|
|
||||||
import com.android.volley.AuthFailureError;
|
import com.android.volley.AuthFailureError;
|
||||||
import com.android.volley.Request;
|
import com.android.volley.Request;
|
||||||
|
@ -36,7 +36,7 @@ import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
public class TimelineFragment extends Fragment implements
|
public class TimelineFragment extends Fragment implements
|
||||||
SwipeRefreshLayout.OnRefreshListener, StatusActionListener {
|
SwipeRefreshLayout.OnRefreshListener, StatusActionListener, FooterActionListener {
|
||||||
|
|
||||||
public enum Kind {
|
public enum Kind {
|
||||||
HOME,
|
HOME,
|
||||||
|
@ -48,8 +48,12 @@ public class TimelineFragment extends Fragment implements
|
||||||
private String accessToken = null;
|
private String accessToken = null;
|
||||||
private String userAccountId = null;
|
private String userAccountId = null;
|
||||||
private SwipeRefreshLayout swipeRefreshLayout;
|
private SwipeRefreshLayout swipeRefreshLayout;
|
||||||
|
private RecyclerView recyclerView;
|
||||||
private TimelineAdapter adapter;
|
private TimelineAdapter adapter;
|
||||||
private Kind kind;
|
private Kind kind;
|
||||||
|
private LinearLayoutManager layoutManager;
|
||||||
|
private EndlessOnScrollListener scrollListener;
|
||||||
|
private TabLayout.OnTabSelectedListener onTabSelectedListener;
|
||||||
|
|
||||||
public static TimelineFragment newInstance(Kind kind) {
|
public static TimelineFragment newInstance(Kind kind) {
|
||||||
TimelineFragment fragment = new TimelineFragment();
|
TimelineFragment fragment = new TimelineFragment();
|
||||||
|
@ -79,33 +83,65 @@ public class TimelineFragment extends Fragment implements
|
||||||
swipeRefreshLayout = (SwipeRefreshLayout) rootView.findViewById(R.id.swipe_refresh_layout);
|
swipeRefreshLayout = (SwipeRefreshLayout) rootView.findViewById(R.id.swipe_refresh_layout);
|
||||||
swipeRefreshLayout.setOnRefreshListener(this);
|
swipeRefreshLayout.setOnRefreshListener(this);
|
||||||
// Setup the RecyclerView.
|
// Setup the RecyclerView.
|
||||||
RecyclerView recyclerView = (RecyclerView) rootView.findViewById(R.id.recycler_view);
|
recyclerView = (RecyclerView) rootView.findViewById(R.id.recycler_view);
|
||||||
recyclerView.setHasFixedSize(true);
|
recyclerView.setHasFixedSize(true);
|
||||||
LinearLayoutManager layoutManager = new LinearLayoutManager(context);
|
layoutManager = new LinearLayoutManager(context);
|
||||||
recyclerView.setLayoutManager(layoutManager);
|
recyclerView.setLayoutManager(layoutManager);
|
||||||
DividerItemDecoration divider = new DividerItemDecoration(
|
DividerItemDecoration divider = new DividerItemDecoration(
|
||||||
context, layoutManager.getOrientation());
|
context, layoutManager.getOrientation());
|
||||||
Drawable drawable = ContextCompat.getDrawable(context, R.drawable.status_divider);
|
Drawable drawable = ContextCompat.getDrawable(context, R.drawable.status_divider);
|
||||||
divider.setDrawable(drawable);
|
divider.setDrawable(drawable);
|
||||||
recyclerView.addItemDecoration(divider);
|
recyclerView.addItemDecoration(divider);
|
||||||
EndlessOnScrollListener scrollListener = new EndlessOnScrollListener(layoutManager) {
|
scrollListener = new EndlessOnScrollListener(layoutManager) {
|
||||||
@Override
|
@Override
|
||||||
public void onLoadMore(int page, int totalItemsCount, RecyclerView view) {
|
public void onLoadMore(int page, int totalItemsCount, RecyclerView view) {
|
||||||
TimelineAdapter adapter = (TimelineAdapter) view.getAdapter();
|
TimelineAdapter adapter = (TimelineAdapter) view.getAdapter();
|
||||||
String fromId = adapter.getItem(adapter.getItemCount() - 1).getId();
|
Status status = adapter.getItem(adapter.getItemCount() - 2);
|
||||||
sendFetchTimelineRequest(fromId);
|
if (status != null) {
|
||||||
|
sendFetchTimelineRequest(status.getId());
|
||||||
|
} else {
|
||||||
|
sendFetchTimelineRequest();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
recyclerView.addOnScrollListener(scrollListener);
|
recyclerView.addOnScrollListener(scrollListener);
|
||||||
adapter = new TimelineAdapter(this);
|
adapter = new TimelineAdapter(this, this);
|
||||||
recyclerView.setAdapter(adapter);
|
recyclerView.setAdapter(adapter);
|
||||||
|
|
||||||
|
TabLayout layout = (TabLayout) getActivity().findViewById(R.id.tab_layout);
|
||||||
|
onTabSelectedListener = new TabLayout.OnTabSelectedListener() {
|
||||||
|
@Override
|
||||||
|
public void onTabSelected(TabLayout.Tab tab) {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onTabUnselected(TabLayout.Tab tab) {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onTabReselected(TabLayout.Tab tab) {
|
||||||
|
jumpToTop();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
layout.addOnTabSelectedListener(onTabSelectedListener);
|
||||||
|
|
||||||
sendUserInfoRequest();
|
sendUserInfoRequest();
|
||||||
sendFetchTimelineRequest();
|
sendFetchTimelineRequest();
|
||||||
|
|
||||||
return rootView;
|
return rootView;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDestroyView() {
|
||||||
|
TabLayout tabLayout = (TabLayout) getActivity().findViewById(R.id.tab_layout);
|
||||||
|
tabLayout.removeOnTabSelectedListener(onTabSelectedListener);
|
||||||
|
super.onDestroyView();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void jumpToTop() {
|
||||||
|
layoutManager.scrollToPositionWithOffset(0, 0);
|
||||||
|
scrollListener.reset();
|
||||||
|
}
|
||||||
|
|
||||||
private void sendUserInfoRequest() {
|
private void sendUserInfoRequest() {
|
||||||
sendRequest(Request.Method.GET, getString(R.string.endpoint_verify_credentials), null,
|
sendRequest(Request.Method.GET, getString(R.string.endpoint_verify_credentials), null,
|
||||||
new Response.Listener<JSONObject>() {
|
new Response.Listener<JSONObject>() {
|
||||||
|
@ -182,15 +218,24 @@ public class TimelineFragment extends Fragment implements
|
||||||
} else {
|
} else {
|
||||||
adapter.update(statuses);
|
adapter.update(statuses);
|
||||||
}
|
}
|
||||||
|
showFetchTimelineRetry(false);
|
||||||
swipeRefreshLayout.setRefreshing(false);
|
swipeRefreshLayout.setRefreshing(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onFetchTimelineFailure(Exception exception) {
|
public void onFetchTimelineFailure(Exception exception) {
|
||||||
Toast.makeText(getContext(), R.string.error_fetching_timeline, Toast.LENGTH_SHORT)
|
showFetchTimelineRetry(true);
|
||||||
.show();
|
|
||||||
swipeRefreshLayout.setRefreshing(false);
|
swipeRefreshLayout.setRefreshing(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void showFetchTimelineRetry(boolean show) {
|
||||||
|
RecyclerView.ViewHolder viewHolder =
|
||||||
|
recyclerView.findViewHolderForAdapterPosition(adapter.getItemCount() - 1);
|
||||||
|
if (viewHolder != null) {
|
||||||
|
TimelineAdapter.FooterViewHolder holder = (TimelineAdapter.FooterViewHolder) viewHolder;
|
||||||
|
holder.showRetry(show);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void onRefresh() {
|
public void onRefresh() {
|
||||||
sendFetchTimelineRequest();
|
sendFetchTimelineRequest();
|
||||||
}
|
}
|
||||||
|
@ -335,4 +380,13 @@ public class TimelineFragment extends Fragment implements
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void onLoadMore() {
|
||||||
|
Status status = adapter.getItem(adapter.getItemCount() - 2);
|
||||||
|
if (status != null) {
|
||||||
|
sendFetchTimelineRequest(status.getId());
|
||||||
|
} else {
|
||||||
|
sendFetchTimelineRequest();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -49,6 +49,7 @@
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="@string/title_public" />
|
android:text="@string/title_public" />
|
||||||
|
|
||||||
</android.support.design.widget.TabLayout>
|
</android.support.design.widget.TabLayout>
|
||||||
|
|
||||||
</android.support.v4.view.ViewPager>
|
</android.support.v4.view.ViewPager>
|
||||||
|
|
41
app/src/main/res/layout/item_footer.xml
Normal file
41
app/src/main/res/layout/item_footer.xml
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:layout_gravity="center">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/footer_retry_bar"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:visibility="gone">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/footer_text"
|
||||||
|
android:padding="@dimen/footer_text_padding"/>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:id="@+id/footer_retry_button"
|
||||||
|
android:text="@string/action_retry" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<ProgressBar
|
||||||
|
android:id="@+id/footer_progress_bar"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
|
@ -7,6 +7,7 @@
|
||||||
<dimen name="status_boost_icon_vertical_padding">5dp</dimen>
|
<dimen name="status_boost_icon_vertical_padding">5dp</dimen>
|
||||||
<dimen name="status_media_preview_top_margin">4dp</dimen>
|
<dimen name="status_media_preview_top_margin">4dp</dimen>
|
||||||
<dimen name="status_media_preview_height">96dp</dimen>
|
<dimen name="status_media_preview_height">96dp</dimen>
|
||||||
|
<dimen name="footer_text_padding">8dp</dimen>
|
||||||
<dimen name="compose_media_preview_margin">8dp</dimen>
|
<dimen name="compose_media_preview_margin">8dp</dimen>
|
||||||
<dimen name="compose_media_preview_margin_bottom">16dp</dimen>
|
<dimen name="compose_media_preview_margin_bottom">16dp</dimen>
|
||||||
<dimen name="compose_media_preview_side">48dp</dimen>
|
<dimen name="compose_media_preview_side">48dp</dimen>
|
||||||
|
|
|
@ -58,6 +58,8 @@
|
||||||
<string name="status_sensitive_media_title">Sensitive Media</string>
|
<string name="status_sensitive_media_title">Sensitive Media</string>
|
||||||
<string name="status_sensitive_media_directions">Click to view.</string>
|
<string name="status_sensitive_media_directions">Click to view.</string>
|
||||||
|
|
||||||
|
<string name="footer_text">Could not load the rest of the toots.</string>
|
||||||
|
|
||||||
<string name="notification_reblog_format">%s boosted your status</string>
|
<string name="notification_reblog_format">%s boosted your status</string>
|
||||||
<string name="notification_favourite_format">%s favourited your status</string>
|
<string name="notification_favourite_format">%s favourited your status</string>
|
||||||
<string name="notification_follow_format">%s followed you</string>
|
<string name="notification_follow_format">%s followed you</string>
|
||||||
|
@ -71,6 +73,7 @@
|
||||||
<string name="action_send">TOOT</string>
|
<string name="action_send">TOOT</string>
|
||||||
<string name="action_retry">Retry</string>
|
<string name="action_retry">Retry</string>
|
||||||
<string name="action_mark_sensitive">Mark Sensitive</string>
|
<string name="action_mark_sensitive">Mark Sensitive</string>
|
||||||
|
<string name="action_cancel">Cancel</string>
|
||||||
|
|
||||||
<string name="description_domain">Domain</string>
|
<string name="description_domain">Domain</string>
|
||||||
<string name="description_compose">What\'s Happening?</string>
|
<string name="description_compose">What\'s Happening?</string>
|
||||||
|
|
Loading…
Reference in a new issue