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.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.pm.PackageManager;
|
||||
|
@ -55,6 +56,7 @@ import java.io.InputStream;
|
|||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Random;
|
||||
|
@ -196,22 +198,12 @@ public class ComposeActivity extends AppCompatActivity {
|
|||
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,
|
||||
final boolean sensitive) {
|
||||
final ProgressDialog dialog = ProgressDialog.show(this, "Finishing Media Upload",
|
||||
"Uploading...", true);
|
||||
new AsyncTask<Void, Void, Boolean>() {
|
||||
"Uploading...", true, true);
|
||||
final AsyncTask<Void, Void, Boolean> waitForMediaTask =
|
||||
new AsyncTask<Void, Void, Boolean>() {
|
||||
private Exception exception;
|
||||
|
||||
@Override
|
||||
|
@ -235,7 +227,34 @@ public class ComposeActivity extends AppCompatActivity {
|
|||
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
|
||||
|
@ -439,16 +458,26 @@ public class ComposeActivity extends AppCompatActivity {
|
|||
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) {
|
||||
item.setReadyStage(QueuedMedia.ReadyStage.DOWNSIZING);
|
||||
InputStream stream;
|
||||
InputStream stream = null;
|
||||
try {
|
||||
stream = getContentResolver().openInputStream(item.getUri());
|
||||
} catch (FileNotFoundException e) {
|
||||
IOUtils.closeQuietly(stream);
|
||||
onMediaDownsizeFailure(item);
|
||||
return;
|
||||
}
|
||||
Bitmap bitmap = BitmapFactory.decodeStream(stream);
|
||||
IOUtils.closeQuietly(stream);
|
||||
new DownsizeImageTask(STATUS_MEDIA_SIZE_LIMIT, new DownsizeImageTask.Listener() {
|
||||
@Override
|
||||
public void onSuccess(List<byte[]> contentList) {
|
||||
|
@ -538,13 +567,15 @@ public class ComposeActivity extends AppCompatActivity {
|
|||
public DataItem getData() {
|
||||
byte[] content = item.getContent();
|
||||
if (content == null) {
|
||||
InputStream stream;
|
||||
InputStream stream = null;
|
||||
try {
|
||||
stream = getContentResolver().openInputStream(item.getUri());
|
||||
} catch (FileNotFoundException e) {
|
||||
IOUtils.closeQuietly(stream);
|
||||
return null;
|
||||
}
|
||||
content = inputStreamGetBytes(stream);
|
||||
IOUtils.closeQuietly(stream);
|
||||
if (content == null) {
|
||||
return null;
|
||||
}
|
||||
|
@ -607,10 +638,11 @@ public class ComposeActivity extends AppCompatActivity {
|
|||
break;
|
||||
}
|
||||
case "image": {
|
||||
InputStream stream;
|
||||
InputStream stream = null;
|
||||
try {
|
||||
stream = contentResolver.openInputStream(uri);
|
||||
} catch (FileNotFoundException e) {
|
||||
IOUtils.closeQuietly(stream);
|
||||
displayTransientError(R.string.error_media_upload_opening);
|
||||
return;
|
||||
}
|
||||
|
@ -618,7 +650,9 @@ public class ComposeActivity extends AppCompatActivity {
|
|||
Bitmap bitmap = ThumbnailUtils.extractThumbnail(source, 96, 96);
|
||||
source.recycle();
|
||||
try {
|
||||
stream.close();
|
||||
if (stream != null) {
|
||||
stream.close();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
bitmap.recycle();
|
||||
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.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Button;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.android.volley.toolbox.ImageLoader;
|
||||
|
@ -21,71 +24,101 @@ import java.util.Date;
|
|||
import java.util.List;
|
||||
|
||||
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();
|
||||
this.listener = listener;
|
||||
statuses = new ArrayList<>();
|
||||
this.statusListener = statusListener;
|
||||
this.footerListener = footerListener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) {
|
||||
View v = LayoutInflater.from(viewGroup.getContext())
|
||||
.inflate(R.layout.item_status, viewGroup, false);
|
||||
return new ViewHolder(v);
|
||||
switch (viewType) {
|
||||
default:
|
||||
case VIEW_TYPE_STATUS: {
|
||||
View view = LayoutInflater.from(viewGroup.getContext())
|
||||
.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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int position) {
|
||||
ViewHolder holder = (ViewHolder) viewHolder;
|
||||
Status status = statuses.get(position);
|
||||
holder.setDisplayName(status.getDisplayName());
|
||||
holder.setUsername(status.getUsername());
|
||||
holder.setCreatedAt(status.getCreatedAt());
|
||||
holder.setContent(status.getContent());
|
||||
holder.setAvatar(status.getAvatar());
|
||||
holder.setContent(status.getContent());
|
||||
holder.setReblogged(status.getReblogged());
|
||||
holder.setFavourited(status.getFavourited());
|
||||
String rebloggedByUsername = status.getRebloggedByUsername();
|
||||
if (rebloggedByUsername == null) {
|
||||
holder.hideRebloggedByUsername();
|
||||
if (position < statuses.size()) {
|
||||
StatusViewHolder holder = (StatusViewHolder) viewHolder;
|
||||
Status status = statuses.get(position);
|
||||
holder.setDisplayName(status.getDisplayName());
|
||||
holder.setUsername(status.getUsername());
|
||||
holder.setCreatedAt(status.getCreatedAt());
|
||||
holder.setContent(status.getContent());
|
||||
holder.setAvatar(status.getAvatar());
|
||||
holder.setContent(status.getContent());
|
||||
holder.setReblogged(status.getReblogged());
|
||||
holder.setFavourited(status.getFavourited());
|
||||
String rebloggedByUsername = status.getRebloggedByUsername();
|
||||
if (rebloggedByUsername == null) {
|
||||
holder.hideRebloggedByUsername();
|
||||
} else {
|
||||
holder.setRebloggedByUsername(rebloggedByUsername);
|
||||
}
|
||||
Status.MediaAttachment[] attachments = status.getAttachments();
|
||||
boolean sensitive = status.getSensitive();
|
||||
holder.setMediaPreviews(attachments, sensitive, statusListener);
|
||||
/* 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. */
|
||||
if (!sensitive || attachments.length == 0) {
|
||||
holder.hideSensitiveMediaWarning();
|
||||
}
|
||||
holder.setupButtons(statusListener, position);
|
||||
if (status.getVisibility() == Status.Visibility.PRIVATE) {
|
||||
holder.disableReblogging();
|
||||
}
|
||||
} else {
|
||||
holder.setRebloggedByUsername(rebloggedByUsername);
|
||||
}
|
||||
Status.MediaAttachment[] attachments = status.getAttachments();
|
||||
boolean sensitive = status.getSensitive();
|
||||
holder.setMediaPreviews(attachments, sensitive, listener);
|
||||
/* 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. */
|
||||
if (!sensitive || attachments.length == 0) {
|
||||
holder.hideSensitiveMediaWarning();
|
||||
}
|
||||
holder.setupButtons(listener, position);
|
||||
if (status.getVisibility() == Status.Visibility.PRIVATE) {
|
||||
holder.disableReblogging();
|
||||
FooterViewHolder holder = (FooterViewHolder) viewHolder;
|
||||
holder.setupButton(footerListener);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
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;
|
||||
if (statuses == null || statuses.isEmpty()) {
|
||||
statuses = new_statuses;
|
||||
statuses = newStatuses;
|
||||
scrollToPosition = 0;
|
||||
} else {
|
||||
int index = new_statuses.indexOf(statuses.get(0));
|
||||
int index = newStatuses.indexOf(statuses.get(0));
|
||||
if (index == -1) {
|
||||
statuses.addAll(0, new_statuses);
|
||||
statuses.addAll(0, newStatuses);
|
||||
scrollToPosition = 0;
|
||||
} else {
|
||||
statuses.addAll(0, new_statuses.subList(0, index));
|
||||
statuses.addAll(0, newStatuses.subList(0, index));
|
||||
scrollToPosition = index;
|
||||
}
|
||||
}
|
||||
|
@ -93,10 +126,10 @@ public class TimelineAdapter extends RecyclerView.Adapter {
|
|||
return scrollToPosition;
|
||||
}
|
||||
|
||||
public void addItems(List<Status> new_statuses) {
|
||||
public void addItems(List<Status> newStatuses) {
|
||||
int end = statuses.size();
|
||||
statuses.addAll(new_statuses);
|
||||
notifyItemRangeInserted(end, new_statuses.size());
|
||||
statuses.addAll(newStatuses);
|
||||
notifyItemRangeInserted(end, newStatuses.size());
|
||||
}
|
||||
|
||||
public void removeItem(int position) {
|
||||
|
@ -104,11 +137,14 @@ public class TimelineAdapter extends RecyclerView.Adapter {
|
|||
notifyItemRemoved(position);
|
||||
}
|
||||
|
||||
public Status getItem(int position) {
|
||||
return statuses.get(position);
|
||||
public @Nullable Status getItem(int position) {
|
||||
if (position >= 0 && position < statuses.size()) {
|
||||
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 username;
|
||||
private TextView sinceCreated;
|
||||
|
@ -128,7 +164,7 @@ public class TimelineAdapter extends RecyclerView.Adapter {
|
|||
private NetworkImageView mediaPreview3;
|
||||
private View sensitiveMediaWarning;
|
||||
|
||||
public ViewHolder(View itemView) {
|
||||
public StatusViewHolder(View itemView) {
|
||||
super(itemView);
|
||||
displayName = (TextView) itemView.findViewById(R.id.status_display_name);
|
||||
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.os.Bundle;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.design.widget.TabLayout;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v4.app.FragmentManager;
|
||||
import android.support.v4.content.ContextCompat;
|
||||
|
@ -18,7 +19,6 @@ import android.view.LayoutInflater;
|
|||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.android.volley.AuthFailureError;
|
||||
import com.android.volley.Request;
|
||||
|
@ -36,7 +36,7 @@ import java.util.List;
|
|||
import java.util.Map;
|
||||
|
||||
public class TimelineFragment extends Fragment implements
|
||||
SwipeRefreshLayout.OnRefreshListener, StatusActionListener {
|
||||
SwipeRefreshLayout.OnRefreshListener, StatusActionListener, FooterActionListener {
|
||||
|
||||
public enum Kind {
|
||||
HOME,
|
||||
|
@ -48,8 +48,12 @@ public class TimelineFragment extends Fragment implements
|
|||
private String accessToken = null;
|
||||
private String userAccountId = null;
|
||||
private SwipeRefreshLayout swipeRefreshLayout;
|
||||
private RecyclerView recyclerView;
|
||||
private TimelineAdapter adapter;
|
||||
private Kind kind;
|
||||
private LinearLayoutManager layoutManager;
|
||||
private EndlessOnScrollListener scrollListener;
|
||||
private TabLayout.OnTabSelectedListener onTabSelectedListener;
|
||||
|
||||
public static TimelineFragment newInstance(Kind kind) {
|
||||
TimelineFragment fragment = new TimelineFragment();
|
||||
|
@ -79,33 +83,65 @@ public class TimelineFragment extends Fragment implements
|
|||
swipeRefreshLayout = (SwipeRefreshLayout) rootView.findViewById(R.id.swipe_refresh_layout);
|
||||
swipeRefreshLayout.setOnRefreshListener(this);
|
||||
// Setup the RecyclerView.
|
||||
RecyclerView recyclerView = (RecyclerView) rootView.findViewById(R.id.recycler_view);
|
||||
recyclerView = (RecyclerView) rootView.findViewById(R.id.recycler_view);
|
||||
recyclerView.setHasFixedSize(true);
|
||||
LinearLayoutManager layoutManager = new LinearLayoutManager(context);
|
||||
layoutManager = new LinearLayoutManager(context);
|
||||
recyclerView.setLayoutManager(layoutManager);
|
||||
DividerItemDecoration divider = new DividerItemDecoration(
|
||||
context, layoutManager.getOrientation());
|
||||
Drawable drawable = ContextCompat.getDrawable(context, R.drawable.status_divider);
|
||||
divider.setDrawable(drawable);
|
||||
recyclerView.addItemDecoration(divider);
|
||||
EndlessOnScrollListener scrollListener = new EndlessOnScrollListener(layoutManager) {
|
||||
scrollListener = new EndlessOnScrollListener(layoutManager) {
|
||||
@Override
|
||||
public void onLoadMore(int page, int totalItemsCount, RecyclerView view) {
|
||||
TimelineAdapter adapter = (TimelineAdapter) view.getAdapter();
|
||||
String fromId = adapter.getItem(adapter.getItemCount() - 1).getId();
|
||||
sendFetchTimelineRequest(fromId);
|
||||
Status status = adapter.getItem(adapter.getItemCount() - 2);
|
||||
if (status != null) {
|
||||
sendFetchTimelineRequest(status.getId());
|
||||
} else {
|
||||
sendFetchTimelineRequest();
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
recyclerView.addOnScrollListener(scrollListener);
|
||||
adapter = new TimelineAdapter(this);
|
||||
adapter = new TimelineAdapter(this, this);
|
||||
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();
|
||||
sendFetchTimelineRequest();
|
||||
|
||||
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() {
|
||||
sendRequest(Request.Method.GET, getString(R.string.endpoint_verify_credentials), null,
|
||||
new Response.Listener<JSONObject>() {
|
||||
|
@ -182,15 +218,24 @@ public class TimelineFragment extends Fragment implements
|
|||
} else {
|
||||
adapter.update(statuses);
|
||||
}
|
||||
showFetchTimelineRetry(false);
|
||||
swipeRefreshLayout.setRefreshing(false);
|
||||
}
|
||||
|
||||
public void onFetchTimelineFailure(Exception exception) {
|
||||
Toast.makeText(getContext(), R.string.error_fetching_timeline, Toast.LENGTH_SHORT)
|
||||
.show();
|
||||
showFetchTimelineRetry(true);
|
||||
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() {
|
||||
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_height="wrap_content"
|
||||
android:text="@string/title_public" />
|
||||
|
||||
</android.support.design.widget.TabLayout>
|
||||
|
||||
</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_media_preview_top_margin">4dp</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_bottom">16dp</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_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_favourite_format">%s favourited your status</string>
|
||||
<string name="notification_follow_format">%s followed you</string>
|
||||
|
@ -71,6 +73,7 @@
|
|||
<string name="action_send">TOOT</string>
|
||||
<string name="action_retry">Retry</string>
|
||||
<string name="action_mark_sensitive">Mark Sensitive</string>
|
||||
<string name="action_cancel">Cancel</string>
|
||||
|
||||
<string name="description_domain">Domain</string>
|
||||
<string name="description_compose">What\'s Happening?</string>
|
||||
|
|
Loading…
Reference in a new issue