Adds correct footer behaviour to account lists and unifies it with how timelines use them.

This commit is contained in:
Vavassor 2017-06-30 18:30:25 -04:00
parent 3955649b9c
commit 275cd51a6d
15 changed files with 164 additions and 124 deletions

View File

@ -21,7 +21,6 @@ import android.support.v7.widget.RecyclerView;
import com.keylesspalace.tusky.entity.Account; import com.keylesspalace.tusky.entity.Account;
import com.keylesspalace.tusky.interfaces.AccountActionListener; import com.keylesspalace.tusky.interfaces.AccountActionListener;
import java.text.ParseException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
@ -29,6 +28,8 @@ import java.util.List;
public abstract class AccountAdapter extends RecyclerView.Adapter { public abstract class AccountAdapter extends RecyclerView.Adapter {
List<Account> accountList; List<Account> accountList;
AccountActionListener accountActionListener; AccountActionListener accountActionListener;
FooterViewHolder.State footerState;
private String topId; private String topId;
private String bottomId; private String bottomId;
@ -36,6 +37,7 @@ public abstract class AccountAdapter extends RecyclerView.Adapter {
super(); super();
accountList = new ArrayList<>(); accountList = new ArrayList<>();
this.accountActionListener = accountActionListener; this.accountActionListener = accountActionListener;
footerState = FooterViewHolder.State.END;
} }
@Override @Override
@ -119,6 +121,10 @@ public abstract class AccountAdapter extends RecyclerView.Adapter {
return null; return null;
} }
public void setFooterState(FooterViewHolder.State newFooterState) {
footerState = newFooterState;
}
@Nullable @Nullable
public String getBottomId() { public String getBottomId() {
return bottomId; return bottomId;

View File

@ -59,6 +59,9 @@ public class BlocksAdapter extends AccountAdapter {
BlockedUserViewHolder holder = (BlockedUserViewHolder) viewHolder; BlockedUserViewHolder holder = (BlockedUserViewHolder) viewHolder;
holder.setupWithAccount(accountList.get(position)); holder.setupWithAccount(accountList.get(position));
holder.setupActionListener(accountActionListener, true); holder.setupActionListener(accountActionListener, true);
} else {
FooterViewHolder holder = (FooterViewHolder) viewHolder;
holder.setState(footerState);
} }
} }

View File

@ -55,6 +55,9 @@ public class FollowAdapter extends AccountAdapter {
AccountViewHolder holder = (AccountViewHolder) viewHolder; AccountViewHolder holder = (AccountViewHolder) viewHolder;
holder.setupWithAccount(accountList.get(position)); holder.setupWithAccount(accountList.get(position));
holder.setupActionListener(accountActionListener); holder.setupActionListener(accountActionListener);
} else {
FooterViewHolder holder = (FooterViewHolder) viewHolder;
holder.setState(footerState);
} }
} }

View File

@ -59,6 +59,9 @@ public class FollowRequestsAdapter extends AccountAdapter {
FollowRequestViewHolder holder = (FollowRequestViewHolder) viewHolder; FollowRequestViewHolder holder = (FollowRequestViewHolder) viewHolder;
holder.setupWithAccount(accountList.get(position)); holder.setupWithAccount(accountList.get(position));
holder.setupActionListener(accountActionListener); holder.setupActionListener(accountActionListener);
} else {
FooterViewHolder holder = (FooterViewHolder) viewHolder;
holder.setState(footerState);
} }
} }

View File

@ -15,18 +15,59 @@
package com.keylesspalace.tusky.adapter; package com.keylesspalace.tusky.adapter;
import android.graphics.drawable.Drawable;
import android.support.v7.content.res.AppCompatResources;
import android.support.v7.widget.RecyclerView; import android.support.v7.widget.RecyclerView;
import android.view.View; import android.view.View;
import android.widget.ProgressBar; import android.widget.ProgressBar;
import android.widget.TextView;
import com.keylesspalace.tusky.R; import com.keylesspalace.tusky.R;
class FooterViewHolder extends RecyclerView.ViewHolder { public class FooterViewHolder extends RecyclerView.ViewHolder {
public enum State {
EMPTY,
END,
LOADING
}
private View container;
private ProgressBar progressBar;
private TextView endMessage;
FooterViewHolder(View itemView) { FooterViewHolder(View itemView) {
super(itemView); super(itemView);
ProgressBar progressBar = (ProgressBar) itemView.findViewById(R.id.footer_progress_bar); container = itemView.findViewById(R.id.footer_container);
if (progressBar != null) { progressBar = (ProgressBar) itemView.findViewById(R.id.footer_progress_bar);
progressBar.setIndeterminate(true); endMessage = (TextView) itemView.findViewById(R.id.footer_end_message);
Drawable top = AppCompatResources.getDrawable(itemView.getContext(),
R.drawable.elephant_friend);
if (top != null) {
top.setBounds(0, 0, top.getIntrinsicWidth() / 2, top.getIntrinsicHeight() / 2);
}
endMessage.setCompoundDrawables(null, top, null, null);
}
public void setState(State state) {
switch (state) {
case LOADING: {
container.setVisibility(View.VISIBLE);
progressBar.setVisibility(View.VISIBLE);
endMessage.setVisibility(View.GONE);
break;
}
case END: {
container.setVisibility(View.GONE);
progressBar.setVisibility(View.GONE);
endMessage.setVisibility(View.GONE);
break;
}
case EMPTY: {
container.setVisibility(View.VISIBLE);
progressBar.setVisibility(View.GONE);
endMessage.setVisibility(View.VISIBLE);
break;
}
} }
} }
} }

View File

@ -44,6 +44,9 @@ public class MutesAdapter extends AccountAdapter {
MutedUserViewHolder holder = (MutedUserViewHolder) viewHolder; MutedUserViewHolder holder = (MutedUserViewHolder) viewHolder;
holder.setupWithAccount(accountList.get(position)); holder.setupWithAccount(accountList.get(position));
holder.setupActionListener(accountActionListener, true, position); holder.setupActionListener(accountActionListener, true, position);
} else {
FooterViewHolder holder = (FooterViewHolder) viewHolder;
holder.setState(footerState);
} }
} }

View File

@ -46,16 +46,10 @@ public class NotificationsAdapter extends RecyclerView.Adapter implements Adapte
private static final int VIEW_TYPE_STATUS_NOTIFICATION = 2; private static final int VIEW_TYPE_STATUS_NOTIFICATION = 2;
private static final int VIEW_TYPE_FOLLOW = 3; private static final int VIEW_TYPE_FOLLOW = 3;
public enum FooterState {
EMPTY,
END,
LOADING
}
private List<Notification> notifications; private List<Notification> notifications;
private StatusActionListener statusListener; private StatusActionListener statusListener;
private NotificationActionListener notificationActionListener; private NotificationActionListener notificationActionListener;
private FooterState footerState = FooterState.END; private FooterViewHolder.State footerState;
private boolean mediaPreviewEnabled; private boolean mediaPreviewEnabled;
private String bottomId; private String bottomId;
private String topId; private String topId;
@ -66,6 +60,7 @@ public class NotificationsAdapter extends RecyclerView.Adapter implements Adapte
notifications = new ArrayList<>(); notifications = new ArrayList<>();
this.statusListener = statusListener; this.statusListener = statusListener;
this.notificationActionListener = notificationActionListener; this.notificationActionListener = notificationActionListener;
footerState = FooterViewHolder.State.END;
mediaPreviewEnabled = true; mediaPreviewEnabled = true;
} }
@ -79,24 +74,8 @@ public class NotificationsAdapter extends RecyclerView.Adapter implements Adapte
return new StatusViewHolder(view); return new StatusViewHolder(view);
} }
case VIEW_TYPE_FOOTER: { case VIEW_TYPE_FOOTER: {
View view; View view = LayoutInflater.from(parent.getContext())
switch (footerState) { .inflate(R.layout.item_footer, parent, false);
default:
case LOADING:
view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.item_footer, parent, false);
break;
case END: {
view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.item_footer_end, parent, false);
break;
}
case EMPTY: {
view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.item_footer_empty, parent, false);
break;
}
}
return new FooterViewHolder(view); return new FooterViewHolder(view);
} }
case VIEW_TYPE_STATUS_NOTIFICATION: { case VIEW_TYPE_STATUS_NOTIFICATION: {
@ -140,6 +119,9 @@ public class NotificationsAdapter extends RecyclerView.Adapter implements Adapte
break; break;
} }
} }
} else {
FooterViewHolder holder = (FooterViewHolder) viewHolder;
holder.setState(footerState);
} }
} }
@ -252,12 +234,8 @@ public class NotificationsAdapter extends RecyclerView.Adapter implements Adapte
notifyDataSetChanged(); notifyDataSetChanged();
} }
public void setFooterState(FooterState newFooterState) { public void setFooterState(FooterViewHolder.State newFooterState) {
FooterState oldValue = footerState;
footerState = newFooterState; footerState = newFooterState;
if (footerState != oldValue) {
notifyItemChanged(notifications.size());
}
} }
@Nullable @Nullable

View File

@ -34,15 +34,9 @@ public class TimelineAdapter extends RecyclerView.Adapter implements AdapterItem
private static final int VIEW_TYPE_STATUS = 0; private static final int VIEW_TYPE_STATUS = 0;
private static final int VIEW_TYPE_FOOTER = 1; private static final int VIEW_TYPE_FOOTER = 1;
public enum FooterState {
EMPTY,
END,
LOADING
}
private List<Status> statuses; private List<Status> statuses;
private StatusActionListener statusListener; private StatusActionListener statusListener;
private FooterState footerState = FooterState.END; private FooterViewHolder.State footerState;
private boolean mediaPreviewEnabled; private boolean mediaPreviewEnabled;
private String topId; private String topId;
private String bottomId; private String bottomId;
@ -51,6 +45,7 @@ public class TimelineAdapter extends RecyclerView.Adapter implements AdapterItem
super(); super();
statuses = new ArrayList<>(); statuses = new ArrayList<>();
this.statusListener = statusListener; this.statusListener = statusListener;
footerState = FooterViewHolder.State.END;
mediaPreviewEnabled = true; mediaPreviewEnabled = true;
} }
@ -64,24 +59,8 @@ public class TimelineAdapter extends RecyclerView.Adapter implements AdapterItem
return new StatusViewHolder(view); return new StatusViewHolder(view);
} }
case VIEW_TYPE_FOOTER: { case VIEW_TYPE_FOOTER: {
View view; View view = LayoutInflater.from(viewGroup.getContext())
switch (footerState) { .inflate(R.layout.item_footer, viewGroup, false);
default:
case LOADING:
view = LayoutInflater.from(viewGroup.getContext())
.inflate(R.layout.item_footer, viewGroup, false);
break;
case END: {
view = LayoutInflater.from(viewGroup.getContext())
.inflate(R.layout.item_footer_end, viewGroup, false);
break;
}
case EMPTY: {
view = LayoutInflater.from(viewGroup.getContext())
.inflate(R.layout.item_footer_empty, viewGroup, false);
break;
}
}
return new FooterViewHolder(view); return new FooterViewHolder(view);
} }
} }
@ -93,6 +72,9 @@ public class TimelineAdapter extends RecyclerView.Adapter implements AdapterItem
StatusViewHolder holder = (StatusViewHolder) viewHolder; StatusViewHolder holder = (StatusViewHolder) viewHolder;
Status status = statuses.get(position); Status status = statuses.get(position);
holder.setupWithStatus(status, statusListener, mediaPreviewEnabled); holder.setupWithStatus(status, statusListener, mediaPreviewEnabled);
} else {
FooterViewHolder holder = (FooterViewHolder) viewHolder;
holder.setState(footerState);
} }
} }
@ -192,12 +174,8 @@ public class TimelineAdapter extends RecyclerView.Adapter implements AdapterItem
return null; return null;
} }
public void setFooterState(FooterState newFooterState) { public void setFooterState(FooterViewHolder.State newFooterState) {
FooterState oldValue = footerState;
footerState = newFooterState; footerState = newFooterState;
if (footerState != oldValue) {
notifyItemChanged(statuses.size());
}
} }
public void setMediaPreviewEnabled(boolean enabled) { public void setMediaPreviewEnabled(boolean enabled) {

View File

@ -36,6 +36,7 @@ import com.keylesspalace.tusky.adapter.AccountAdapter;
import com.keylesspalace.tusky.adapter.BlocksAdapter; import com.keylesspalace.tusky.adapter.BlocksAdapter;
import com.keylesspalace.tusky.adapter.FollowAdapter; import com.keylesspalace.tusky.adapter.FollowAdapter;
import com.keylesspalace.tusky.adapter.FollowRequestsAdapter; import com.keylesspalace.tusky.adapter.FollowRequestsAdapter;
import com.keylesspalace.tusky.adapter.FooterViewHolder;
import com.keylesspalace.tusky.adapter.MutesAdapter; import com.keylesspalace.tusky.adapter.MutesAdapter;
import com.keylesspalace.tusky.BaseActivity; import com.keylesspalace.tusky.BaseActivity;
import com.keylesspalace.tusky.entity.Account; import com.keylesspalace.tusky.entity.Account;
@ -358,7 +359,12 @@ public class AccountListFragment extends BaseFragment implements AccountActionLi
} }
private void onRespondToFollowRequestFailure(boolean accept, String accountId) { private void onRespondToFollowRequestFailure(boolean accept, String accountId) {
String verb = (accept) ? "accept" : "reject"; String verb;
if (accept) {
verb = "accept";
} else {
verb = "reject";
}
String message = String.format("Failed to %s account id %s.", verb, accountId); String message = String.format("Failed to %s account id %s.", verb, accountId);
Log.e(TAG, message); Log.e(TAG, message);
} }
@ -399,6 +405,11 @@ public class AccountListFragment extends BaseFragment implements AccountActionLi
bottomFetches++; bottomFetches++;
return; return;
} }
if (fromId != null || adapter.getItemCount() <= 1) {
setFooterState(FooterViewHolder.State.LOADING);
}
Callback<List<Account>> cb = new Callback<List<Account>>() { Callback<List<Account>> cb = new Callback<List<Account>>() {
@Override @Override
public void onResponse(Call<List<Account>> call, Response<List<Account>> response) { public void onResponse(Call<List<Account>> call, Response<List<Account>> response) {
@ -456,6 +467,11 @@ public class AccountListFragment extends BaseFragment implements AccountActionLi
} }
} }
fulfillAnyQueuedFetches(fetchEnd); fulfillAnyQueuedFetches(fetchEnd);
if (accounts.size() == 0 && adapter.getItemCount() == 1) {
setFooterState(FooterViewHolder.State.EMPTY);
} else {
setFooterState(FooterViewHolder.State.END);
}
} }
private void onFetchAccountsFailure(Exception exception, FetchEnd fetchEnd) { private void onFetchAccountsFailure(Exception exception, FetchEnd fetchEnd) {
@ -463,6 +479,20 @@ public class AccountListFragment extends BaseFragment implements AccountActionLi
fulfillAnyQueuedFetches(fetchEnd); fulfillAnyQueuedFetches(fetchEnd);
} }
/* This needs to be called from the endless scroll listener, which does not allow notifying the
* adapter during the callback. So, this is the workaround. */
private void setFooterState(FooterViewHolder.State state) {
// Set the adapter to set its state when it's bound, if the current Footer is offscreen.
adapter.setFooterState(state);
// Check if it's onscreen, and update it directly if it is.
RecyclerView.ViewHolder viewHolder =
recyclerView.findViewHolderForAdapterPosition(adapter.getItemCount() - 1);
if (viewHolder != null) {
FooterViewHolder holder = (FooterViewHolder) viewHolder;
holder.setState(state);
}
}
private void onRefresh() { private void onRefresh() {
fetchAccounts(null, adapter.getTopId(), FetchEnd.TOP); fetchAccounts(null, adapter.getTopId(), FetchEnd.TOP);
} }
@ -478,7 +508,6 @@ public class AccountListFragment extends BaseFragment implements AccountActionLi
bottomLoading = false; bottomLoading = false;
if (bottomFetches > 0) { if (bottomFetches > 0) {
bottomFetches--; bottomFetches--;
Log.d(TAG, "extra fetchos " + bottomFetches);
onLoadMore(recyclerView); onLoadMore(recyclerView);
} }
break; break;

View File

@ -34,6 +34,7 @@ import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import com.keylesspalace.tusky.MainActivity; import com.keylesspalace.tusky.MainActivity;
import com.keylesspalace.tusky.adapter.FooterViewHolder;
import com.keylesspalace.tusky.adapter.NotificationsAdapter; import com.keylesspalace.tusky.adapter.NotificationsAdapter;
import com.keylesspalace.tusky.R; import com.keylesspalace.tusky.R;
import com.keylesspalace.tusky.entity.Notification; import com.keylesspalace.tusky.entity.Notification;
@ -274,7 +275,7 @@ public class NotificationsFragment extends SFragment implements
} }
if (fromId != null || adapter.getItemCount() <= 1) { if (fromId != null || adapter.getItemCount() <= 1) {
adapter.setFooterState(NotificationsAdapter.FooterState.LOADING); setFooterState(FooterViewHolder.State.LOADING);
} }
Call<List<Notification>> call = mastodonApi.notifications(fromId, uptoId, null); Call<List<Notification>> call = mastodonApi.notifications(fromId, uptoId, null);
@ -341,9 +342,9 @@ public class NotificationsFragment extends SFragment implements
} }
fulfillAnyQueuedFetches(fetchEnd); fulfillAnyQueuedFetches(fetchEnd);
if (notifications.size() == 0 && adapter.getItemCount() == 1) { if (notifications.size() == 0 && adapter.getItemCount() == 1) {
adapter.setFooterState(NotificationsAdapter.FooterState.EMPTY); setFooterState(FooterViewHolder.State.EMPTY);
} else { } else {
adapter.setFooterState(NotificationsAdapter.FooterState.END); setFooterState(FooterViewHolder.State.END);
} }
swipeRefreshLayout.setRefreshing(false); swipeRefreshLayout.setRefreshing(false);
} }
@ -354,6 +355,20 @@ public class NotificationsFragment extends SFragment implements
fulfillAnyQueuedFetches(fetchEnd); fulfillAnyQueuedFetches(fetchEnd);
} }
/* This needs to be called from the endless scroll listener, which does not allow notifying the
* adapter during the callback. So, this is the workaround. */
private void setFooterState(FooterViewHolder.State state) {
// Set the adapter to set its state when it's bound, if the current Footer is offscreen.
adapter.setFooterState(state);
// Check if it's onscreen, and update it directly if it is.
RecyclerView.ViewHolder viewHolder =
recyclerView.findViewHolderForAdapterPosition(adapter.getItemCount() - 1);
if (viewHolder != null) {
FooterViewHolder holder = (FooterViewHolder) viewHolder;
holder.setState(state);
}
}
private void fulfillAnyQueuedFetches(FetchEnd fetchEnd) { private void fulfillAnyQueuedFetches(FetchEnd fetchEnd) {
switch (fetchEnd) { switch (fetchEnd) {
case BOTTOM: { case BOTTOM: {

View File

@ -35,6 +35,7 @@ import android.view.ViewGroup;
import com.keylesspalace.tusky.MainActivity; import com.keylesspalace.tusky.MainActivity;
import com.keylesspalace.tusky.R; import com.keylesspalace.tusky.R;
import com.keylesspalace.tusky.adapter.FooterViewHolder;
import com.keylesspalace.tusky.adapter.TimelineAdapter; import com.keylesspalace.tusky.adapter.TimelineAdapter;
import com.keylesspalace.tusky.entity.Status; import com.keylesspalace.tusky.entity.Status;
import com.keylesspalace.tusky.interfaces.StatusActionListener; import com.keylesspalace.tusky.interfaces.StatusActionListener;
@ -359,7 +360,7 @@ public class TimelineFragment extends SFragment implements
} }
if (fromId != null || adapter.getItemCount() <= 1) { if (fromId != null || adapter.getItemCount() <= 1) {
adapter.setFooterState(TimelineAdapter.FooterState.LOADING); setFooterState(FooterViewHolder.State.LOADING);
} }
Callback<List<Status>> callback = new Callback<List<Status>>() { Callback<List<Status>> callback = new Callback<List<Status>>() {
@ -422,9 +423,9 @@ public class TimelineFragment extends SFragment implements
} }
fulfillAnyQueuedFetches(fetchEnd); fulfillAnyQueuedFetches(fetchEnd);
if (statuses.size() == 0 && adapter.getItemCount() == 1) { if (statuses.size() == 0 && adapter.getItemCount() == 1) {
adapter.setFooterState(TimelineAdapter.FooterState.EMPTY); setFooterState(FooterViewHolder.State.EMPTY);
} else { } else {
adapter.setFooterState(TimelineAdapter.FooterState.END); setFooterState(FooterViewHolder.State.END);
} }
swipeRefreshLayout.setRefreshing(false); swipeRefreshLayout.setRefreshing(false);
} }
@ -435,6 +436,20 @@ public class TimelineFragment extends SFragment implements
fulfillAnyQueuedFetches(fetchEnd); fulfillAnyQueuedFetches(fetchEnd);
} }
/* This needs to be called from the endless scroll listener, which does not allow notifying the
* adapter during the callback. So, this is the workaround. */
private void setFooterState(FooterViewHolder.State state) {
// Set the adapter to set its state when it's bound, if the current Footer is offscreen.
adapter.setFooterState(state);
// Check if it's onscreen, and update it directly if it is.
RecyclerView.ViewHolder viewHolder =
recyclerView.findViewHolderForAdapterPosition(adapter.getItemCount() - 1);
if (viewHolder != null) {
FooterViewHolder holder = (FooterViewHolder) viewHolder;
holder.setState(state);
}
}
private void fulfillAnyQueuedFetches(FetchEnd fetchEnd) { private void fulfillAnyQueuedFetches(FetchEnd fetchEnd) {
switch (fetchEnd) { switch (fetchEnd) {
case BOTTOM: { case BOTTOM: {

View File

@ -1,20 +1,23 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<LinearLayout <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:gravity="center" android:id="@+id/footer_container">
android:orientation="vertical">
<LinearLayout <ProgressBar
android:id="@+id/footer_progress_bar"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="horizontal" android:layout_centerInParent="true"
android:layout_gravity="center"> android:indeterminate="true" />
<ProgressBar
android:id="@+id/footer_progress_bar"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
</LinearLayout> <TextView
android:id="@+id/footer_end_message"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/footer_empty"
android:textAlignment="center"
android:layout_centerInParent="true"
android:drawablePadding="16dp" />
</RelativeLayout>

View File

@ -1,25 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="16dp"
android:gravity="center"
android:orientation="vertical">
<ImageView
android:id="@+id/imageView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:srcCompat="@drawable/elephant_friend" />
<TextView
android:id="@+id/textView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="@string/footer_empty"
android:textAlignment="center" />
</LinearLayout>

View File

@ -1,9 +0,0 @@
<?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="match_parent"
android:gravity="center"
android:orientation="vertical">
</LinearLayout>

View File

@ -40,10 +40,7 @@
<string name="status_content_warning_show_more">Show More</string> <string name="status_content_warning_show_more">Show More</string>
<string name="status_content_warning_show_less">Show Less</string> <string name="status_content_warning_show_less">Show Less</string>
<string name="footer_end_of_statuses">end of the statuses</string> <string name="footer_empty">Nothing here. Pull down to refresh!</string>
<string name="footer_end_of_notifications">end of the notifications</string>
<string name="footer_end_of_accounts">end of the accounts</string>
<string name="footer_empty">There are no toots here so far. Pull down to refresh!</string>
<string name="notification_reblog_format">%s boosted your toot</string> <string name="notification_reblog_format">%s boosted your toot</string>
<string name="notification_favourite_format">%s favourited your toot</string> <string name="notification_favourite_format">%s favourited your toot</string>