From 0aa0a51912c1cc8a5d8646747d276ca58f28fa9c Mon Sep 17 00:00:00 2001 From: nuclearfog Date: Thu, 1 Dec 2022 23:03:10 +0100 Subject: [PATCH] implemented notification fragment, restructured adapter and holder, added notification to user and status item layout --- .../twidda/adapter/FragmentAdapter.java | 7 +- .../twidda/adapter/NotificationAdapter.java | 286 ++++++++++++++++++ .../twidda/adapter/StatusAdapter.java | 263 +++++----------- .../twidda/adapter/TrendAdapter.java | 44 +-- .../twidda/adapter/UserAdapter.java | 126 +++----- .../twidda/adapter/UserlistAdapter.java | 131 ++------ .../twidda/adapter/holder/PlaceHolder.java | 54 +++- .../twidda/adapter/holder/StatusHolder.java | 204 ++++++++++++- .../twidda/adapter/holder/TrendHolder.java | 68 ++++- .../twidda/adapter/holder/UserHolder.java | 144 ++++++++- .../twidda/adapter/holder/UserlistHolder.java | 106 ++++++- .../twidda/backend/api/Connection.java | 9 - .../twidda/backend/api/mastodon/Mastodon.java | 28 +- .../api/mastodon/impl/MastodonUser.java | 2 +- .../twidda/backend/api/twitter/Twitter.java | 19 +- .../backend/async/NotificationLoader.java | 61 ++++ .../twidda/backend/async/StatusLoader.java | 22 +- .../twidda/database/AppDatabase.java | 47 --- .../ui/fragments/NotificationFragment.java | 115 +++++++ .../twidda/ui/fragments/StatusFragment.java | 18 +- app/src/main/res/layout/item_status.xml | 15 +- app/src/main/res/layout/item_user.xml | 88 +++--- app/src/main/res/values/dimens.xml | 20 +- app/src/main/res/values/strings.xml | 5 + app/src/main/res/values/styles.xml | 4 +- 25 files changed, 1283 insertions(+), 603 deletions(-) create mode 100644 app/src/main/java/org/nuclearfog/twidda/adapter/NotificationAdapter.java create mode 100644 app/src/main/java/org/nuclearfog/twidda/backend/async/NotificationLoader.java create mode 100644 app/src/main/java/org/nuclearfog/twidda/ui/fragments/NotificationFragment.java diff --git a/app/src/main/java/org/nuclearfog/twidda/adapter/FragmentAdapter.java b/app/src/main/java/org/nuclearfog/twidda/adapter/FragmentAdapter.java index 7ec2164e..c9e364b9 100644 --- a/app/src/main/java/org/nuclearfog/twidda/adapter/FragmentAdapter.java +++ b/app/src/main/java/org/nuclearfog/twidda/adapter/FragmentAdapter.java @@ -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(); } diff --git a/app/src/main/java/org/nuclearfog/twidda/adapter/NotificationAdapter.java b/app/src/main/java/org/nuclearfog/twidda/adapter/NotificationAdapter.java new file mode 100644 index 00000000..c7e124a5 --- /dev/null +++ b/app/src/main/java/org/nuclearfog/twidda/adapter/NotificationAdapter.java @@ -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 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 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 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); + } +} \ No newline at end of file diff --git a/app/src/main/java/org/nuclearfog/twidda/adapter/StatusAdapter.java b/app/src/main/java/org/nuclearfog/twidda/adapter/StatusAdapter.java index da8f8855..83ab0704 100644 --- a/app/src/main/java/org/nuclearfog/twidda/adapter/StatusAdapter.java +++ b/app/src/main/java/org/nuclearfog/twidda/adapter/StatusAdapter.java @@ -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 { +public class StatusAdapter extends Adapter implements OnStatusClickListener, OnHolderClickListener { /** * index of {@link #loadingIndex} if no index is defined @@ -63,18 +51,12 @@ public class StatusAdapter extends Adapter { */ 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 statuses = new LinkedList<>(); + private final List items = new LinkedList<>(); private int loadingIndex = NO_LOADING; /** @@ -84,13 +66,12 @@ public class StatusAdapter extends Adapter { 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 { @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 { @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 { @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 { } } + + @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 statuses, int index) { + public void addItems(@NonNull List 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 statuses) { - this.statuses.clear(); - this.statuses.addAll(statuses); - if (statuses.size() > MIN_COUNT) { - this.statuses.add(null); + public void replaceItems(@NonNull List 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 { * @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 { * @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 { * @return true if list is empty */ public boolean isEmpty() { - return statuses.isEmpty(); + return items.isEmpty(); } /** diff --git a/app/src/main/java/org/nuclearfog/twidda/adapter/TrendAdapter.java b/app/src/main/java/org/nuclearfog/twidda/adapter/TrendAdapter.java index 16a0f7dc..1710da42 100644 --- a/app/src/main/java/org/nuclearfog/twidda/adapter/TrendAdapter.java +++ b/app/src/main/java/org/nuclearfog/twidda/adapter/TrendAdapter.java @@ -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 { +public class TrendAdapter extends Adapter 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 { @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 { 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)); } /** diff --git a/app/src/main/java/org/nuclearfog/twidda/adapter/UserAdapter.java b/app/src/main/java/org/nuclearfog/twidda/adapter/UserAdapter.java index e7886ba3..926d851a 100644 --- a/app/src/main/java/org/nuclearfog/twidda/adapter/UserAdapter.java +++ b/app/src/main/java/org/nuclearfog/twidda/adapter/UserAdapter.java @@ -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 { +public class UserAdapter extends Adapter implements OnUserClickListener, OnHolderClickListener { /** * index of {@link #loadingIndex} if no index is defined @@ -52,10 +43,7 @@ public class UserAdapter extends Adapter { */ 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 { @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 { 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 { } } + + @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 * diff --git a/app/src/main/java/org/nuclearfog/twidda/adapter/UserlistAdapter.java b/app/src/main/java/org/nuclearfog/twidda/adapter/UserlistAdapter.java index 1402e0f2..34a9985c 100644 --- a/app/src/main/java/org/nuclearfog/twidda/adapter/UserlistAdapter.java +++ b/app/src/main/java/org/nuclearfog/twidda/adapter/UserlistAdapter.java @@ -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 { +public class UserlistAdapter extends Adapter implements OnListClickListener, OnHolderClickListener { /** * indicator if there is no loading progress @@ -56,11 +45,9 @@ public class UserlistAdapter extends Adapter { /** * 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 { this.listener = listener; settings = GlobalSettings.getInstance(context); picasso = PicassoBuilder.get(context); - resources = context.getResources(); } @@ -96,47 +82,10 @@ public class UserlistAdapter extends Adapter { @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 { 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 { } } + + @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 * diff --git a/app/src/main/java/org/nuclearfog/twidda/adapter/holder/PlaceHolder.java b/app/src/main/java/org/nuclearfog/twidda/adapter/holder/PlaceHolder.java index 7ddc3889..41a7590f 100644 --- a/app/src/main/java/org/nuclearfog/twidda/adapter/holder/PlaceHolder.java +++ b/app/src/main/java/org/nuclearfog/twidda/adapter/holder/PlaceHolder.java @@ -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); + } } \ No newline at end of file diff --git a/app/src/main/java/org/nuclearfog/twidda/adapter/holder/StatusHolder.java b/app/src/main/java/org/nuclearfog/twidda/adapter/holder/StatusHolder.java index 1995b81c..e92cd7e9 100644 --- a/app/src/main/java/org/nuclearfog/twidda/adapter/holder/StatusHolder.java +++ b/app/src/main/java/org/nuclearfog/twidda/adapter/holder/StatusHolder.java @@ -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); } } \ No newline at end of file diff --git a/app/src/main/java/org/nuclearfog/twidda/adapter/holder/TrendHolder.java b/app/src/main/java/org/nuclearfog/twidda/adapter/holder/TrendHolder.java index 92dc354f..88437364 100644 --- a/app/src/main/java/org/nuclearfog/twidda/adapter/holder/TrendHolder.java +++ b/app/src/main/java/org/nuclearfog/twidda/adapter/holder/TrendHolder.java @@ -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); } } \ No newline at end of file diff --git a/app/src/main/java/org/nuclearfog/twidda/adapter/holder/UserHolder.java b/app/src/main/java/org/nuclearfog/twidda/adapter/holder/UserHolder.java index c6f4d971..64cf2fd1 100644 --- a/app/src/main/java/org/nuclearfog/twidda/adapter/holder/UserHolder.java +++ b/app/src/main/java/org/nuclearfog/twidda/adapter/holder/UserHolder.java @@ -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); } } \ No newline at end of file diff --git a/app/src/main/java/org/nuclearfog/twidda/adapter/holder/UserlistHolder.java b/app/src/main/java/org/nuclearfog/twidda/adapter/holder/UserlistHolder.java index c74f7907..00446b95 100644 --- a/app/src/main/java/org/nuclearfog/twidda/adapter/holder/UserlistHolder.java +++ b/app/src/main/java/org/nuclearfog/twidda/adapter/holder/UserlistHolder.java @@ -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); } } \ No newline at end of file diff --git a/app/src/main/java/org/nuclearfog/twidda/backend/api/Connection.java b/app/src/main/java/org/nuclearfog/twidda/backend/api/Connection.java index 7bb5ddb4..bb37f3f5 100644 --- a/app/src/main/java/org/nuclearfog/twidda/backend/api/Connection.java +++ b/app/src/main/java/org/nuclearfog/twidda/backend/api/Connection.java @@ -247,15 +247,6 @@ public interface Connection { */ List 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 getMentionTimeline(long minId, long maxId) throws ConnectionException; - /** * show the timeline of an user * diff --git a/app/src/main/java/org/nuclearfog/twidda/backend/api/mastodon/Mastodon.java b/app/src/main/java/org/nuclearfog/twidda/backend/api/mastodon/Mastodon.java index a7985e36..1b0d941e 100644 --- a/app/src/main/java/org/nuclearfog/twidda/backend/api/mastodon/Mastodon.java +++ b/app/src/main/java/org/nuclearfog/twidda/backend/api/mastodon/Mastodon.java @@ -392,28 +392,6 @@ public class Mastodon implements Connection { } - @Override - public List getMentionTimeline(long minId, long maxId) throws MastodonException { - List params = new ArrayList<>(); - params.add("since_id=" + minId); - params.add("max_id=" + maxId); - params.add("limit=" + settings.getListSize()); - params.add("types[]=mention"); - try { - List notifications = createNotifications(get(ENDPOINT_NOTIFICATION, params)); - List 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 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 getNotifications(long minId, long maxId) throws ConnectionException { List 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)); diff --git a/app/src/main/java/org/nuclearfog/twidda/backend/api/mastodon/impl/MastodonUser.java b/app/src/main/java/org/nuclearfog/twidda/backend/api/mastodon/impl/MastodonUser.java index 15185c49..fe7ed2ea 100644 --- a/app/src/main/java/org/nuclearfog/twidda/backend/api/mastodon/impl/MastodonUser.java +++ b/app/src/main/java/org/nuclearfog/twidda/backend/api/mastodon/impl/MastodonUser.java @@ -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"); diff --git a/app/src/main/java/org/nuclearfog/twidda/backend/api/twitter/Twitter.java b/app/src/main/java/org/nuclearfog/twidda/backend/api/twitter/Twitter.java index 699e5c38..51b1f5fd 100644 --- a/app/src/main/java/org/nuclearfog/twidda/backend/api/twitter/Twitter.java +++ b/app/src/main/java/org/nuclearfog/twidda/backend/api/twitter/Twitter.java @@ -558,17 +558,6 @@ public class Twitter implements Connection { } - @Override - public List getMentionTimeline(long minId, long maxId) throws TwitterException { - List 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 getUserTimeline(long id, long minId, long maxId) throws TwitterException { List params = new ArrayList<>(); @@ -1146,7 +1135,13 @@ public class Twitter implements Connection { @Override public List getNotifications(long minId, long maxId) throws ConnectionException { - List mentions = getMentionTimeline(minId, maxId); + List 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 mentions = getTweets1(TWEETS_MENTIONS, params); List result = new ArrayList<>(mentions.size()); for (Status status : mentions) { result.add(new TwitterNotification(status)); diff --git a/app/src/main/java/org/nuclearfog/twidda/backend/async/NotificationLoader.java b/app/src/main/java/org/nuclearfog/twidda/backend/async/NotificationLoader.java new file mode 100644 index 00000000..5abf091b --- /dev/null +++ b/app/src/main/java/org/nuclearfog/twidda/backend/async/NotificationLoader.java @@ -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> { + + private WeakReference 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 doInBackground(Long... ids) { + try { + return connection.getNotifications(ids[0], ids[1]); + } catch (ConnectionException exception) { + this.exception = exception; + } + return null; + } + + + @Override + protected void onPostExecute(List notifications) { + NotificationFragment fragment = callback.get(); + if (fragment != null) { + if (notifications != null) { + fragment.onSuccess(notifications, pos); + } else { + fragment.onError(exception); + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/org/nuclearfog/twidda/backend/async/StatusLoader.java b/app/src/main/java/org/nuclearfog/twidda/backend/async/StatusLoader.java index fbd68833..a01c9fe8 100644 --- a/app/src/main/java/org/nuclearfog/twidda/backend/async/StatusLoader.java +++ b/app/src/main/java/org/nuclearfog/twidda/backend/async/StatusLoader.java @@ -27,12 +27,7 @@ public class StatusLoader extends AsyncTask> { /** * 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> { } 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) { diff --git a/app/src/main/java/org/nuclearfog/twidda/database/AppDatabase.java b/app/src/main/java/org/nuclearfog/twidda/database/AppDatabase.java index 5d283d71..a8c0d1cc 100644 --- a/app/src/main/java/org/nuclearfog/twidda/database/AppDatabase.java +++ b/app/src/main/java/org/nuclearfog/twidda/database/AppDatabase.java @@ -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 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 getMentionTimeline() { - String homeStr = Long.toString(settings.getLogin().getId()); - String[] args = {homeStr, homeStr, Integer.toString(settings.getListSize())}; - - SQLiteDatabase db = getDbRead(); - List 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 * diff --git a/app/src/main/java/org/nuclearfog/twidda/ui/fragments/NotificationFragment.java b/app/src/main/java/org/nuclearfog/twidda/ui/fragments/NotificationFragment.java new file mode 100644 index 00000000..049352fc --- /dev/null +++ b/app/src/main/java/org/nuclearfog/twidda/ui/fragments/NotificationFragment.java @@ -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 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); + } +} \ No newline at end of file diff --git a/app/src/main/java/org/nuclearfog/twidda/ui/fragments/StatusFragment.java b/app/src/main/java/org/nuclearfog/twidda/ui/fragments/StatusFragment.java index c748e7e6..e4fcf619 100644 --- a/app/src/main/java/org/nuclearfog/twidda/ui/fragments/StatusFragment.java +++ b/app/src/main/java/org/nuclearfog/twidda/ui/fragments/StatusFragment.java @@ -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); diff --git a/app/src/main/res/layout/item_status.xml b/app/src/main/res/layout/item_status.xml index cc13f63b..fe15ca71 100644 --- a/app/src/main/res/layout/item_status.xml +++ b/app/src/main/res/layout/item_status.xml @@ -12,13 +12,26 @@ android:layout_height="match_parent" android:padding="@dimen/item_status_layout_padding"> + + + app:layout_constraintTop_toBottomOf="@id/item_status_label" /> + + + + diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index 9e18e442..1d044395 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -73,7 +73,9 @@ 36sp 5dp 5dp + 8dp 5dp + 12sp 12sp 12sp 14sp @@ -90,14 +92,16 @@ 40sp - 56sp - 5dp - 5dp - 5dp - 36dp - 7dp - 12sp - 14sp + 56sp + 5dp + 5dp + 5dp + 36dp + 7dp + 12sp + 14sp + 8dp + 12sp 5dp diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index ac16e1af..ff443537 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -279,5 +279,10 @@ User liking this status now open in browser + %1$s favorited your status + %1$s followed you + %1$s request to follow you + %1$s mentioned you + %1$s reposted your status \ No newline at end of file diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index f9ea76a5..bc69d226 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -81,8 +81,8 @@ \ No newline at end of file