implemented notification fragment, restructured adapter and holder, added notification to user and status item layout

This commit is contained in:
nuclearfog 2022-12-01 23:03:10 +01:00
parent 2da277da8a
commit 0aa0a51912
No known key found for this signature in database
GPG Key ID: 03488A185C476379
25 changed files with 1283 additions and 603 deletions

View File

@ -5,7 +5,6 @@ import static org.nuclearfog.twidda.ui.fragments.StatusFragment.KEY_STATUS_FRAGM
import static org.nuclearfog.twidda.ui.fragments.StatusFragment.KEY_STATUS_FRAGMENT_SEARCH;
import static org.nuclearfog.twidda.ui.fragments.StatusFragment.STATUS_FRAGMENT_FAVORIT;
import static org.nuclearfog.twidda.ui.fragments.StatusFragment.STATUS_FRAGMENT_HOME;
import static org.nuclearfog.twidda.ui.fragments.StatusFragment.STATUS_FRAGMENT_MENTION;
import static org.nuclearfog.twidda.ui.fragments.StatusFragment.STATUS_FRAGMENT_SEARCH;
import static org.nuclearfog.twidda.ui.fragments.StatusFragment.STATUS_FRAGMENT_USER;
import static org.nuclearfog.twidda.ui.fragments.StatusFragment.STATUS_FRAGMENT_USERLIST;
@ -38,6 +37,7 @@ import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentStatePagerAdapter;
import org.nuclearfog.twidda.ui.fragments.ListFragment;
import org.nuclearfog.twidda.ui.fragments.NotificationFragment;
import org.nuclearfog.twidda.ui.fragments.StatusFragment;
import org.nuclearfog.twidda.ui.fragments.TrendFragment;
import org.nuclearfog.twidda.ui.fragments.UserFragment;
@ -95,15 +95,12 @@ public class FragmentAdapter extends FragmentStatePagerAdapter {
*/
public void setupForHomePage() {
Bundle paramHomeTl = new Bundle();
Bundle paramMention = new Bundle();
paramHomeTl.putInt(KEY_STATUS_FRAGMENT_MODE, STATUS_FRAGMENT_HOME);
paramMention.putInt(KEY_STATUS_FRAGMENT_MODE, STATUS_FRAGMENT_MENTION);
fragments = new ListFragment[3];
fragments[0] = new StatusFragment();
fragments[1] = new TrendFragment();
fragments[2] = new StatusFragment();
fragments[2] = new NotificationFragment();
fragments[0].setArguments(paramHomeTl);
fragments[2].setArguments(paramMention);
notifyDataSetChanged();
}

View File

@ -0,0 +1,286 @@
package org.nuclearfog.twidda.adapter;
import static androidx.recyclerview.widget.RecyclerView.NO_POSITION;
import android.content.Context;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView.Adapter;
import androidx.recyclerview.widget.RecyclerView.ViewHolder;
import com.squareup.picasso.Picasso;
import org.nuclearfog.twidda.adapter.holder.PlaceHolder;
import org.nuclearfog.twidda.adapter.holder.PlaceHolder.OnHolderClickListener;
import org.nuclearfog.twidda.adapter.holder.StatusHolder;
import org.nuclearfog.twidda.adapter.holder.StatusHolder.OnStatusClickListener;
import org.nuclearfog.twidda.adapter.holder.UserHolder;
import org.nuclearfog.twidda.adapter.holder.UserHolder.OnUserClickListener;
import org.nuclearfog.twidda.backend.utils.PicassoBuilder;
import org.nuclearfog.twidda.database.GlobalSettings;
import org.nuclearfog.twidda.model.Notification;
import org.nuclearfog.twidda.model.Status;
import org.nuclearfog.twidda.model.User;
import java.util.LinkedList;
import java.util.List;
/**
* Rycyclerview adapter for notifications
*
* @author nuclearfog
*/
public class NotificationAdapter extends Adapter<ViewHolder> implements OnStatusClickListener, OnUserClickListener, OnHolderClickListener {
/**
* Minimum count of new statuses to insert a placeholder.
*/
private static final int MIN_COUNT = 2;
private static final int NO_LOADING = -1;
/**
* notification placeholder
*/
private static final int TYPE_PLACEHOLER = 0;
/**
* notifcation type for statuses
*/
private static final int TYPE_STATUS = 1;
/**
* notification type for users
*/
private static final int TYPE_USER = 2;
private Picasso picasso;
private GlobalSettings settings;
private OnNotificationClickListener listener;
private List<Notification> items = new LinkedList<>();
private int loadingIndex = NO_LOADING;
public NotificationAdapter(Context context, OnNotificationClickListener listener) {
settings = GlobalSettings.getInstance(context);
picasso = PicassoBuilder.get(context);
this.listener = listener;
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
if (viewType == TYPE_STATUS) {
StatusHolder holder = new StatusHolder(parent, settings, picasso);
holder.setOnStatusClickListener(this);
return holder;
} else if (viewType == TYPE_USER) {
final UserHolder holder = new UserHolder(parent, settings, picasso);
holder.setOnUserClickListener(this);
return holder;
} else {
final PlaceHolder placeHolder = new PlaceHolder(parent, settings, false);
placeHolder.loadBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
int position = placeHolder.getLayoutPosition();
if (position != NO_POSITION) {
long sinceId = 0;
long maxId = 0;
if (position == 0) {
Notification item = items.get(position + 1);
if (item != null) {
sinceId = item.getId();
}
} else if (position == items.size() - 1) {
Notification item = items.get(position - 1);
if (item != null) {
maxId = item.getId() - 1;
}
} else {
Notification item = items.get(position + 1);
if (item != null) {
sinceId = item.getId();
}
item = items.get(position - 1);
if (item != null) {
maxId = item.getId() - 1;
}
}
boolean success = listener.onPlaceholderClick(sinceId, maxId, position);
if (success) {
placeHolder.setLoading(true);
loadingIndex = position;
}
}
}
});
return placeHolder;
}
}
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
Notification item = items.get(position);
if (item != null) {
if (holder instanceof StatusHolder && item.getStatus() != null) {
StatusHolder statusHolder = (StatusHolder) holder;
statusHolder.setContent(item.getStatus());
statusHolder.setLabel(item);
} else if (holder instanceof UserHolder && item.getUser() != null) {
UserHolder userHolder = (UserHolder) holder;
userHolder.setContent(item.getUser());
userHolder.setLabel(item);
}
} else if (holder instanceof PlaceHolder) {
PlaceHolder placeHolder = (PlaceHolder) holder;
placeHolder.setLoading(loadingIndex == position);
}
}
@Override
public int getItemCount() {
return items.size();
}
@Override
public int getItemViewType(int position) {
Notification item = items.get(position);
if (item == null)
return TYPE_PLACEHOLER;
switch (item.getType()) {
default:
return TYPE_PLACEHOLER;
case Notification.TYPE_FAVORITE:
case Notification.TYPE_MENTION:
case Notification.TYPE_REPOST:
case Notification.TYPE_POLL:
case Notification.TYPE_STATUS:
case Notification.TYPE_UPDATE:
return TYPE_STATUS;
case Notification.TYPE_FOLLOW:
case Notification.TYPE_REQUEST:
return TYPE_USER;
}
}
@Override
public boolean onHolderClick(int position) {
return false;
}
@Override
public void onStatusClick(int position, int type) {
Notification item = items.get(position);
switch (type) {
case OnStatusClickListener.TYPE_LABEL:
if (item != null && item.getUser() != null) {
listener.onUserClick(item.getUser());
}
break;
case OnStatusClickListener.TYPE_STATUS:
if (item != null && item.getStatus() != null) {
listener.onStatusClick(item.getStatus());
}
break;
}
}
@Override
public void onUserClick(int position, int type) {
Notification item = items.get(position);
if (type == OnUserClickListener.ITEM_CLICK) {
if (item != null && item.getUser() != null) {
listener.onUserClick(item.getUser());
}
}
}
/**
* add new items at specific position
*
* @param newItems items to add
* @param index position where to add the items
*/
public void addItems(List<Notification> newItems, int index) {
disableLoading();
if (newItems.size() > MIN_COUNT) {
if (items.isEmpty() || items.get(index) != null) {
// Add placeholder
items.add(index, null);
notifyItemInserted(index);
}
} else {
if (!items.isEmpty() && items.get(index) == null) {
// remove placeholder
items.remove(index);
notifyItemRemoved(index);
}
}
if (!newItems.isEmpty()) {
items.addAll(index, newItems);
notifyItemRangeInserted(index, newItems.size());
}
}
/**
* disable placeholder load animation
*/
public void disableLoading() {
if (loadingIndex != NO_LOADING) {
int oldIndex = loadingIndex;
loadingIndex = NO_LOADING;
notifyItemChanged(oldIndex);
}
}
/**
* @return true if adapter is empty
*/
public boolean isEmpty() {
return items.isEmpty();
}
/**
* notification item listener
*/
public interface OnNotificationClickListener {
/**
* called on status item click
*
* @param status clicked status
*/
void onStatusClick(Status status);
/**
* called on user item click
*
* @param user clicked user
*/
void onUserClick(User user);
/**
* called on placeholder click
*
* @param sinceId notification ID below the placeholder
* @param maxId notification ID over the placeholder
* @param position position of the placeholder
* @return true to enable loading animation
*/
boolean onPlaceholderClick(long sinceId, long maxId, long position);
}
}

View File

@ -1,15 +1,8 @@
package org.nuclearfog.twidda.adapter;
import static android.view.View.GONE;
import static android.view.View.VISIBLE;
import static androidx.recyclerview.widget.RecyclerView.NO_ID;
import static androidx.recyclerview.widget.RecyclerView.NO_POSITION;
import android.content.Context;
import android.content.res.Resources;
import android.text.Spanned;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
@ -18,30 +11,25 @@ import androidx.recyclerview.widget.RecyclerView.ViewHolder;
import com.squareup.picasso.Picasso;
import org.nuclearfog.tag.Tagger;
import org.nuclearfog.twidda.R;
import org.nuclearfog.twidda.adapter.holder.PlaceHolder;
import org.nuclearfog.twidda.adapter.holder.PlaceHolder.OnHolderClickListener;
import org.nuclearfog.twidda.adapter.holder.StatusHolder;
import org.nuclearfog.twidda.adapter.holder.StatusHolder.OnStatusClickListener;
import org.nuclearfog.twidda.backend.utils.PicassoBuilder;
import org.nuclearfog.twidda.backend.utils.StringTools;
import org.nuclearfog.twidda.database.GlobalSettings;
import org.nuclearfog.twidda.model.Status;
import org.nuclearfog.twidda.model.User;
import org.nuclearfog.twidda.ui.fragments.StatusFragment;
import java.text.NumberFormat;
import java.util.LinkedList;
import java.util.List;
import jp.wasabeef.picasso.transformations.RoundedCornersTransformation;
/**
* custom {@link androidx.recyclerview.widget.RecyclerView} adapter to show statuses
*
* @author nuclearfog
* @see StatusFragment
*/
public class StatusAdapter extends Adapter<ViewHolder> {
public class StatusAdapter extends Adapter<ViewHolder> implements OnStatusClickListener, OnHolderClickListener {
/**
* index of {@link #loadingIndex} if no index is defined
@ -63,18 +51,12 @@ public class StatusAdapter extends Adapter<ViewHolder> {
*/
private static final int MIN_COUNT = 2;
/**
* Locale specific number format
*/
private static final NumberFormat NUM_FORMAT = NumberFormat.getIntegerInstance();
private StatusSelectListener itemClickListener;
private GlobalSettings settings;
private Resources resources;
private Picasso picasso;
private final List<Status> statuses = new LinkedList<>();
private final List<Status> items = new LinkedList<>();
private int loadingIndex = NO_LOADING;
/**
@ -84,13 +66,12 @@ public class StatusAdapter extends Adapter<ViewHolder> {
this.itemClickListener = itemClickListener;
settings = GlobalSettings.getInstance(context);
picasso = PicassoBuilder.get(context);
resources = context.getResources();
}
@Override
public long getItemId(int index) {
Status status = statuses.get(index);
Status status = items.get(index);
if (status != null)
return status.getId();
return NO_ID;
@ -99,13 +80,13 @@ public class StatusAdapter extends Adapter<ViewHolder> {
@Override
public int getItemCount() {
return statuses.size();
return items.size();
}
@Override
public int getItemViewType(int index) {
if (statuses.get(index) == null)
if (items.get(index) == null)
return VIEW_PLACEHOLDER;
return VIEW_STATUS;
}
@ -115,57 +96,12 @@ public class StatusAdapter extends Adapter<ViewHolder> {
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
if (viewType == VIEW_STATUS) {
final StatusHolder vh = new StatusHolder(parent, settings);
vh.itemView.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
int position = vh.getLayoutPosition();
if (position != NO_POSITION) {
Status status = statuses.get(position);
if (status != null) {
itemClickListener.onStatusSelected(status);
}
}
}
});
StatusHolder vh = new StatusHolder(parent, settings, picasso);
vh.setOnStatusClickListener(this);
return vh;
} else {
final PlaceHolder placeHolder = new PlaceHolder(parent, settings, false);
placeHolder.loadBtn.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
int position = placeHolder.getLayoutPosition();
if (position != NO_POSITION) {
long sinceId = 0;
long maxId = 0;
if (position == 0) {
Status status = statuses.get(position + 1);
if (status != null) {
sinceId = status.getId();
}
} else if (position == statuses.size() - 1) {
Status status = statuses.get(position - 1);
if (status != null) {
maxId = status.getId() - 1;
}
} else {
Status status = statuses.get(position + 1);
if (status != null) {
sinceId = status.getId();
}
status = statuses.get(position - 1);
if (status != null) {
maxId = status.getId() - 1;
}
}
boolean success = itemClickListener.onPlaceholderClick(sinceId, maxId, position);
if (success) {
placeHolder.setLoading(true);
loadingIndex = position;
}
}
}
});
PlaceHolder placeHolder = new PlaceHolder(parent, settings, false);
placeHolder.setOnHolderClickListener(this);
return placeHolder;
}
}
@ -174,94 +110,9 @@ public class StatusAdapter extends Adapter<ViewHolder> {
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int index) {
if (holder instanceof StatusHolder) {
Status status = statuses.get(index);
Status status = items.get(index);
if (status != null) {
StatusHolder statusHolder = (StatusHolder) holder;
User user = status.getAuthor();
if (status.getEmbeddedStatus() != null) {
statusHolder.reposter.setText(user.getScreenname());
statusHolder.reposter.setVisibility(VISIBLE);
statusHolder.rpUser.setVisibility(VISIBLE);
status = status.getEmbeddedStatus();
user = status.getAuthor();
} else {
statusHolder.reposter.setVisibility(GONE);
statusHolder.rpUser.setVisibility(GONE);
}
statusHolder.username.setText(user.getUsername());
statusHolder.screenname.setText(user.getScreenname());
statusHolder.repost.setText(NUM_FORMAT.format(status.getRepostCount()));
statusHolder.favorite.setText(NUM_FORMAT.format(status.getFavoriteCount()));
statusHolder.created.setText(StringTools.formatCreationTime(resources, status.getTimestamp()));
if (!status.getText().isEmpty()) {
Spanned text = Tagger.makeTextWithLinks(status.getText(), settings.getHighlightColor());
statusHolder.text.setText(text);
statusHolder.text.setVisibility(VISIBLE);
} else {
statusHolder.text.setVisibility(GONE);
}
if (status.isReposted()) {
statusHolder.rtIcon.setColorFilter(settings.getRepostIconColor());
} else {
statusHolder.rtIcon.setColorFilter(settings.getIconColor());
}
if (status.isFavorited()) {
statusHolder.favIcon.setColorFilter(settings.getFavoriteIconColor());
} else {
statusHolder.favIcon.setColorFilter(settings.getIconColor());
}
if (user.isVerified()) {
statusHolder.verifiedIcon.setVisibility(VISIBLE);
} else {
statusHolder.verifiedIcon.setVisibility(GONE);
}
if (user.isProtected()) {
statusHolder.lockedIcon.setVisibility(VISIBLE);
} else {
statusHolder.lockedIcon.setVisibility(GONE);
}
if (settings.imagesEnabled() && !user.getImageUrl().isEmpty()) {
String profileImageUrl;
if (!user.hasDefaultProfileImage()) {
profileImageUrl = StringTools.buildImageLink(user.getImageUrl(), settings.getImageSuffix());
} else {
profileImageUrl = user.getImageUrl();
}
picasso.load(profileImageUrl).transform(new RoundedCornersTransformation(2, 0)).error(R.drawable.no_image).into(statusHolder.profile);
} else {
statusHolder.profile.setImageResource(0);
}
if (status.getRepliedStatusId() > 0) {
statusHolder.replyIcon.setVisibility(VISIBLE);
statusHolder.replyname.setVisibility(VISIBLE);
statusHolder.replyname.setText(status.getReplyName());
} else {
statusHolder.replyIcon.setVisibility(GONE);
statusHolder.replyname.setVisibility(GONE);
}
if (settings.statusIndicatorsEnabled()) {
if (status.getLocationName() != null && !status.getLocationName().isEmpty()) {
statusHolder.location.setVisibility(VISIBLE);
} else {
statusHolder.location.setVisibility(GONE);
}
if (status.getMediaType() != Status.MEDIA_NONE) {
if (status.getMediaType() == Status.MEDIA_PHOTO) {
statusHolder.media.setImageResource(R.drawable.image);
} else if (status.getMediaType() == Status.MEDIA_VIDEO) {
statusHolder.media.setImageResource(R.drawable.video);
} else if (status.getMediaType() == Status.MEDIA_GIF) {
statusHolder.media.setImageResource(R.drawable.gif);
}
statusHolder.media.setColorFilter(settings.getIconColor());
statusHolder.media.setVisibility(VISIBLE);
} else {
statusHolder.media.setVisibility(GONE);
}
} else {
statusHolder.location.setVisibility(GONE);
statusHolder.media.setVisibility(GONE);
}
((StatusHolder) holder).setContent(status);
}
} else if (holder instanceof PlaceHolder) {
PlaceHolder placeHolder = (PlaceHolder) holder;
@ -269,43 +120,87 @@ public class StatusAdapter extends Adapter<ViewHolder> {
}
}
@Override
public boolean onHolderClick(int position) {
long sinceId = 0;
long maxId = 0;
if (position == 0) {
Status status = items.get(position + 1);
if (status != null) {
sinceId = status.getId();
}
} else if (position == items.size() - 1) {
Status status = items.get(position - 1);
if (status != null) {
maxId = status.getId() - 1;
}
} else {
Status status = items.get(position + 1);
if (status != null) {
sinceId = status.getId();
}
status = items.get(position - 1);
if (status != null) {
maxId = status.getId() - 1;
}
}
boolean success = itemClickListener.onPlaceholderClick(sinceId, maxId, position);
if (success) {
loadingIndex = position;
return true;
}
return false;
}
@Override
public void onStatusClick(int position, int type) {
if (type == OnStatusClickListener.TYPE_STATUS) {
Status status = items.get(position);
if (status != null) {
itemClickListener.onStatusSelected(status);
}
}
}
/**
* Insert data at specific index of the list
*
* @param statuses list of statuses to insert
* @param newItems list of statuses to insert
* @param index position to insert
*/
public void addItems(@NonNull List<Status> statuses, int index) {
public void addItems(@NonNull List<Status> newItems, int index) {
disableLoading();
if (statuses.size() > MIN_COUNT) {
if (this.statuses.isEmpty() || this.statuses.get(index) != null) {
if (newItems.size() > MIN_COUNT) {
if (items.isEmpty() || items.get(index) != null) {
// Add placeholder
this.statuses.add(index, null);
items.add(index, null);
notifyItemInserted(index);
}
} else {
if (!this.statuses.isEmpty() && this.statuses.get(index) == null) {
if (!items.isEmpty() && items.get(index) == null) {
// remove placeholder
this.statuses.remove(index);
items.remove(index);
notifyItemRemoved(index);
}
}
if (!statuses.isEmpty()) {
this.statuses.addAll(index, statuses);
notifyItemRangeInserted(index, statuses.size());
if (!newItems.isEmpty()) {
items.addAll(index, newItems);
notifyItemRangeInserted(index, newItems.size());
}
}
/**
* Replace all items in the list
*
* @param statuses list of statuses to add
* @param newItems list of statuses to add
*/
public void replaceItems(@NonNull List<Status> statuses) {
this.statuses.clear();
this.statuses.addAll(statuses);
if (statuses.size() > MIN_COUNT) {
this.statuses.add(null);
public void replaceItems(@NonNull List<Status> newItems) {
items.clear();
items.addAll(newItems);
if (newItems.size() > MIN_COUNT) {
items.add(null);
}
loadingIndex = NO_LOADING;
notifyDataSetChanged();
@ -317,9 +212,9 @@ public class StatusAdapter extends Adapter<ViewHolder> {
* @param status status to update
*/
public void updateItem(Status status) {
int index = statuses.indexOf(status);
int index = items.indexOf(status);
if (index >= 0) {
statuses.set(index, status);
items.set(index, status);
notifyItemChanged(index);
}
}
@ -330,13 +225,13 @@ public class StatusAdapter extends Adapter<ViewHolder> {
* @param id ID of the status
*/
public void removeItem(long id) {
for (int pos = statuses.size() - 1; pos >= 0; pos--) {
Status status = statuses.get(pos);
for (int pos = items.size() - 1; pos >= 0; pos--) {
Status status = items.get(pos);
if (status != null) {
Status embedded = status.getEmbeddedStatus();
// remove status and any repost of it
if (status.getId() == id || (embedded != null && embedded.getId() == id)) {
statuses.remove(pos);
items.remove(pos);
notifyItemRemoved(pos);
}
}
@ -349,7 +244,7 @@ public class StatusAdapter extends Adapter<ViewHolder> {
* @return true if list is empty
*/
public boolean isEmpty() {
return statuses.isEmpty();
return items.isEmpty();
}
/**

View File

@ -1,24 +1,16 @@
package org.nuclearfog.twidda.adapter;
import static android.view.View.GONE;
import static android.view.View.VISIBLE;
import static androidx.recyclerview.widget.RecyclerView.NO_POSITION;
import android.content.res.Resources;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView.Adapter;
import androidx.recyclerview.widget.RecyclerView.ViewHolder;
import org.nuclearfog.twidda.R;
import org.nuclearfog.twidda.adapter.holder.TrendHolder;
import org.nuclearfog.twidda.adapter.holder.TrendHolder.OnTrendClickListener;
import org.nuclearfog.twidda.database.GlobalSettings;
import org.nuclearfog.twidda.model.Trend;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.List;
@ -28,18 +20,13 @@ import java.util.List;
* @author nuclearfog
* @see org.nuclearfog.twidda.ui.fragments.TrendFragment
*/
public class TrendAdapter extends Adapter<ViewHolder> {
public class TrendAdapter extends Adapter<ViewHolder> implements OnTrendClickListener {
/**
* trend limit
*/
private static final int INIT_COUNT = 50;
/**
* Locale specific number format
*/
private static final NumberFormat NUM_FORMAT = NumberFormat.getIntegerInstance();
private TrendClickListener itemClickListener;
private GlobalSettings settings;
@ -64,15 +51,7 @@ public class TrendAdapter extends Adapter<ViewHolder> {
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
final TrendHolder vh = new TrendHolder(parent, settings);
vh.itemView.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
int position = vh.getLayoutPosition();
if (position != NO_POSITION) {
itemClickListener.onTrendClick(trends.get(position));
}
}
});
vh.setOnTrendClickListener(this);
return vh;
}
@ -81,16 +60,13 @@ public class TrendAdapter extends Adapter<ViewHolder> {
public void onBindViewHolder(@NonNull ViewHolder vh, int index) {
TrendHolder holder = (TrendHolder) vh;
Trend trend = trends.get(index);
holder.rank.setText(trend.getRank() + ".");
holder.name.setText(trend.getName());
if (trend.getPopularity() > 0) {
Resources resources = holder.vol.getContext().getResources();
String trendVol = NUM_FORMAT.format(trend.getPopularity()) + " " + resources.getString(R.string.trend_range);
holder.vol.setText(trendVol);
holder.vol.setVisibility(VISIBLE);
} else {
holder.vol.setVisibility(GONE);
}
holder.setContent(trend);
}
@Override
public void onTrendClick(int position) {
itemClickListener.onTrendClick(trends.get(position));
}
/**

View File

@ -1,13 +1,8 @@
package org.nuclearfog.twidda.adapter;
import static android.view.View.GONE;
import static android.view.View.VISIBLE;
import static androidx.recyclerview.widget.RecyclerView.NO_ID;
import static androidx.recyclerview.widget.RecyclerView.NO_POSITION;
import android.content.Context;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
@ -16,26 +11,22 @@ import androidx.recyclerview.widget.RecyclerView.ViewHolder;
import com.squareup.picasso.Picasso;
import org.nuclearfog.twidda.R;
import org.nuclearfog.twidda.adapter.holder.PlaceHolder;
import org.nuclearfog.twidda.adapter.holder.PlaceHolder.OnHolderClickListener;
import org.nuclearfog.twidda.adapter.holder.UserHolder;
import org.nuclearfog.twidda.adapter.holder.UserHolder.OnUserClickListener;
import org.nuclearfog.twidda.backend.lists.Users;
import org.nuclearfog.twidda.backend.utils.PicassoBuilder;
import org.nuclearfog.twidda.backend.utils.StringTools;
import org.nuclearfog.twidda.database.GlobalSettings;
import org.nuclearfog.twidda.model.User;
import java.text.NumberFormat;
import jp.wasabeef.picasso.transformations.RoundedCornersTransformation;
/**
* custom {@link androidx.recyclerview.widget.RecyclerView} adapter implementation to show users
*
* @author nuclearfog
* @see org.nuclearfog.twidda.ui.fragments.UserFragment
*/
public class UserAdapter extends Adapter<ViewHolder> {
public class UserAdapter extends Adapter<ViewHolder> implements OnUserClickListener, OnHolderClickListener {
/**
* index of {@link #loadingIndex} if no index is defined
@ -52,10 +43,7 @@ public class UserAdapter extends Adapter<ViewHolder> {
*/
private static final int ITEM_GAP = 1;
/**
* locale specific number formatter
*/
private static final NumberFormat NUM_FORMAT = NumberFormat.getIntegerInstance();
private GlobalSettings settings;
private Picasso picasso;
@ -105,52 +93,12 @@ public class UserAdapter extends Adapter<ViewHolder> {
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
if (viewType == ITEM_USER) {
final UserHolder vh = new UserHolder(parent, settings);
vh.itemView.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
int position = vh.getLayoutPosition();
if (position != NO_POSITION) {
User user = users.get(position);
if (user != null) {
listener.onUserClick(user);
}
}
}
});
if (enableDelete) {
vh.delete.setVisibility(VISIBLE);
vh.delete.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
int position = vh.getLayoutPosition();
if (position != NO_POSITION) {
User user = users.get(position);
if (user != null) {
listener.onDelete(user);
}
}
}
});
} else {
vh.delete.setVisibility(GONE);
}
UserHolder vh = new UserHolder(parent, settings, picasso);
vh.setOnUserClickListener(this);
return vh;
} else {
final PlaceHolder placeHolder = new PlaceHolder(parent, settings, false);
placeHolder.loadBtn.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
int position = placeHolder.getLayoutPosition();
if (position != NO_POSITION) {
boolean actionPerformed = listener.onPlaceholderClick(users.getNext());
if (actionPerformed) {
placeHolder.setLoading(true);
loadingIndex = position;
}
}
}
});
PlaceHolder placeHolder = new PlaceHolder(parent, settings, false);
placeHolder.setOnHolderClickListener(this);
return placeHolder;
}
}
@ -161,32 +109,7 @@ public class UserAdapter extends Adapter<ViewHolder> {
if (holder instanceof UserHolder) {
User user = users.get(index);
if (user != null) {
UserHolder userholder = (UserHolder) holder;
userholder.username.setText(user.getUsername());
userholder.screenname.setText(user.getScreenname());
userholder.followingCount.setText(NUM_FORMAT.format(user.getFollowing()));
userholder.followerCount.setText(NUM_FORMAT.format(user.getFollower()));
if (user.isVerified()) {
userholder.verifyIcon.setVisibility(VISIBLE);
} else {
userholder.verifyIcon.setVisibility(GONE);
}
if (user.isProtected()) {
userholder.lockedIcon.setVisibility(VISIBLE);
} else {
userholder.lockedIcon.setVisibility(GONE);
}
if (settings.imagesEnabled() && !user.getImageUrl().isEmpty()) {
String profileImageUrl;
if (!user.hasDefaultProfileImage()) {
profileImageUrl = StringTools.buildImageLink(user.getImageUrl(), settings.getImageSuffix());
} else {
profileImageUrl = user.getImageUrl();
}
picasso.load(profileImageUrl).transform(new RoundedCornersTransformation(2, 0)).error(R.drawable.no_image).into(userholder.profileImg);
} else {
userholder.profileImg.setImageResource(0);
}
((UserHolder) holder).setContent(user);
}
} else if (holder instanceof PlaceHolder) {
PlaceHolder placeHolder = (PlaceHolder) holder;
@ -194,6 +117,37 @@ public class UserAdapter extends Adapter<ViewHolder> {
}
}
@Override
public boolean onHolderClick(int position) {
boolean actionPerformed = listener.onPlaceholderClick(users.getNext());
if (actionPerformed) {
loadingIndex = position;
return true;
}
return false;
}
@Override
public void onUserClick(int position, int type) {
switch (type) {
case OnUserClickListener.ITEM_CLICK:
User user = users.get(position);
if (user != null) {
listener.onUserClick(user);
}
break;
case OnUserClickListener.ITEM_REMOVE:
user = users.get(position);
if (enableDelete && user != null) {
listener.onDelete(user);
}
break;
}
}
/**
* insert an user list depending on cursor to the top or bottom
*

View File

@ -1,13 +1,6 @@
package org.nuclearfog.twidda.adapter;
import static android.view.View.GONE;
import static android.view.View.VISIBLE;
import static androidx.recyclerview.widget.RecyclerView.NO_POSITION;
import android.content.Context;
import android.content.res.Resources;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
@ -16,27 +9,23 @@ import androidx.recyclerview.widget.RecyclerView.ViewHolder;
import com.squareup.picasso.Picasso;
import org.nuclearfog.twidda.R;
import org.nuclearfog.twidda.adapter.holder.PlaceHolder;
import org.nuclearfog.twidda.adapter.holder.PlaceHolder.OnHolderClickListener;
import org.nuclearfog.twidda.adapter.holder.UserlistHolder;
import org.nuclearfog.twidda.adapter.holder.UserlistHolder.OnListClickListener;
import org.nuclearfog.twidda.backend.lists.UserLists;
import org.nuclearfog.twidda.backend.utils.PicassoBuilder;
import org.nuclearfog.twidda.backend.utils.StringTools;
import org.nuclearfog.twidda.database.GlobalSettings;
import org.nuclearfog.twidda.model.User;
import org.nuclearfog.twidda.model.UserList;
import java.text.NumberFormat;
import jp.wasabeef.picasso.transformations.RoundedCornersTransformation;
/**
* custom {@link androidx.recyclerview.widget.RecyclerView} adapter implementation to show userlists
*
* @author nuclearfog
* @see org.nuclearfog.twidda.ui.fragments.UserListFragment
*/
public class UserlistAdapter extends Adapter<ViewHolder> {
public class UserlistAdapter extends Adapter<ViewHolder> implements OnListClickListener, OnHolderClickListener {
/**
* indicator if there is no loading progress
@ -56,11 +45,9 @@ public class UserlistAdapter extends Adapter<ViewHolder> {
/**
* locale specific number format
*/
private static final NumberFormat NUM_FORMAT = NumberFormat.getIntegerInstance();
private ListClickListener listener;
private GlobalSettings settings;
private Resources resources;
private Picasso picasso;
private UserLists userlists = new UserLists(0L, 0L);
@ -74,7 +61,6 @@ public class UserlistAdapter extends Adapter<ViewHolder> {
this.listener = listener;
settings = GlobalSettings.getInstance(context);
picasso = PicassoBuilder.get(context);
resources = context.getResources();
}
@ -96,47 +82,10 @@ public class UserlistAdapter extends Adapter<ViewHolder> {
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
if (viewType == ITEM_LIST) {
final UserlistHolder itemHolder = new UserlistHolder(parent, settings);
itemHolder.profile.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
int position = itemHolder.getLayoutPosition();
if (position != NO_POSITION) {
UserList item = userlists.get(position);
if (item != null) {
listener.onProfileClick(item.getListOwner());
}
}
}
});
itemHolder.itemView.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
int position = itemHolder.getLayoutPosition();
if (position != NO_POSITION) {
UserList item = userlists.get(position);
if (item != null) {
listener.onListClick(item);
}
}
}
});
return itemHolder;
return new UserlistHolder(parent, settings, picasso, this);
} else {
final PlaceHolder placeHolder = new PlaceHolder(parent, settings, false);
placeHolder.loadBtn.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
int position = placeHolder.getLayoutPosition();
if (position != NO_POSITION) {
boolean actionPerformed = listener.onPlaceholderClick(userlists.getNext());
if (actionPerformed) {
placeHolder.setLoading(true);
loadingIndex = position;
}
}
}
});
PlaceHolder placeHolder = new PlaceHolder(parent, settings, false);
placeHolder.setOnHolderClickListener(this);
return placeHolder;
}
}
@ -148,47 +97,7 @@ public class UserlistAdapter extends Adapter<ViewHolder> {
UserlistHolder vh = (UserlistHolder) holder;
UserList item = userlists.get(index);
if (item != null) {
User owner = item.getListOwner();
vh.title.setText(item.getTitle());
vh.description.setText(item.getDescription());
vh.username.setText(owner.getUsername());
vh.screenname.setText(owner.getScreenname());
vh.date.setText(StringTools.formatCreationTime(resources, item.getTimestamp()));
vh.member.setText(NUM_FORMAT.format(item.getMemberCount()));
vh.subscriber.setText(NUM_FORMAT.format(item.getSubscriberCount()));
if (settings.imagesEnabled() && !owner.getImageUrl().isEmpty()) {
String profileImageUrl;
if (!owner.hasDefaultProfileImage()) {
profileImageUrl = StringTools.buildImageLink(owner.getImageUrl(), settings.getImageSuffix());
} else {
profileImageUrl = owner.getImageUrl();
}
picasso.load(profileImageUrl).transform(new RoundedCornersTransformation(3, 0)).error(R.drawable.no_image).into(vh.profile);
} else {
vh.profile.setImageResource(0);
}
if (!item.getListOwner().isCurrentUser() && item.isFollowing()) {
vh.follow.setVisibility(VISIBLE);
vh.followList.setVisibility(VISIBLE);
} else {
vh.follow.setVisibility(GONE);
vh.followList.setVisibility(GONE);
}
if (owner.isVerified()) {
vh.verified.setVisibility(VISIBLE);
} else {
vh.verified.setVisibility(GONE);
}
if (owner.isProtected()) {
vh.locked.setVisibility(VISIBLE);
} else {
vh.locked.setVisibility(GONE);
}
if (item.isPrivate()) {
vh.privateList.setVisibility(VISIBLE);
} else {
vh.privateList.setVisibility(GONE);
}
vh.setContent(item);
}
} else if (holder instanceof PlaceHolder) {
PlaceHolder placeHolder = (PlaceHolder) holder;
@ -196,6 +105,32 @@ public class UserlistAdapter extends Adapter<ViewHolder> {
}
}
@Override
public void onUserlistClick(int position, int type) {
UserList item = userlists.get(position);
if (item != null) {
switch (type) {
case OnListClickListener.LIST_CLICK:
listener.onListClick(item);
break;
case OnListClickListener.PROFILE_CLICK:
listener.onProfileClick(item.getListOwner());
break;
}
}
}
@Override
public boolean onHolderClick(int position) {
boolean actionPerformed = listener.onPlaceholderClick(userlists.getNext());
if (actionPerformed)
loadingIndex = position;
return actionPerformed;
}
/**
* adds new data to the list
*

View File

@ -1,11 +1,12 @@
package org.nuclearfog.twidda.adapter.holder;
import static android.view.View.INVISIBLE;
import static android.view.View.VISIBLE;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
import static androidx.recyclerview.widget.RecyclerView.NO_POSITION;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.ProgressBar;
@ -22,11 +23,13 @@ import org.nuclearfog.twidda.database.GlobalSettings;
*
* @author nuclearfog
*/
public class PlaceHolder extends ViewHolder {
public class PlaceHolder extends ViewHolder implements OnClickListener {
public final ProgressBar loadCircle;
public final Button loadBtn;
private OnHolderClickListener listener;
/**
* @param parent Parent view from adapter
* @param horizontal true if placeholder orientation is horizontal
@ -45,11 +48,24 @@ public class PlaceHolder extends ViewHolder {
AppStyles.setProgressColor(loadCircle, settings.getHighlightColor());
// enable extra views
if (horizontal) {
loadBtn.setVisibility(INVISIBLE);
loadCircle.setVisibility(VISIBLE);
loadBtn.setVisibility(View.INVISIBLE);
loadCircle.setVisibility(View.VISIBLE);
background.getLayoutParams().height = MATCH_PARENT;
background.getLayoutParams().width = WRAP_CONTENT;
}
loadBtn.setOnClickListener(this);
}
@Override
public void onClick(View v) {
if (v == loadBtn) {
int position = getLayoutPosition();
if (position != NO_POSITION && listener != null) {
boolean enableLoading = listener.onHolderClick(position);
setLoading(enableLoading);
}
}
}
/**
@ -59,11 +75,31 @@ public class PlaceHolder extends ViewHolder {
*/
public void setLoading(boolean enable) {
if (enable) {
loadCircle.setVisibility(VISIBLE);
loadBtn.setVisibility(INVISIBLE);
loadCircle.setVisibility(View.VISIBLE);
loadBtn.setVisibility(View.INVISIBLE);
} else {
loadCircle.setVisibility(INVISIBLE);
loadBtn.setVisibility(VISIBLE);
loadCircle.setVisibility(View.INVISIBLE);
loadBtn.setVisibility(View.VISIBLE);
}
}
/**
* set click listener for this item
*/
public void setOnHolderClickListener(OnHolderClickListener listener) {
this.listener = listener;
}
/**
* listener used to call after item click
*/
public interface OnHolderClickListener {
/**
*
* @param position position of the item
* @return true to enable loading animation
*/
boolean onHolderClick(int position);
}
}

View File

@ -1,6 +1,13 @@
package org.nuclearfog.twidda.adapter.holder;
import static androidx.recyclerview.widget.RecyclerView.NO_POSITION;
import android.content.res.Resources;
import android.text.Spanned;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
@ -8,10 +15,21 @@ import android.widget.TextView;
import androidx.cardview.widget.CardView;
import androidx.recyclerview.widget.RecyclerView.ViewHolder;
import com.squareup.picasso.Picasso;
import org.nuclearfog.tag.Tagger;
import org.nuclearfog.twidda.R;
import org.nuclearfog.twidda.adapter.StatusAdapter;
import org.nuclearfog.twidda.backend.utils.AppStyles;
import org.nuclearfog.twidda.backend.utils.StringTools;
import org.nuclearfog.twidda.database.GlobalSettings;
import org.nuclearfog.twidda.model.Notification;
import org.nuclearfog.twidda.model.Status;
import org.nuclearfog.twidda.model.User;
import java.text.NumberFormat;
import jp.wasabeef.picasso.transformations.RoundedCornersTransformation;
/**
* Holder class for the status item view
@ -19,19 +37,27 @@ import org.nuclearfog.twidda.database.GlobalSettings;
* @author nuclearfog
* @see StatusAdapter
*/
public class StatusHolder extends ViewHolder {
public class StatusHolder extends ViewHolder implements OnClickListener {
public final ImageView profile, rpUser, verifiedIcon, lockedIcon, rtIcon, favIcon, media, location, replyIcon;
public final TextView username, screenname, text, repost, favorite, reposter, created, replyname;
private static final NumberFormat NUM_FORMAT = NumberFormat.getIntegerInstance();
private ImageView profile, rpUser, verifiedIcon, lockedIcon, rtIcon, favIcon, media, location, replyIcon;
private TextView username, screenname, text, repost, favorite, reposter, created, replyname, label;
private GlobalSettings settings;
private Picasso picasso;
private OnStatusClickListener listener;
/**
* @param parent Parent view from adapter
* @param settings app settings to set theme
*/
public StatusHolder(ViewGroup parent, GlobalSettings settings) {
public StatusHolder(ViewGroup parent, GlobalSettings settings, Picasso picasso) {
super(LayoutInflater.from(parent.getContext()).inflate(R.layout.item_status, parent, false));
CardView cardLayout = (CardView) itemView;
ViewGroup container = itemView.findViewById(R.id.item_status_container);
label = itemView.findViewById(R.id.item_status_label);
profile = itemView.findViewById(R.id.item_status_profile_image);
verifiedIcon = itemView.findViewById(R.id.item_status_verified_icon);
lockedIcon = itemView.findViewById(R.id.item_status_locked_icon);
@ -57,5 +83,175 @@ public class StatusHolder extends ViewHolder {
}
AppStyles.setTheme(container, 0);
cardLayout.setCardBackgroundColor(settings.getCardColor());
this.settings = settings;
this.picasso = picasso;
}
@Override
public void onClick(View v) {
int position = getLayoutPosition();
if (position != NO_POSITION && listener != null) {
if (v == itemView) {
listener.onStatusClick(position, OnStatusClickListener.TYPE_STATUS);
} else if (v == label) {
listener.onStatusClick(position, OnStatusClickListener.TYPE_LABEL);
}
}
}
/**
* set view content
*
* @param status content to show
*/
public void setContent(Status status) {
User user = status.getAuthor();
if (status.getEmbeddedStatus() != null) {
reposter.setText(user.getScreenname());
reposter.setVisibility(View.VISIBLE);
rpUser.setVisibility(View.VISIBLE);
status = status.getEmbeddedStatus();
user = status.getAuthor();
} else {
reposter.setVisibility(View.GONE);
rpUser.setVisibility(View.GONE);
}
username.setText(user.getUsername());
screenname.setText(user.getScreenname());
repost.setText(NUM_FORMAT.format(status.getRepostCount()));
favorite.setText(NUM_FORMAT.format(status.getFavoriteCount()));
created.setText(StringTools.formatCreationTime(itemView.getResources(), status.getTimestamp()));
if (!status.getText().isEmpty()) {
Spanned textSpan = Tagger.makeTextWithLinks(status.getText(), settings.getHighlightColor());
text.setText(textSpan);
text.setVisibility(View.VISIBLE);
} else {
text.setVisibility(View.GONE);
}
if (status.isReposted()) {
rtIcon.setColorFilter(settings.getRepostIconColor());
} else {
rtIcon.setColorFilter(settings.getIconColor());
}
if (status.isFavorited()) {
favIcon.setColorFilter(settings.getFavoriteIconColor());
} else {
favIcon.setColorFilter(settings.getIconColor());
}
if (user.isVerified()) {
verifiedIcon.setVisibility(View.VISIBLE);
} else {
verifiedIcon.setVisibility(View.GONE);
}
if (user.isProtected()) {
lockedIcon.setVisibility(View.VISIBLE);
} else {
lockedIcon.setVisibility(View.GONE);
}
if (settings.imagesEnabled() && !user.getImageUrl().isEmpty()) {
String profileImageUrl;
if (!user.hasDefaultProfileImage()) {
profileImageUrl = StringTools.buildImageLink(user.getImageUrl(), settings.getImageSuffix());
} else {
profileImageUrl = user.getImageUrl();
}
picasso.load(profileImageUrl).transform(new RoundedCornersTransformation(2, 0)).error(R.drawable.no_image).into(profile);
} else {
profile.setImageResource(0);
}
if (status.getRepliedStatusId() > 0) {
replyIcon.setVisibility(View.VISIBLE);
replyname.setVisibility(View.VISIBLE);
replyname.setText(status.getReplyName());
} else {
replyIcon.setVisibility(View.GONE);
replyname.setVisibility(View.GONE);
}
if (settings.statusIndicatorsEnabled()) {
if (status.getLocationName() != null && !status.getLocationName().isEmpty()) {
location.setVisibility(View.VISIBLE);
} else {
location.setVisibility(View.GONE);
}
if (status.getMediaType() != Status.MEDIA_NONE) {
if (status.getMediaType() == Status.MEDIA_PHOTO) {
media.setImageResource(R.drawable.image);
} else if (status.getMediaType() == Status.MEDIA_VIDEO) {
media.setImageResource(R.drawable.video);
} else if (status.getMediaType() == Status.MEDIA_GIF) {
media.setImageResource(R.drawable.gif);
}
media.setColorFilter(settings.getIconColor());
media.setVisibility(View.VISIBLE);
} else {
media.setVisibility(View.GONE);
}
} else {
location.setVisibility(View.GONE);
media.setVisibility(View.GONE);
}
}
/**
* set notification label
*/
public void setLabel(Notification notification) {
int iconRes;
String text, name;
if (notification.getUser() != null)
name = notification.getUser().getScreenname();
else
name = "";
Resources resources = itemView.getResources();
switch (notification.getType()) {
default:
text = "";
iconRes = 0;
break;
case Notification.TYPE_MENTION:
text = resources.getString(R.string.info_user_mention, name);
iconRes = R.drawable.mention;
break;
case Notification.TYPE_REPOST:
text = resources.getString(R.string.info_user_repost, name);
iconRes = R.drawable.repost;
break;
case Notification.TYPE_FAVORITE:
text = resources.getString(R.string.info_user_favorited, name);
iconRes = R.drawable.favorite;
break;
}
label.setVisibility(View.VISIBLE);
label.setText(text);
label.setCompoundDrawablesWithIntrinsicBounds(iconRes, 0, 0, 0);
AppStyles.setDrawableColor(label, settings.getIconColor());
}
/**
* set item click listener
*/
public void setOnStatusClickListener(OnStatusClickListener listener) {
this.listener = listener;
}
/**
* item click listener
*/
public interface OnStatusClickListener {
int TYPE_STATUS = 1;
int TYPE_LABEL = 2;
/**
* @param position position of this item
* @param type type of user click {@link #TYPE_LABEL,#TYPE_STATUS}
*/
void onStatusClick(int position, int type);
}
}

View File

@ -1,6 +1,12 @@
package org.nuclearfog.twidda.adapter.holder;
import static androidx.recyclerview.widget.RecyclerView.NO_POSITION;
import android.content.res.Resources;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.TextView;
@ -10,6 +16,9 @@ import androidx.recyclerview.widget.RecyclerView.ViewHolder;
import org.nuclearfog.twidda.R;
import org.nuclearfog.twidda.backend.utils.AppStyles;
import org.nuclearfog.twidda.database.GlobalSettings;
import org.nuclearfog.twidda.model.Trend;
import java.text.NumberFormat;
/**
* ViewHolder for a trend item
@ -17,9 +26,16 @@ import org.nuclearfog.twidda.database.GlobalSettings;
* @author nuclearfog
* @see org.nuclearfog.twidda.adapter.TrendAdapter
*/
public class TrendHolder extends ViewHolder {
public class TrendHolder extends ViewHolder implements OnClickListener {
public final TextView name, rank, vol;
/**
* Locale specific number format
*/
private static final NumberFormat NUM_FORMAT = NumberFormat.getIntegerInstance();
private TextView name, rank, vol;
private OnTrendClickListener listener;
/**
* @param parent Parent view from adapter
@ -34,5 +50,53 @@ public class TrendHolder extends ViewHolder {
AppStyles.setTheme(container, 0);
background.setCardBackgroundColor(settings.getCardColor());
itemView.setOnClickListener(this);
}
@Override
public void onClick(View v) {
if (v == itemView) {
int position = getLayoutPosition();
if (position != NO_POSITION && listener != null) {
listener.onTrendClick(position);
}
}
}
/**
* set item click listener
*/
public void setOnTrendClickListener(OnTrendClickListener listener) {
this.listener = listener;
}
/**
* set view content
*
* @param trend content information
*/
public void setContent(Trend trend) {
rank.setText(trend.getRank() + ".");
name.setText(trend.getName());
if (trend.getPopularity() > 0) {
Resources resources = vol.getResources();
String trendVol = NUM_FORMAT.format(trend.getPopularity()) + " " + resources.getString(R.string.trend_range);
vol.setText(trendVol);
vol.setVisibility(View.VISIBLE);
} else {
vol.setVisibility(View.GONE);
}
}
/**
* Item click listener
*/
public interface OnTrendClickListener {
/**
* @param position index of the view holder
*/
void onTrendClick(int position);
}
}

View File

@ -1,6 +1,13 @@
package org.nuclearfog.twidda.adapter.holder;
import static android.view.View.GONE;
import static android.view.View.VISIBLE;
import static androidx.recyclerview.widget.RecyclerView.NO_POSITION;
import android.content.res.Resources;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.ImageButton;
import android.widget.ImageView;
@ -9,9 +16,18 @@ import android.widget.TextView;
import androidx.cardview.widget.CardView;
import androidx.recyclerview.widget.RecyclerView.ViewHolder;
import com.squareup.picasso.Picasso;
import org.nuclearfog.twidda.R;
import org.nuclearfog.twidda.backend.utils.AppStyles;
import org.nuclearfog.twidda.backend.utils.StringTools;
import org.nuclearfog.twidda.database.GlobalSettings;
import org.nuclearfog.twidda.model.Notification;
import org.nuclearfog.twidda.model.User;
import java.text.NumberFormat;
import jp.wasabeef.picasso.transformations.RoundedCornersTransformation;
/**
* View holder class for user item
@ -19,19 +35,30 @@ import org.nuclearfog.twidda.database.GlobalSettings;
* @author nuclearfog
* @see org.nuclearfog.twidda.adapter.UserAdapter
*/
public class UserHolder extends ViewHolder {
public class UserHolder extends ViewHolder implements OnClickListener {
public final TextView username, screenname, followingCount, followerCount;
public final ImageView profileImg, verifyIcon, lockedIcon;
public final ImageButton delete;
/**
* locale specific number formatter
*/
private static final NumberFormat NUM_FORMAT = NumberFormat.getIntegerInstance();
private TextView username, screenname, followingCount, followerCount, label;
private ImageView profileImg, verifyIcon, lockedIcon;
private ImageButton delete;
private GlobalSettings settings;
private Picasso picasso;
private OnUserClickListener listener;
/**
* @param parent Parent view from adapter
*/
public UserHolder(ViewGroup parent, GlobalSettings settings) {
public UserHolder(ViewGroup parent, GlobalSettings settings, Picasso picasso) {
super(LayoutInflater.from(parent.getContext()).inflate(R.layout.item_user, parent, false));
CardView background = (CardView) itemView;
ViewGroup container = itemView.findViewById(R.id.item_user_container);
label = itemView.findViewById(R.id.item_user_label);
username = itemView.findViewById(R.id.item_user_username);
screenname = itemView.findViewById(R.id.item_user_screenname);
followingCount = itemView.findViewById(R.id.item_user_following_count);
@ -43,5 +70,112 @@ public class UserHolder extends ViewHolder {
AppStyles.setTheme(container, 0);
background.setCardBackgroundColor(settings.getCardColor());
this.settings = settings;
this.picasso = picasso;
itemView.setOnClickListener(this);
delete.setOnClickListener(this);
}
@Override
public void onClick(View v) {
int position = getLayoutPosition();
if (listener != null && position != NO_POSITION) {
if (v == itemView) {
listener.onUserClick(position, OnUserClickListener.ITEM_CLICK);
} else if (v == delete) {
listener.onUserClick(position, OnUserClickListener.ITEM_REMOVE);
}
}
}
/**
* set item click listener
*/
public void setOnUserClickListener(OnUserClickListener listener) {
this.listener = listener;
}
/**
* set user information
*
* @param user user information
*/
public void setContent(User user) {
username.setText(user.getUsername());
screenname.setText(user.getScreenname());
followingCount.setText(NUM_FORMAT.format(user.getFollowing()));
followerCount.setText(NUM_FORMAT.format(user.getFollower()));
if (user.isVerified()) {
verifyIcon.setVisibility(VISIBLE);
} else {
verifyIcon.setVisibility(GONE);
}
if (user.isProtected()) {
lockedIcon.setVisibility(VISIBLE);
} else {
lockedIcon.setVisibility(GONE);
}
if (settings.imagesEnabled() && !user.getImageUrl().isEmpty()) {
String profileImageUrl;
if (!user.hasDefaultProfileImage()) {
profileImageUrl = StringTools.buildImageLink(user.getImageUrl(), settings.getImageSuffix());
} else {
profileImageUrl = user.getImageUrl();
}
picasso.load(profileImageUrl).transform(new RoundedCornersTransformation(2, 0)).error(R.drawable.no_image).into(profileImg);
} else {
profileImg.setImageResource(0);
}
}
/**
* set notification label
*/
public void setLabel(Notification notification) {
int iconRes;
String text, name;
Resources resources = itemView.getResources();
if (notification.getUser() != null)
name = notification.getUser().getScreenname();
else
name = "";
switch (notification.getType()) {
default:
text = "";
iconRes = 0;
break;
case Notification.TYPE_FOLLOW:
text = resources.getString(R.string.info_user_follow, name);
iconRes = R.drawable.follower;
break;
case Notification.TYPE_REQUEST:
text = resources.getString(R.string.info_user_follow_request, name);
iconRes = R.drawable.follower_request;
break;
}
label.setVisibility(VISIBLE);
label.setCompoundDrawablesWithIntrinsicBounds(iconRes, 0, 0, 0);
label.setText(text);
}
/**
* Item click listener
*/
public interface OnUserClickListener {
int ITEM_CLICK = 1;
int ITEM_REMOVE = 2;
/**
* @param position position of the item
* @param type type of action {@link #ITEM_REMOVE,#ITEM_CLICK}
*/
void onUserClick(int position, int type);
}
}

View File

@ -1,6 +1,10 @@
package org.nuclearfog.twidda.adapter.holder;
import static androidx.recyclerview.widget.RecyclerView.NO_POSITION;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
@ -8,9 +12,18 @@ import android.widget.TextView;
import androidx.cardview.widget.CardView;
import androidx.recyclerview.widget.RecyclerView.ViewHolder;
import com.squareup.picasso.Picasso;
import org.nuclearfog.twidda.R;
import org.nuclearfog.twidda.backend.utils.AppStyles;
import org.nuclearfog.twidda.backend.utils.StringTools;
import org.nuclearfog.twidda.database.GlobalSettings;
import org.nuclearfog.twidda.model.User;
import org.nuclearfog.twidda.model.UserList;
import java.text.NumberFormat;
import jp.wasabeef.picasso.transformations.RoundedCornersTransformation;
/**
* view holder class for an user list item
@ -18,15 +31,22 @@ import org.nuclearfog.twidda.database.GlobalSettings;
* @author nuclearfog
* @see org.nuclearfog.twidda.adapter.UserlistAdapter
*/
public class UserlistHolder extends ViewHolder {
public class UserlistHolder extends ViewHolder implements OnClickListener {
private static final NumberFormat NUM_FORMAT = NumberFormat.getIntegerInstance();
public final ImageView profile, verified, locked, privateList, follow;
public final TextView title, description, username, screenname, date, member, subscriber, followList;
private GlobalSettings settings;
private Picasso picasso;
private OnListClickListener listener;
/**
* @param parent Parent view from adapter
*/
public UserlistHolder(ViewGroup parent, GlobalSettings settings) {
public UserlistHolder(ViewGroup parent, GlobalSettings settings, Picasso picasso, OnListClickListener listener) {
super(LayoutInflater.from(parent.getContext()).inflate(R.layout.item_list, parent, false));
CardView background = (CardView) itemView;
ViewGroup container = itemView.findViewById(R.id.item_list_container);
@ -43,8 +63,90 @@ public class UserlistHolder extends ViewHolder {
member = itemView.findViewById(R.id.item_list_member);
subscriber = itemView.findViewById(R.id.item_list_subscriber);
followList = itemView.findViewById(R.id.item_list_following_indicator);
this.settings = settings;
this.picasso = picasso;
this.listener = listener;
AppStyles.setTheme(container, 0);
background.setCardBackgroundColor(settings.getCardColor());
itemView.setOnClickListener(this);
profile.setOnClickListener(this);
}
@Override
public void onClick(View v) {
int position = getLayoutPosition();
if (position != NO_POSITION) {
if (v == itemView) {
listener.onUserlistClick(position, OnListClickListener.LIST_CLICK);
} else if (v == profile) {
listener.onUserlistClick(position, OnListClickListener.PROFILE_CLICK);
}
}
}
/**
* set view content
*/
public void setContent(UserList userlist) {
User owner = userlist.getListOwner();
title.setText(userlist.getTitle());
description.setText(userlist.getDescription());
username.setText(owner.getUsername());
screenname.setText(owner.getScreenname());
date.setText(StringTools.formatCreationTime(itemView.getResources(), userlist.getTimestamp()));
member.setText(NUM_FORMAT.format(userlist.getMemberCount()));
subscriber.setText(NUM_FORMAT.format(userlist.getSubscriberCount()));
if (settings.imagesEnabled() && !owner.getImageUrl().isEmpty()) {
String profileImageUrl;
if (!owner.hasDefaultProfileImage()) {
profileImageUrl = StringTools.buildImageLink(owner.getImageUrl(), settings.getImageSuffix());
} else {
profileImageUrl = owner.getImageUrl();
}
picasso.load(profileImageUrl).transform(new RoundedCornersTransformation(3, 0)).error(R.drawable.no_image).into(profile);
} else {
profile.setImageResource(0);
}
if (!userlist.getListOwner().isCurrentUser() && userlist.isFollowing()) {
follow.setVisibility(View.VISIBLE);
followList.setVisibility(View.VISIBLE);
} else {
follow.setVisibility(View.GONE);
followList.setVisibility(View.GONE);
}
if (owner.isVerified()) {
verified.setVisibility(View.VISIBLE);
} else {
verified.setVisibility(View.GONE);
}
if (owner.isProtected()) {
locked.setVisibility(View.VISIBLE);
} else {
locked.setVisibility(View.GONE);
}
if (userlist.isPrivate()) {
privateList.setVisibility(View.VISIBLE);
} else {
privateList.setVisibility(View.GONE);
}
}
/**
* item click listener
*/
public interface OnListClickListener {
int LIST_CLICK = 1;
int PROFILE_CLICK = 2;
/**
* @param position position of the item
* @param type type of click
*/
void onUserlistClick(int position, int type);
}
}

View File

@ -247,15 +247,6 @@ public interface Connection {
*/
List<Status> getHomeTimeline(long minId, long maxId) throws ConnectionException;
/**
* show current user's home timeline
*
* @param minId get statuses with ID above the min ID
* @param maxId get statuses with ID under the max ID
* @return list of statuses
*/
List<Status> getMentionTimeline(long minId, long maxId) throws ConnectionException;
/**
* show the timeline of an user
*

View File

@ -392,28 +392,6 @@ public class Mastodon implements Connection {
}
@Override
public List<Status> getMentionTimeline(long minId, long maxId) throws MastodonException {
List<String> params = new ArrayList<>();
params.add("since_id=" + minId);
params.add("max_id=" + maxId);
params.add("limit=" + settings.getListSize());
params.add("types[]=mention");
try {
List<Notification> notifications = createNotifications(get(ENDPOINT_NOTIFICATION, params));
List<Status> mentions = new ArrayList<>(notifications.size());
for (Notification notification : notifications) {
if (notification.getType() == Notification.TYPE_MENTION) {
mentions.add(notification.getStatus());
}
}
return mentions;
} catch (IOException e) {
throw new MastodonException(e);
}
}
@Override
public List<Status> getUserTimeline(long id, long minId, long maxId) throws MastodonException {
String endpoint = ENDPOINT_USER_TIMELINE + id + "/statuses";
@ -733,8 +711,10 @@ public class Mastodon implements Connection {
@Override
public List<Notification> getNotifications(long minId, long maxId) throws ConnectionException {
List<String> params = new ArrayList<>();
params.add("since_id=" + minId);
params.add("max_id=" + maxId);
if (minId > 0)
params.add("since_id=" + minId);
if (maxId > minId)
params.add("max_id=" + maxId);
params.add("limit=" + settings.getListSize());
try {
return createNotifications(get(ENDPOINT_NOTIFICATION, params));

View File

@ -42,7 +42,7 @@ public class MastodonUser implements User {
*/
public MastodonUser(JSONObject json) throws JSONException {
String idStr = json.getString("id");
screenname = json.optString("acct", "");
screenname = '@' + json.optString("acct", "");
username = json.optString("display_name");
createdAt = StringTools.getTime(json.optString("created_at", ""), StringTools.TIME_MASTODON);
profileUrl = json.optString("avatar");

View File

@ -558,17 +558,6 @@ public class Twitter implements Connection {
}
@Override
public List<Status> getMentionTimeline(long minId, long maxId) throws TwitterException {
List<String> params = new ArrayList<>();
if (minId > 0)
params.add("since_id=" + minId);
if (maxId > 1)
params.add("max_id=" + maxId);
return getTweets1(TWEETS_MENTIONS, params);
}
@Override
public List<Status> getUserTimeline(long id, long minId, long maxId) throws TwitterException {
List<String> params = new ArrayList<>();
@ -1146,7 +1135,13 @@ public class Twitter implements Connection {
@Override
public List<Notification> getNotifications(long minId, long maxId) throws ConnectionException {
List<Status> mentions = getMentionTimeline(minId, maxId);
List<String> params = new ArrayList<>();
if (minId > 0)
params.add("since_id=" + minId);
if (maxId > 1)
params.add("max_id=" + maxId);
params.add("count=" + settings.getListSize());
List<Status> mentions = getTweets1(TWEETS_MENTIONS, params);
List<Notification> result = new ArrayList<>(mentions.size());
for (Status status : mentions) {
result.add(new TwitterNotification(status));

View File

@ -0,0 +1,61 @@
package org.nuclearfog.twidda.backend.async;
import android.os.AsyncTask;
import androidx.annotation.Nullable;
import org.nuclearfog.twidda.backend.api.Connection;
import org.nuclearfog.twidda.backend.api.ConnectionException;
import org.nuclearfog.twidda.backend.api.ConnectionManager;
import org.nuclearfog.twidda.model.Notification;
import org.nuclearfog.twidda.ui.fragments.NotificationFragment;
import java.lang.ref.WeakReference;
import java.util.List;
/**
* Notification loader for {@link NotificationFragment}
*
* @author nuclearfog
*/
public class NotificationLoader extends AsyncTask<Long, Void, List<Notification>> {
private WeakReference<NotificationFragment> callback;
private Connection connection;
@Nullable
private ConnectionException exception;
private int pos;
public NotificationLoader(NotificationFragment fragment, int pos) {
super();
callback = new WeakReference<>(fragment);
connection = ConnectionManager.get(fragment.getContext());
this.pos = pos;
}
@Override
protected List<Notification> doInBackground(Long... ids) {
try {
return connection.getNotifications(ids[0], ids[1]);
} catch (ConnectionException exception) {
this.exception = exception;
}
return null;
}
@Override
protected void onPostExecute(List<Notification> notifications) {
NotificationFragment fragment = callback.get();
if (fragment != null) {
if (notifications != null) {
fragment.onSuccess(notifications, pos);
} else {
fragment.onError(exception);
}
}
}
}

View File

@ -27,12 +27,7 @@ public class StatusLoader extends AsyncTask<Long, Void, List<Status>> {
/**
* home timeline
*/
public static final int HOME = 1;
/**
* mention timeline
*/
public static final int MENTION = 2;
public static final int HOME = 2;
/**
* user timeline
@ -117,21 +112,6 @@ public class StatusLoader extends AsyncTask<Long, Void, List<Status>> {
}
break;
case MENTION:
if (sinceId == 0 && maxId == 0) {
statuses = db.getMentionTimeline();
if (statuses.isEmpty()) {
statuses = connection.getMentionTimeline(sinceId, maxId);
db.saveMentionTimeline(statuses);
}
} else if (sinceId > 0) {
statuses = connection.getMentionTimeline(sinceId, maxId);
db.saveMentionTimeline(statuses);
} else if (maxId > 1) {
statuses = connection.getMentionTimeline(sinceId, maxId);
}
break;
case USER:
if (id > 0) {
if (sinceId == 0 && maxId == 0) {

View File

@ -44,7 +44,6 @@ public class AppDatabase {
public static final int FAV_MASK = 1; // status is favorited by user
public static final int RTW_MASK = 1 << 1; // status is reposted by user
public static final int HOM_MASK = 1 << 2; // status is from home timeline
public static final int MEN_MASK = 1 << 3; // status is from mention timeline
public static final int UTW_MASK = 1 << 4; // status is from an users timeline
public static final int RPL_MASK = 1 << 5; // status is from a reply timeline
public static final int MEDIA_IMAGE_MASK = 1 << 6; // status contains images
@ -89,17 +88,6 @@ public class AppDatabase {
+ " ORDER BY " + StatusTable.ID
+ " DESC LIMIT ?";
/**
* SQL query to get mention timeline
*/
static final String MENTION_QUERY = "SELECT * FROM " + STATUS_TABLE
+ " WHERE " + StatusRegisterTable.NAME + "." + StatusRegisterTable.REGISTER + "&" + MEN_MASK + " IS NOT 0"
+ " AND " + UserRegisterTable.NAME + "." + UserRegisterTable.REGISTER + "&" + EXCL_USR + " IS 0"
+ " AND " + StatusRegisterTable.NAME + "." + StatusRegisterTable.OWNER + "=?"
+ " AND " + UserRegisterTable.NAME + "." + UserRegisterTable.OWNER + "=?"
+ " ORDER BY " + StatusTable.ID
+ " DESC LIMIT ?";
/**
* SQL query to get status of an user
*/
@ -258,18 +246,6 @@ public class AppDatabase {
commit(db);
}
/**
* save mention timeline
*
* @param mentions status
*/
public void saveMentionTimeline(List<Status> mentions) {
SQLiteDatabase db = getDbWrite();
for (Status status : mentions)
saveStatus(status, MEN_MASK, db);
commit(db);
}
/**
* save user timeline
*
@ -379,29 +355,6 @@ public class AppDatabase {
return result;
}
/**
* load mention timeline
*
* @return mention timeline
*/
public List<Status> getMentionTimeline() {
String homeStr = Long.toString(settings.getLogin().getId());
String[] args = {homeStr, homeStr, Integer.toString(settings.getListSize())};
SQLiteDatabase db = getDbRead();
List<Status> result = new LinkedList<>();
Cursor cursor = db.rawQuery(MENTION_QUERY, args);
if (cursor.moveToFirst()) {
do
{
Status status = getStatus(cursor);
result.add(status);
} while (cursor.moveToNext());
}
cursor.close();
return result;
}
/**
* load user timeline
*

View File

@ -0,0 +1,115 @@
package org.nuclearfog.twidda.ui.fragments;
import android.os.Bundle;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.nuclearfog.twidda.adapter.NotificationAdapter;
import org.nuclearfog.twidda.adapter.NotificationAdapter.OnNotificationClickListener;
import org.nuclearfog.twidda.backend.api.ConnectionException;
import org.nuclearfog.twidda.backend.async.NotificationLoader;
import org.nuclearfog.twidda.backend.utils.ErrorHandler;
import org.nuclearfog.twidda.model.Notification;
import org.nuclearfog.twidda.model.Status;
import org.nuclearfog.twidda.model.User;
import java.util.List;
/**
* fragment to show notifications
*
* @author nuclearfog
*/
public class NotificationFragment extends ListFragment implements OnNotificationClickListener {
private NotificationLoader notificationAsync;
private NotificationAdapter adapter;
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
adapter = new NotificationAdapter(requireContext(), this);
setAdapter(adapter);
}
@Override
public void onStart() {
super.onStart();
if (notificationAsync == null) {
load(0L, 0L, 0);
}
}
@Override
protected void onReload() {
long sinceId = 0;
if (!adapter.isEmpty())
sinceId = adapter.getItemId(0);
load(sinceId, 0L, 0);
}
@Override
protected void onReset() {
adapter = new NotificationAdapter(requireContext(), this);
setAdapter(adapter);
load(0L, 0L, 0);
}
@Override
public void onStatusClick(Status status) {
if (!isRefreshing()) {
// todo add implementation
}
}
@Override
public void onUserClick(User user) {
if (!isRefreshing()) {
// todo add implementation
}
}
@Override
public boolean onPlaceholderClick(long sinceId, long maxId, long position) {
return false;
}
/**
* called from {@link NotificationLoader} when notifications were loaded successfully
*
* @param notifications new items
* @param position index where to insert the new items
*/
public void onSuccess(List<Notification> notifications, int position) {
adapter.addItems(notifications, position);
setRefresh(false);
}
/**
* called from {@link NotificationLoader} if an error occurs
*/
public void onError(@Nullable ConnectionException exception) {
ErrorHandler.handleFailure(requireContext(), exception);
adapter.disableLoading();
setRefresh(false);
}
/**
* @param minId lowest notification ID to load
* @param maxId highest notification Id to load
* @param pos index to insert the new items
*/
private void load(long minId, long maxId, int pos) {
notificationAsync = new NotificationLoader(this, pos);
notificationAsync.execute(minId, maxId);
}
}

View File

@ -53,13 +53,6 @@ public class StatusFragment extends ListFragment implements StatusSelectListener
*/
public static final int STATUS_FRAGMENT_HOME = 0xE7028B60;
/**
* setup list for mention timeline
*
* @see #KEY_STATUS_FRAGMENT_MODE
*/
public static final int STATUS_FRAGMENT_MENTION = 0x9EC8274D;
/**
* setup list for status timeline of a specific user
*
@ -131,7 +124,7 @@ public class StatusFragment extends ListFragment implements StatusSelectListener
public void onStart() {
super.onStart();
if (statusAsync == null) {
load(0, 0, CLEAR_LIST);
load(0L, 0L, CLEAR_LIST);
setRefresh(true);
}
}
@ -141,7 +134,7 @@ public class StatusFragment extends ListFragment implements StatusSelectListener
protected void onReset() {
adapter = new StatusAdapter(requireContext(), this);
setAdapter(adapter);
load(0, 0, CLEAR_LIST);
load(0L, 0L, CLEAR_LIST);
setRefresh(true);
}
@ -178,7 +171,7 @@ public class StatusFragment extends ListFragment implements StatusSelectListener
long sinceId = 0;
if (!adapter.isEmpty())
sinceId = adapter.getItemId(0);
load(sinceId, 0, 0);
load(sinceId, 0L, 0);
}
@ -239,11 +232,6 @@ public class StatusFragment extends ListFragment implements StatusSelectListener
statusAsync.execute(sinceId, maxId);
break;
case STATUS_FRAGMENT_MENTION:
statusAsync = new StatusLoader(this, StatusLoader.MENTION, id, search, index);
statusAsync.execute(sinceId, maxId);
break;
case STATUS_FRAGMENT_USER:
statusAsync = new StatusLoader(this, StatusLoader.USER, id, search, index);
statusAsync.execute(sinceId, maxId);

View File

@ -12,13 +12,26 @@
android:layout_height="match_parent"
android:padding="@dimen/item_status_layout_padding">
<TextView
android:id="@+id/item_status_label"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:lines="1"
android:drawablePadding="@dimen/item_status_drawable_margin"
android:textSize="@dimen/item_status_textsize_notification"
android:visibility="gone"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
<ImageView
android:id="@+id/item_status_profile_image"
android:layout_width="@dimen/item_status_profile_size"
android:layout_height="@dimen/item_status_profile_size"
android:contentDescription="@string/profile_image"
android:layout_marginTop="@dimen/item_status_image_margin"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
app:layout_constraintTop_toBottomOf="@id/item_status_label" />
<ImageView
android:id="@+id/item_status_verified_icon"

View File

@ -10,23 +10,43 @@
android:id="@+id/item_user_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="@dimen/useritem_layout_padding"
android:padding="@dimen/item_user_layout_padding"
android:gravity="center_vertical">
<TextView
android:id="@+id/item_user_label"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:lines="1"
android:drawablePadding="@dimen/item_user_drawable_margin"
android:visibility="gone"
android:textSize="@dimen/item_user_textsize_notification"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
<androidx.constraintlayout.widget.Barrier
android:id="@+id/item_user_notification_barrier"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:barrierDirection="top"
app:constraint_referenced_ids="item_user_profile,item_user_verified, item_user_username" />
<ImageView
android:id="@+id/item_user_profile"
android:layout_width="@dimen/useritem_image_size"
android:layout_height="@dimen/useritem_image_size"
android:layout_width="@dimen/item_user_image_size"
android:layout_height="@dimen/item_user_image_size"
android:contentDescription="@string/profile_image"
android:layout_marginTop="@dimen/item_user_image_margin"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintTop_toBottomOf="@id/item_user_label"
app:layout_constraintBottom_toBottomOf="parent" />
<ImageView
android:id="@+id/item_user_verified"
android:layout_width="@dimen/useritem_icon_size"
android:layout_height="@dimen/useritem_icon_size"
android:layout_marginStart="@dimen/useritem_drawable_margin"
android:layout_width="@dimen/item_user_icon_size"
android:layout_height="@dimen/item_user_icon_size"
android:layout_marginStart="@dimen/item_user_drawable_margin"
android:src="@drawable/verify"
app:layout_constraintStart_toEndOf="@id/item_user_profile"
app:layout_constraintTop_toTopOf="@id/item_user_username"
@ -38,19 +58,19 @@
android:id="@+id/item_user_username"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:drawablePadding="@dimen/useritem_drawable_margin"
android:layout_marginStart="@dimen/useritem_textview_padding"
android:layout_marginEnd="@dimen/useritem_textview_padding"
android:drawablePadding="@dimen/item_user_drawable_margin"
android:layout_marginStart="@dimen/item_user_textview_padding"
android:layout_marginEnd="@dimen/item_user_textview_padding"
android:singleLine="true"
app:layout_constraintStart_toEndOf="@id/item_user_verified"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintTop_toTopOf="@id/item_user_profile"
app:layout_constraintEnd_toStartOf="@id/item_user_delete_buton" />
<ImageView
android:id="@+id/item_user_private"
android:layout_width="@dimen/useritem_icon_size"
android:layout_height="@dimen/useritem_icon_size"
android:layout_marginStart="@dimen/useritem_drawable_margin"
android:layout_width="@dimen/item_user_icon_size"
android:layout_height="@dimen/item_user_icon_size"
android:layout_marginStart="@dimen/item_user_drawable_margin"
android:src="@drawable/lock"
app:layout_constraintStart_toEndOf="@id/item_user_profile"
app:layout_constraintTop_toTopOf="@id/item_user_screenname"
@ -63,9 +83,9 @@
android:id="@+id/item_user_screenname"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:drawablePadding="@dimen/useritem_drawable_margin"
android:layout_marginStart="@dimen/useritem_textview_padding"
android:layout_marginEnd="@dimen/useritem_textview_padding"
android:drawablePadding="@dimen/item_user_drawable_margin"
android:layout_marginStart="@dimen/item_user_textview_padding"
android:layout_marginEnd="@dimen/item_user_textview_padding"
android:singleLine="true"
app:layout_constraintStart_toEndOf="@id/item_user_private"
app:layout_constraintTop_toBottomOf="@id/item_user_username"
@ -73,10 +93,10 @@
<ImageView
android:id="@+id/item_user_following_icon"
android:layout_width="@dimen/useritem_icon_size"
android:layout_height="@dimen/useritem_icon_size"
android:layout_marginStart="@dimen/useritem_drawable_margin"
android:layout_marginEnd="@dimen/useritem_drawable_margin"
android:layout_width="@dimen/item_user_icon_size"
android:layout_height="@dimen/item_user_icon_size"
android:layout_marginStart="@dimen/item_user_drawable_margin"
android:layout_marginEnd="@dimen/item_user_drawable_margin"
android:src="@drawable/following"
app:layout_constraintStart_toEndOf="@id/item_user_profile"
app:layout_constraintTop_toBottomOf="@id/item_user_screenname"
@ -88,9 +108,9 @@
android:id="@+id/item_user_following_count"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:drawablePadding="@dimen/useritem_drawable_margin"
android:drawablePadding="@dimen/item_user_drawable_margin"
android:singleLine="true"
android:textSize="@dimen/useritem_textsize_small"
android:textSize="@dimen/item_user_textsize_small"
app:layout_constraintStart_toEndOf="@id/item_user_following_icon"
app:layout_constraintTop_toTopOf="@id/item_user_following_icon"
app:layout_constraintBottom_toBottomOf="@id/item_user_following_icon"
@ -98,10 +118,10 @@
<ImageView
android:id="@+id/item_user_follower_icon"
android:layout_width="@dimen/useritem_icon_size"
android:layout_height="@dimen/useritem_icon_size"
android:layout_marginStart="@dimen/useritem_drawable_margin"
android:layout_marginEnd="@dimen/useritem_drawable_margin"
android:layout_width="@dimen/item_user_icon_size"
android:layout_height="@dimen/item_user_icon_size"
android:layout_marginStart="@dimen/item_user_drawable_margin"
android:layout_marginEnd="@dimen/item_user_drawable_margin"
android:src="@drawable/follower"
app:layout_constraintStart_toEndOf="@id/item_user_following_count"
app:layout_constraintTop_toBottomOf="@id/item_user_screenname"
@ -113,10 +133,10 @@
android:id="@+id/item_user_follower_count"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:drawablePadding="@dimen/useritem_drawable_margin"
android:layout_marginEnd="@dimen/useritem_textview_padding"
android:drawablePadding="@dimen/item_user_drawable_margin"
android:layout_marginEnd="@dimen/item_user_textview_padding"
android:singleLine="true"
android:textSize="@dimen/useritem_textsize_small"
android:textSize="@dimen/item_user_textsize_small"
app:layout_constraintStart_toEndOf="@id/item_user_follower_icon"
app:layout_constraintTop_toTopOf="@id/item_user_follower_icon"
app:layout_constraintBottom_toBottomOf="@id/item_user_follower_icon"
@ -124,14 +144,14 @@
<ImageButton
android:id="@+id/item_user_delete_buton"
android:layout_width="@dimen/useritem_button_size"
android:layout_height="@dimen/useritem_button_size"
android:layout_width="@dimen/item_user_button_size"
android:layout_height="@dimen/item_user_button_size"
android:visibility="invisible"
android:padding="@dimen/useritem_button_padding"
android:padding="@dimen/item_user_button_padding"
android:contentDescription="@string/descr_remove_user"
android:scaleType="fitCenter"
android:src="@drawable/cross"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintTop_toBottomOf="@id/item_user_label"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
style="@style/RoundButton" />

View File

@ -73,7 +73,9 @@
<dimen name="item_status_profile_size">36sp</dimen>
<dimen name="item_status_layout_padding">5dp</dimen>
<dimen name="item_status_text_margin">5dp</dimen>
<dimen name="item_status_image_margin">8dp</dimen>
<dimen name="item_status_drawable_margin">5dp</dimen>
<dimen name="item_status_textsize_notification">12sp</dimen>
<dimen name="item_status_textsize_button">12sp</dimen>
<dimen name="item_status_textsize_date">12sp</dimen>
<dimen name="item_status_icon_size">14sp</dimen>
@ -90,14 +92,16 @@
<dimen name="trenditem_textsize_trendindex_width">40sp</dimen>
<!--dimens of item_user.xml-->
<dimen name="useritem_image_size">56sp</dimen>
<dimen name="useritem_layout_padding">5dp</dimen>
<dimen name="useritem_textview_padding">5dp</dimen>
<dimen name="useritem_drawable_margin">5dp</dimen>
<dimen name="useritem_button_size">36dp</dimen>
<dimen name="useritem_button_padding">7dp</dimen>
<dimen name="useritem_textsize_small">12sp</dimen>
<dimen name="useritem_icon_size">14sp</dimen>
<dimen name="item_user_image_size">56sp</dimen>
<dimen name="item_user_layout_padding">5dp</dimen>
<dimen name="item_user_textview_padding">5dp</dimen>
<dimen name="item_user_drawable_margin">5dp</dimen>
<dimen name="item_user_button_size">36dp</dimen>
<dimen name="item_user_button_padding">7dp</dimen>
<dimen name="item_user_textsize_small">12sp</dimen>
<dimen name="item_user_icon_size">14sp</dimen>
<dimen name="item_user_image_margin">8dp</dimen>
<dimen name="item_user_textsize_notification">12sp</dimen>
<!--dimens of item_list.xml-->
<dimen name="listitem_padding">5dp</dimen>

View File

@ -279,5 +279,10 @@
<string name="toolbar_tweet_liker">User liking this status</string>
<string name="time_now">now</string>
<string name="confirm_open_link">open in browser</string>
<string name="info_user_favorited">%1$s favorited your status</string>
<string name="info_user_follow">%1$s followed you</string>
<string name="info_user_follow_request">%1$s request to follow you</string>
<string name="info_user_mention">%1$s mentioned you</string>
<string name="info_user_repost">%1$s reposted your status</string>
</resources>

View File

@ -81,8 +81,8 @@
<style name="CardViewStyle" parent="CardView">
<item name="cardPreventCornerOverlap">false</item>
<item name="cardUseCompatPadding">true</item>
<item name="cardCornerRadius">2dp</item>
<item name="cardElevation">2dp</item>
<item name="cardCornerRadius">1dp</item>
<item name="cardElevation">1dp</item>
</style>
</resources>