mirror of
https://github.com/KokaKiwi/BarInsta
synced 2024-11-08 15:57:29 +00:00
Allow forwarding messages (need to check types which cannot be forwarded)
This commit is contained in:
parent
8e3d0af9d3
commit
8a659c9f1f
@ -6,80 +6,146 @@ import android.view.ViewGroup;
|
|||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.recyclerview.widget.DiffUtil;
|
import androidx.recyclerview.widget.DiffUtil;
|
||||||
import androidx.recyclerview.widget.ListAdapter;
|
import androidx.recyclerview.widget.ListAdapter;
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import awais.instagrabber.adapters.DirectUsersAdapter.OnDirectUserClickListener;
|
import awais.instagrabber.adapters.DirectUsersAdapter.OnDirectUserClickListener;
|
||||||
import awais.instagrabber.adapters.viewholder.directmessages.DirectUserViewHolder;
|
import awais.instagrabber.adapters.viewholder.directmessages.DirectUserViewHolder;
|
||||||
|
import awais.instagrabber.adapters.viewholder.directmessages.RecipientThreadViewHolder;
|
||||||
import awais.instagrabber.databinding.LayoutDmUserItemBinding;
|
import awais.instagrabber.databinding.LayoutDmUserItemBinding;
|
||||||
import awais.instagrabber.repositories.responses.User;
|
import awais.instagrabber.repositories.responses.directmessages.RankedRecipient;
|
||||||
|
|
||||||
public final class UserSearchResultsAdapter extends ListAdapter<User, DirectUserViewHolder> {
|
public final class UserSearchResultsAdapter extends ListAdapter<RankedRecipient, RecyclerView.ViewHolder> {
|
||||||
|
private static final int VIEW_TYPE_USER = 0;
|
||||||
private static final DiffUtil.ItemCallback<User> DIFF_CALLBACK = new DiffUtil.ItemCallback<User>() {
|
private static final int VIEW_TYPE_THREAD = 1;
|
||||||
|
private static final DiffUtil.ItemCallback<RankedRecipient> DIFF_CALLBACK = new DiffUtil.ItemCallback<RankedRecipient>() {
|
||||||
@Override
|
@Override
|
||||||
public boolean areItemsTheSame(@NonNull final User oldItem, @NonNull final User newItem) {
|
public boolean areItemsTheSame(@NonNull final RankedRecipient oldItem, @NonNull final RankedRecipient newItem) {
|
||||||
return oldItem.getPk() == newItem.getPk();
|
final boolean bothUsers = oldItem.getUser() != null && newItem.getUser() != null;
|
||||||
|
if (!bothUsers) return false;
|
||||||
|
final boolean bothThreads = oldItem.getThread() != null && newItem.getThread() != null;
|
||||||
|
if (!bothThreads) return false;
|
||||||
|
if (bothUsers) {
|
||||||
|
return oldItem.getUser().getPk() == newItem.getUser().getPk();
|
||||||
|
}
|
||||||
|
return Objects.equals(oldItem.getThread().getThreadId(), newItem.getThread().getThreadId());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean areContentsTheSame(@NonNull final User oldItem, @NonNull final User newItem) {
|
public boolean areContentsTheSame(@NonNull final RankedRecipient oldItem, @NonNull final RankedRecipient newItem) {
|
||||||
return oldItem.getUsername().equals(newItem.getUsername()) &&
|
final boolean bothUsers = oldItem.getUser() != null && newItem.getUser() != null;
|
||||||
oldItem.getFullName().equals(newItem.getFullName());
|
if (bothUsers) {
|
||||||
|
return Objects.equals(oldItem.getUser().getUsername(), newItem.getUser().getUsername()) &&
|
||||||
|
Objects.equals(oldItem.getUser().getFullName(), newItem.getUser().getFullName());
|
||||||
|
}
|
||||||
|
return Objects.equals(oldItem.getThread().getThreadTitle(), newItem.getThread().getThreadTitle());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
private final boolean showSelection;
|
private final boolean showSelection;
|
||||||
private final Set<Long> selectedUserIds;
|
private final Set<RankedRecipient> selectedRecipients;
|
||||||
private final OnDirectUserClickListener onUserClickListener;
|
private final OnDirectUserClickListener onUserClickListener;
|
||||||
|
private final OnRecipientClickListener onRecipientClickListener;
|
||||||
|
|
||||||
public UserSearchResultsAdapter(final boolean showSelection,
|
public UserSearchResultsAdapter(final boolean showSelection,
|
||||||
final OnDirectUserClickListener onUserClickListener) {
|
final OnRecipientClickListener onRecipientClickListener) {
|
||||||
super(DIFF_CALLBACK);
|
super(DIFF_CALLBACK);
|
||||||
this.showSelection = showSelection;
|
this.showSelection = showSelection;
|
||||||
selectedUserIds = showSelection ? new HashSet<>() : null;
|
selectedRecipients = showSelection ? new HashSet<>() : null;
|
||||||
this.onUserClickListener = onUserClickListener;
|
this.onRecipientClickListener = onRecipientClickListener;
|
||||||
|
this.onUserClickListener = (position, user, selected) -> {
|
||||||
|
if (onRecipientClickListener != null) {
|
||||||
|
onRecipientClickListener.onClick(position, RankedRecipient.of(user), selected);
|
||||||
|
}
|
||||||
|
};
|
||||||
setHasStableIds(true);
|
setHasStableIds(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public DirectUserViewHolder onCreateViewHolder(@NonNull final ViewGroup parent, final int viewType) {
|
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull final ViewGroup parent, final int viewType) {
|
||||||
final LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext());
|
final LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext());
|
||||||
final LayoutDmUserItemBinding binding = LayoutDmUserItemBinding.inflate(layoutInflater, parent, false);
|
final LayoutDmUserItemBinding binding = LayoutDmUserItemBinding.inflate(layoutInflater, parent, false);
|
||||||
|
if (viewType == VIEW_TYPE_USER) {
|
||||||
return new DirectUserViewHolder(binding, onUserClickListener, null);
|
return new DirectUserViewHolder(binding, onUserClickListener, null);
|
||||||
|
}
|
||||||
|
return new RecipientThreadViewHolder(binding, onRecipientClickListener);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onBindViewHolder(@NonNull final DirectUserViewHolder holder, final int position) {
|
public void onBindViewHolder(@NonNull final RecyclerView.ViewHolder holder, final int position) {
|
||||||
final User user = getItem(position);
|
final RankedRecipient recipient = getItem(position);
|
||||||
boolean isSelected = selectedUserIds != null && selectedUserIds.contains(user.getPk());
|
final int itemViewType = getItemViewType(position);
|
||||||
holder.bind(position, user, false, false, showSelection, isSelected);
|
if (itemViewType == VIEW_TYPE_USER) {
|
||||||
|
boolean isSelected = false;
|
||||||
|
if (selectedRecipients != null) {
|
||||||
|
isSelected = selectedRecipients.stream()
|
||||||
|
.anyMatch(rankedRecipient -> rankedRecipient.getUser() != null
|
||||||
|
&& rankedRecipient.getUser().getPk() == recipient.getUser().getPk());
|
||||||
|
}
|
||||||
|
((DirectUserViewHolder) holder).bind(position, recipient.getUser(), false, false, showSelection, isSelected);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
boolean isSelected = false;
|
||||||
|
if (selectedRecipients != null) {
|
||||||
|
isSelected = selectedRecipients.stream()
|
||||||
|
.anyMatch(rankedRecipient -> rankedRecipient.getThread() != null
|
||||||
|
&& Objects.equals(rankedRecipient.getThread().getThreadId(), recipient.getThread().getThreadId()));
|
||||||
|
}
|
||||||
|
((RecipientThreadViewHolder) holder).bind(position, recipient.getThread(), showSelection, isSelected);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public long getItemId(final int position) {
|
public long getItemId(final int position) {
|
||||||
return getItem(position).getPk();
|
final RankedRecipient recipient = getItem(position);
|
||||||
|
if (recipient.getUser() != null) {
|
||||||
|
return recipient.getUser().getPk();
|
||||||
|
}
|
||||||
|
if (recipient.getThread() != null) {
|
||||||
|
return recipient.getThread().getThreadTitle().hashCode();
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setSelectedUser(final long userId, final boolean selected) {
|
@Override
|
||||||
if (selectedUserIds == null) return;
|
public int getItemViewType(final int position) {
|
||||||
|
final RankedRecipient recipient = getItem(position);
|
||||||
|
return recipient.getUser() != null ? VIEW_TYPE_USER : VIEW_TYPE_THREAD;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSelectedRecipient(final RankedRecipient recipient, final boolean selected) {
|
||||||
|
if (selectedRecipients == null || recipient == null || (recipient.getUser() == null && recipient.getThread() == null)) return;
|
||||||
|
final boolean isUser = recipient.getUser() != null;
|
||||||
int position = -1;
|
int position = -1;
|
||||||
final List<User> currentList = getCurrentList();
|
final List<RankedRecipient> currentList = getCurrentList();
|
||||||
for (int i = 0; i < currentList.size(); i++) {
|
for (int i = 0; i < currentList.size(); i++) {
|
||||||
if (currentList.get(i).getPk() == userId) {
|
final RankedRecipient temp = currentList.get(i);
|
||||||
|
if (isUser) {
|
||||||
|
if (temp.getUser() != null && temp.getUser().getPk() == recipient.getUser().getPk()) {
|
||||||
|
position = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (temp.getThread() != null && Objects.equals(temp.getThread().getThreadId(), recipient.getThread().getThreadId())) {
|
||||||
position = i;
|
position = i;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (position < 0) return;
|
if (position < 0) return;
|
||||||
if (selected) {
|
if (selected) {
|
||||||
selectedUserIds.add(userId);
|
selectedRecipients.add(recipient);
|
||||||
} else {
|
} else {
|
||||||
selectedUserIds.remove(userId);
|
selectedRecipients.remove(recipient);
|
||||||
}
|
}
|
||||||
notifyItemChanged(position);
|
notifyItemChanged(position);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public interface OnRecipientClickListener {
|
||||||
|
void onClick(int position, RankedRecipient recipient, final boolean isSelected);
|
||||||
|
}
|
||||||
}
|
}
|
@ -22,4 +22,9 @@ public class DirectItemLikeViewHolder extends DirectItemViewHolder {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void bindItem(final DirectItem directItemModel, final MessageDirection messageDirection) {}
|
public void bindItem(final DirectItem directItemModel, final MessageDirection messageDirection) {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean canForward() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -184,4 +184,9 @@ public class DirectItemRavenMediaViewHolder extends DirectItemViewHolder {
|
|||||||
final String thumbUrl = ResponseBodyUtils.getThumbUrl(imageVersions2);
|
final String thumbUrl = ResponseBodyUtils.getThumbUrl(imageVersions2);
|
||||||
binding.preview.setImageURI(thumbUrl);
|
binding.preview.setImageURI(thumbUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean allowLongClick() {
|
||||||
|
return false; // disabling until confirmed
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -167,4 +167,9 @@ public class DirectItemReelShareViewHolder extends DirectItemViewHolder {
|
|||||||
final String thumbUrl = ResponseBodyUtils.getThumbUrl(imageVersions2);
|
final String thumbUrl = ResponseBodyUtils.getThumbUrl(imageVersions2);
|
||||||
binding.preview.setImageURI(thumbUrl);
|
binding.preview.setImageURI(thumbUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean canForward() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -105,4 +105,9 @@ public class DirectItemStoryShareViewHolder extends DirectItemViewHolder {
|
|||||||
binding.ivMediaPreview.setVisibility(View.GONE);
|
binding.ivMediaPreview.setVisibility(View.GONE);
|
||||||
binding.typeIcon.setVisibility(View.GONE);
|
binding.typeIcon.setVisibility(View.GONE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean canForward() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -135,6 +135,9 @@ public abstract class DirectItemViewHolder extends RecyclerView.ViewHolder {
|
|||||||
}
|
}
|
||||||
setupReply(item, messageDirection);
|
setupReply(item, messageDirection);
|
||||||
setReactions(item, position);
|
setReactions(item, position);
|
||||||
|
if (item.getRepliedToMessage() == null && item.showForwardAttribution()) {
|
||||||
|
setForwardInfo(messageDirection);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setBackground(final MessageDirection messageDirection) {
|
private void setBackground(final MessageDirection messageDirection) {
|
||||||
@ -316,6 +319,11 @@ public abstract class DirectItemViewHolder extends RecyclerView.ViewHolder {
|
|||||||
return String.format("Replied to %s", repliedToUsername);
|
return String.format("Replied to %s", repliedToUsername);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void setForwardInfo(final MessageDirection direction) {
|
||||||
|
binding.replyInfo.setVisibility(View.VISIBLE);
|
||||||
|
binding.replyInfo.setText(direction == MessageDirection.OUTGOING ? "You forwarded a message" : "Forwarded a message");
|
||||||
|
}
|
||||||
|
|
||||||
private void setReplyGravity(final MessageDirection messageDirection) {
|
private void setReplyGravity(final MessageDirection messageDirection) {
|
||||||
final boolean isIncoming = messageDirection == MessageDirection.INCOMING;
|
final boolean isIncoming = messageDirection == MessageDirection.INCOMING;
|
||||||
final ConstraintLayout.LayoutParams quoteLineLayoutParams = (ConstraintLayout.LayoutParams) binding.quoteLine.getLayoutParams();
|
final ConstraintLayout.LayoutParams quoteLineLayoutParams = (ConstraintLayout.LayoutParams) binding.quoteLine.getLayoutParams();
|
||||||
@ -426,6 +434,10 @@ public abstract class DirectItemViewHolder extends RecyclerView.ViewHolder {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected boolean canForward() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
protected List<DirectItemContextMenu.MenuItem> getLongClickOptions() {
|
protected List<DirectItemContextMenu.MenuItem> getLongClickOptions() {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -510,6 +522,9 @@ public abstract class DirectItemViewHolder extends RecyclerView.ViewHolder {
|
|||||||
if (longClickOptions != null) {
|
if (longClickOptions != null) {
|
||||||
builder.addAll(longClickOptions);
|
builder.addAll(longClickOptions);
|
||||||
}
|
}
|
||||||
|
if (canForward()) {
|
||||||
|
builder.add(new DirectItemContextMenu.MenuItem(R.id.forward, R.string.forward));
|
||||||
|
}
|
||||||
if (messageDirection == MessageDirection.OUTGOING) {
|
if (messageDirection == MessageDirection.OUTGOING) {
|
||||||
builder.add(new DirectItemContextMenu.MenuItem(R.id.unsend, R.string.dms_inbox_unsend));
|
builder.add(new DirectItemContextMenu.MenuItem(R.id.unsend, R.string.dms_inbox_unsend));
|
||||||
}
|
}
|
||||||
|
@ -169,6 +169,11 @@ public class DirectItemVoiceMediaViewHolder extends DirectItemViewHolder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean canForward() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
private static class AudioItemState {
|
private static class AudioItemState {
|
||||||
private boolean prepared;
|
private boolean prepared;
|
||||||
|
|
||||||
|
@ -0,0 +1,89 @@
|
|||||||
|
package awais.instagrabber.adapters.viewholder.directmessages;
|
||||||
|
|
||||||
|
import android.content.res.Resources;
|
||||||
|
import android.view.View;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import awais.instagrabber.R;
|
||||||
|
import awais.instagrabber.adapters.UserSearchResultsAdapter.OnRecipientClickListener;
|
||||||
|
import awais.instagrabber.databinding.LayoutDmUserItemBinding;
|
||||||
|
import awais.instagrabber.repositories.responses.User;
|
||||||
|
import awais.instagrabber.repositories.responses.directmessages.DirectThread;
|
||||||
|
import awais.instagrabber.repositories.responses.directmessages.RankedRecipient;
|
||||||
|
|
||||||
|
public class RecipientThreadViewHolder extends RecyclerView.ViewHolder {
|
||||||
|
private static final String TAG = RecipientThreadViewHolder.class.getSimpleName();
|
||||||
|
|
||||||
|
private final LayoutDmUserItemBinding binding;
|
||||||
|
private final OnRecipientClickListener onThreadClickListener;
|
||||||
|
private final float translateAmount;
|
||||||
|
|
||||||
|
public RecipientThreadViewHolder(@NonNull final LayoutDmUserItemBinding binding,
|
||||||
|
final OnRecipientClickListener onThreadClickListener) {
|
||||||
|
super(binding.getRoot());
|
||||||
|
this.binding = binding;
|
||||||
|
this.onThreadClickListener = onThreadClickListener;
|
||||||
|
binding.info.setVisibility(View.GONE);
|
||||||
|
final Resources resources = itemView.getResources();
|
||||||
|
final int avatarSize = resources.getDimensionPixelSize(R.dimen.dm_inbox_avatar_size);
|
||||||
|
translateAmount = ((float) avatarSize) / 7;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void bind(final int position,
|
||||||
|
final DirectThread thread,
|
||||||
|
final boolean showSelection,
|
||||||
|
final boolean isSelected) {
|
||||||
|
if (thread == null) return;
|
||||||
|
binding.getRoot().setOnClickListener(v -> {
|
||||||
|
if (onThreadClickListener == null) return;
|
||||||
|
onThreadClickListener.onClick(position, RankedRecipient.of(thread), isSelected);
|
||||||
|
});
|
||||||
|
binding.fullName.setText(thread.getThreadTitle());
|
||||||
|
setUsername(thread);
|
||||||
|
setProfilePic(thread);
|
||||||
|
setSelection(showSelection, isSelected);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setProfilePic(final DirectThread thread) {
|
||||||
|
final List<User> users = thread.getUsers();
|
||||||
|
binding.profilePic.setImageURI(users.get(0).getProfilePicUrl());
|
||||||
|
binding.profilePic.setScaleX(1);
|
||||||
|
binding.profilePic.setScaleY(1);
|
||||||
|
binding.profilePic.setTranslationX(0);
|
||||||
|
binding.profilePic.setTranslationY(0);
|
||||||
|
if (users.size() > 1) {
|
||||||
|
binding.profilePic2.setVisibility(View.VISIBLE);
|
||||||
|
binding.profilePic2.setImageURI(users.get(1).getProfilePicUrl());
|
||||||
|
binding.profilePic2.setTranslationX(translateAmount);
|
||||||
|
binding.profilePic2.setTranslationY(translateAmount);
|
||||||
|
final float scaleAmount = 0.75f;
|
||||||
|
binding.profilePic2.setScaleX(scaleAmount);
|
||||||
|
binding.profilePic2.setScaleY(scaleAmount);
|
||||||
|
binding.profilePic.setScaleX(scaleAmount);
|
||||||
|
binding.profilePic.setScaleY(scaleAmount);
|
||||||
|
binding.profilePic.setTranslationX(-translateAmount);
|
||||||
|
binding.profilePic.setTranslationY(-translateAmount);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
binding.profilePic2.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setUsername(final DirectThread thread) {
|
||||||
|
if (thread.isGroup()) {
|
||||||
|
binding.username.setVisibility(View.GONE);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
binding.username.setVisibility(View.VISIBLE);
|
||||||
|
// for a non-group thread, the thread title is the username so set the full name in the username text view
|
||||||
|
binding.username.setText(thread.getUsers().get(0).getFullName());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setSelection(final boolean showSelection, final boolean isSelected) {
|
||||||
|
binding.select.setVisibility(showSelection ? View.VISIBLE : View.GONE);
|
||||||
|
binding.getRoot().setSelected(isSelected);
|
||||||
|
}
|
||||||
|
}
|
@ -23,11 +23,14 @@ import androidx.transition.TransitionManager;
|
|||||||
import com.google.android.material.chip.Chip;
|
import com.google.android.material.chip.Chip;
|
||||||
import com.google.android.material.snackbar.Snackbar;
|
import com.google.android.material.snackbar.Snackbar;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
import awais.instagrabber.activities.MainActivity;
|
import awais.instagrabber.activities.MainActivity;
|
||||||
import awais.instagrabber.adapters.UserSearchResultsAdapter;
|
import awais.instagrabber.adapters.UserSearchResultsAdapter;
|
||||||
import awais.instagrabber.customviews.helpers.TextWatcherAdapter;
|
import awais.instagrabber.customviews.helpers.TextWatcherAdapter;
|
||||||
import awais.instagrabber.databinding.FragmentUserSearchBinding;
|
import awais.instagrabber.databinding.FragmentUserSearchBinding;
|
||||||
import awais.instagrabber.repositories.responses.User;
|
import awais.instagrabber.repositories.responses.directmessages.RankedRecipient;
|
||||||
import awais.instagrabber.utils.TextUtils;
|
import awais.instagrabber.utils.TextUtils;
|
||||||
import awais.instagrabber.utils.Utils;
|
import awais.instagrabber.utils.Utils;
|
||||||
import awais.instagrabber.utils.ViewUtils;
|
import awais.instagrabber.utils.ViewUtils;
|
||||||
@ -46,7 +49,6 @@ public class UserSearchFragment extends Fragment {
|
|||||||
private String actionLabel;
|
private String actionLabel;
|
||||||
private String title;
|
private String title;
|
||||||
private boolean multiple;
|
private boolean multiple;
|
||||||
private long[] hideUserIds;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(@Nullable final Bundle savedInstanceState) {
|
public void onCreate(@Nullable final Bundle savedInstanceState) {
|
||||||
@ -81,12 +83,17 @@ public class UserSearchFragment extends Fragment {
|
|||||||
actionLabel = fragmentArgs.getActionLabel();
|
actionLabel = fragmentArgs.getActionLabel();
|
||||||
title = fragmentArgs.getTitle();
|
title = fragmentArgs.getTitle();
|
||||||
multiple = fragmentArgs.getMultiple();
|
multiple = fragmentArgs.getMultiple();
|
||||||
|
viewModel.setHideThreadIds(fragmentArgs.getHideThreadIds());
|
||||||
viewModel.setHideUserIds(fragmentArgs.getHideUserIds());
|
viewModel.setHideUserIds(fragmentArgs.getHideUserIds());
|
||||||
|
viewModel.setSearchMode(fragmentArgs.getSearchMode());
|
||||||
|
viewModel.setShowGroups(fragmentArgs.getShowGroups());
|
||||||
}
|
}
|
||||||
setupTitles();
|
setupTitles();
|
||||||
setupInput();
|
setupInput();
|
||||||
setupResults();
|
setupResults();
|
||||||
setupObservers();
|
setupObservers();
|
||||||
|
// show cached results
|
||||||
|
viewModel.showCachedResults();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setupTitles() {
|
private void setupTitles() {
|
||||||
@ -108,54 +115,64 @@ public class UserSearchFragment extends Fragment {
|
|||||||
final Context context = getContext();
|
final Context context = getContext();
|
||||||
if (context == null) return;
|
if (context == null) return;
|
||||||
binding.results.setLayoutManager(new LinearLayoutManager(context));
|
binding.results.setLayoutManager(new LinearLayoutManager(context));
|
||||||
resultsAdapter = new UserSearchResultsAdapter(multiple, (position, user, selected) -> {
|
resultsAdapter = new UserSearchResultsAdapter(multiple, (position, recipient, selected) -> {
|
||||||
if (!multiple) {
|
if (!multiple) {
|
||||||
final NavController navController = NavHostFragment.findNavController(this);
|
final NavController navController = NavHostFragment.findNavController(this);
|
||||||
final NavBackStackEntry navBackStackEntry = navController.getPreviousBackStackEntry();
|
if (!setResult(navController, recipient)) return;
|
||||||
if (navBackStackEntry == null) return;
|
|
||||||
final SavedStateHandle savedStateHandle = navBackStackEntry.getSavedStateHandle();
|
|
||||||
savedStateHandle.set("result", user);
|
|
||||||
navController.navigateUp();
|
navController.navigateUp();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
viewModel.setSelectedUser(user, !selected);
|
viewModel.setSelectedRecipient(recipient, !selected);
|
||||||
resultsAdapter.setSelectedUser(user.getPk(), !selected);
|
resultsAdapter.setSelectedRecipient(recipient, !selected);
|
||||||
if (!selected) {
|
if (!selected) {
|
||||||
createUserChip(user);
|
createChip(recipient);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final View chip = findChip(user.getPk());
|
final View chip = findChip(recipient);
|
||||||
if (chip == null) return;
|
if (chip == null) return;
|
||||||
removeChip(chip);
|
removeChipFromGroup(chip);
|
||||||
});
|
});
|
||||||
binding.results.setAdapter(resultsAdapter);
|
binding.results.setAdapter(resultsAdapter);
|
||||||
binding.done.setOnClickListener(v -> {
|
binding.done.setOnClickListener(v -> {
|
||||||
final NavController navController = NavHostFragment.findNavController(this);
|
final NavController navController = NavHostFragment.findNavController(this);
|
||||||
final NavBackStackEntry navBackStackEntry = navController.getPreviousBackStackEntry();
|
if (!setResult(navController, viewModel.getSelectedRecipients())) return;
|
||||||
if (navBackStackEntry == null) return;
|
|
||||||
final SavedStateHandle savedStateHandle = navBackStackEntry.getSavedStateHandle();
|
|
||||||
savedStateHandle.set("result", viewModel.getSelectedUsers());
|
|
||||||
navController.navigateUp();
|
navController.navigateUp();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean setResult(@NonNull final NavController navController, final RankedRecipient rankedRecipient) {
|
||||||
|
final NavBackStackEntry navBackStackEntry = navController.getPreviousBackStackEntry();
|
||||||
|
if (navBackStackEntry == null) return false;
|
||||||
|
final SavedStateHandle savedStateHandle = navBackStackEntry.getSavedStateHandle();
|
||||||
|
savedStateHandle.set("result", rankedRecipient);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean setResult(@NonNull final NavController navController, final Set<RankedRecipient> rankedRecipients) {
|
||||||
|
final NavBackStackEntry navBackStackEntry = navController.getPreviousBackStackEntry();
|
||||||
|
if (navBackStackEntry == null) return false;
|
||||||
|
final SavedStateHandle savedStateHandle = navBackStackEntry.getSavedStateHandle();
|
||||||
|
savedStateHandle.set("result", rankedRecipients);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
private void setupInput() {
|
private void setupInput() {
|
||||||
binding.search.addTextChangedListener(new TextWatcherAdapter() {
|
binding.search.addTextChangedListener(new TextWatcherAdapter() {
|
||||||
@Override
|
@Override
|
||||||
public void onTextChanged(final CharSequence s, final int start, final int before, final int count) {
|
public void onTextChanged(final CharSequence s, final int start, final int before, final int count) {
|
||||||
if (TextUtils.isEmpty(s)) {
|
// if (TextUtils.isEmpty(s)) {
|
||||||
viewModel.cancelSearch();
|
// viewModel.cancelSearch();
|
||||||
viewModel.clearResults();
|
// viewModel.clearResults();
|
||||||
return;
|
// return;
|
||||||
}
|
// }
|
||||||
viewModel.search(s.toString().trim());
|
viewModel.search(s == null ? null : s.toString().trim());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
binding.search.setOnKeyListener((v, keyCode, event) -> {
|
binding.search.setOnKeyListener((v, keyCode, event) -> {
|
||||||
if (event != null && event.getAction() == KeyEvent.ACTION_DOWN && event.getKeyCode() == KeyEvent.KEYCODE_DEL) {
|
if (event != null && event.getAction() == KeyEvent.ACTION_DOWN && event.getKeyCode() == KeyEvent.KEYCODE_DEL) {
|
||||||
final View chip = getLastChip();
|
final View chip = getLastChip();
|
||||||
if (chip == null) return false;
|
if (chip == null) return false;
|
||||||
removeSelectedUser(chip);
|
removeChip(chip);
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
@ -175,32 +192,41 @@ public class UserSearchFragment extends Fragment {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void setupObservers() {
|
private void setupObservers() {
|
||||||
viewModel.getUsers().observe(getViewLifecycleOwner(), results -> {
|
viewModel.getRecipients().observe(getViewLifecycleOwner(), results -> {
|
||||||
if (results == null) return;
|
if (results == null) return;
|
||||||
switch (results.status) {
|
switch (results.status) {
|
||||||
case SUCCESS:
|
case SUCCESS:
|
||||||
|
if (results.data != null) {
|
||||||
resultsAdapter.submitList(results.data);
|
resultsAdapter.submitList(results.data);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case ERROR:
|
case ERROR:
|
||||||
if (results.message != null) {
|
if (results.message != null) {
|
||||||
Snackbar.make(binding.getRoot(), results.message, Snackbar.LENGTH_LONG).show();
|
Snackbar.make(binding.getRoot(), results.message, Snackbar.LENGTH_LONG).show();
|
||||||
}
|
}
|
||||||
|
if (results.data != null) {
|
||||||
|
resultsAdapter.submitList(results.data);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case LOADING:
|
case LOADING:
|
||||||
|
//noinspection DuplicateBranchesInSwitch
|
||||||
|
if (results.data != null) {
|
||||||
|
resultsAdapter.submitList(results.data);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
viewModel.showAction().observe(getViewLifecycleOwner(), showAction -> binding.done.setVisibility(showAction ? View.VISIBLE : View.GONE));
|
viewModel.showAction().observe(getViewLifecycleOwner(), showAction -> binding.done.setVisibility(showAction ? View.VISIBLE : View.GONE));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void createUserChip(final User user) {
|
private void createChip(final RankedRecipient recipient) {
|
||||||
final Context context = getContext();
|
final Context context = getContext();
|
||||||
if (context == null) return;
|
if (context == null) return;
|
||||||
final Chip chip = new Chip(context);
|
final Chip chip = new Chip(context);
|
||||||
chip.setTag(user);
|
chip.setTag(recipient);
|
||||||
chip.setText(user.getFullName());
|
chip.setText(getRecipientText(recipient));
|
||||||
chip.setCloseIconVisible(true);
|
chip.setCloseIconVisible(true);
|
||||||
chip.setOnCloseIconClickListener(v -> removeSelectedUser(chip));
|
chip.setOnCloseIconClickListener(v -> removeChip(chip));
|
||||||
binding.group.post(() -> {
|
binding.group.post(() -> {
|
||||||
final Pair<Integer, Integer> measure = ViewUtils.measure(chip, binding.group);
|
final Pair<Integer, Integer> measure = ViewUtils.measure(chip, binding.group);
|
||||||
TransitionManager.beginDelayedTransition(binding.getRoot());
|
TransitionManager.beginDelayedTransition(binding.getRoot());
|
||||||
@ -209,31 +235,44 @@ public class UserSearchFragment extends Fragment {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void removeSelectedUser(final View chip) {
|
private String getRecipientText(final RankedRecipient recipient) {
|
||||||
final User user = (User) chip.getTag();
|
if (recipient == null) return null;
|
||||||
if (user == null) return;
|
if (recipient.getUser() != null) {
|
||||||
viewModel.setSelectedUser(user, false);
|
return recipient.getUser().getFullName();
|
||||||
resultsAdapter.setSelectedUser(user.getPk(), false);
|
}
|
||||||
removeChip(chip);
|
if (recipient.getThread() != null) {
|
||||||
|
return recipient.getThread().getThreadTitle();
|
||||||
}
|
}
|
||||||
|
|
||||||
private View findChip(final long userId) {
|
|
||||||
final int childCount = binding.group.getChildCount();
|
|
||||||
if (childCount == 0) {
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void removeChip(@NonNull final View chip) {
|
||||||
|
final RankedRecipient recipient = (RankedRecipient) chip.getTag();
|
||||||
|
if (recipient == null) return;
|
||||||
|
viewModel.setSelectedRecipient(recipient, false);
|
||||||
|
resultsAdapter.setSelectedRecipient(recipient, false);
|
||||||
|
removeChipFromGroup(chip);
|
||||||
|
}
|
||||||
|
|
||||||
|
private View findChip(final RankedRecipient recipient) {
|
||||||
|
if (recipient == null || recipient.getUser() == null && recipient.getThread() == null) return null;
|
||||||
|
boolean isUser = recipient.getUser() != null;
|
||||||
|
final int childCount = binding.group.getChildCount();
|
||||||
|
if (childCount == 0) return null;
|
||||||
for (int i = childCount - 1; i >= 0; i--) {
|
for (int i = childCount - 1; i >= 0; i--) {
|
||||||
final View child = binding.group.getChildAt(i);
|
final View child = binding.group.getChildAt(i);
|
||||||
if (child == null) continue;
|
if (child == null) continue;
|
||||||
final User user = (User) child.getTag();
|
final RankedRecipient tag = (RankedRecipient) child.getTag();
|
||||||
if (user != null && user.getPk() == userId) {
|
if (tag == null || isUser && tag.getUser() == null || !isUser && tag.getThread() == null) continue;
|
||||||
|
if ((isUser && tag.getUser().getPk() == recipient.getUser().getPk())
|
||||||
|
|| (!isUser && Objects.equals(tag.getThread().getThreadId(), recipient.getThread().getThreadId()))) {
|
||||||
return child;
|
return child;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void removeChip(final View chip) {
|
private void removeChipFromGroup(final View chip) {
|
||||||
binding.group.post(() -> {
|
binding.group.post(() -> {
|
||||||
TransitionManager.beginDelayedTransition(binding.getRoot());
|
TransitionManager.beginDelayedTransition(binding.getRoot());
|
||||||
binding.group.removeView(chip);
|
binding.group.removeView(chip);
|
||||||
@ -267,4 +306,20 @@ public class UserSearchFragment extends Fragment {
|
|||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public enum SearchMode {
|
||||||
|
USER_SEARCH("user_name"),
|
||||||
|
RAVEN("raven"),
|
||||||
|
RESHARE("reshare");
|
||||||
|
|
||||||
|
private final String name;
|
||||||
|
|
||||||
|
SearchMode(final String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,7 +20,6 @@ import androidx.lifecycle.ViewModelStoreOwner;
|
|||||||
import androidx.navigation.NavBackStackEntry;
|
import androidx.navigation.NavBackStackEntry;
|
||||||
import androidx.navigation.NavController;
|
import androidx.navigation.NavController;
|
||||||
import androidx.navigation.NavDestination;
|
import androidx.navigation.NavDestination;
|
||||||
import androidx.navigation.NavDirections;
|
|
||||||
import androidx.navigation.fragment.NavHostFragment;
|
import androidx.navigation.fragment.NavHostFragment;
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||||
|
|
||||||
@ -29,18 +28,24 @@ import com.google.android.material.snackbar.Snackbar;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import awais.instagrabber.R;
|
import awais.instagrabber.R;
|
||||||
|
import awais.instagrabber.UserSearchNavGraphDirections;
|
||||||
import awais.instagrabber.adapters.DirectUsersAdapter;
|
import awais.instagrabber.adapters.DirectUsersAdapter;
|
||||||
import awais.instagrabber.customviews.helpers.TextWatcherAdapter;
|
import awais.instagrabber.customviews.helpers.TextWatcherAdapter;
|
||||||
import awais.instagrabber.databinding.FragmentDirectMessagesSettingsBinding;
|
import awais.instagrabber.databinding.FragmentDirectMessagesSettingsBinding;
|
||||||
import awais.instagrabber.dialogs.MultiOptionDialogFragment;
|
import awais.instagrabber.dialogs.MultiOptionDialogFragment;
|
||||||
import awais.instagrabber.dialogs.MultiOptionDialogFragment.Option;
|
import awais.instagrabber.dialogs.MultiOptionDialogFragment.Option;
|
||||||
|
import awais.instagrabber.fragments.UserSearchFragment;
|
||||||
|
import awais.instagrabber.fragments.UserSearchFragmentDirections;
|
||||||
import awais.instagrabber.models.Resource;
|
import awais.instagrabber.models.Resource;
|
||||||
import awais.instagrabber.repositories.responses.User;
|
import awais.instagrabber.repositories.responses.User;
|
||||||
import awais.instagrabber.repositories.responses.directmessages.DirectThread;
|
import awais.instagrabber.repositories.responses.directmessages.DirectThread;
|
||||||
|
import awais.instagrabber.repositories.responses.directmessages.RankedRecipient;
|
||||||
import awais.instagrabber.viewmodels.DirectInboxViewModel;
|
import awais.instagrabber.viewmodels.DirectInboxViewModel;
|
||||||
import awais.instagrabber.viewmodels.DirectSettingsViewModel;
|
import awais.instagrabber.viewmodels.DirectSettingsViewModel;
|
||||||
|
|
||||||
@ -155,6 +160,12 @@ public class DirectMessageSettingsFragment extends Fragment {
|
|||||||
setupObservers();
|
setupObservers();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDestroyView() {
|
||||||
|
super.onDestroyView();
|
||||||
|
binding = null;
|
||||||
|
}
|
||||||
|
|
||||||
private void setupObservers() {
|
private void setupObservers() {
|
||||||
viewModel.getUsers().observe(getViewLifecycleOwner(), users -> {
|
viewModel.getUsers().observe(getViewLifecycleOwner(), users -> {
|
||||||
if (usersAdapter == null) return;
|
if (usersAdapter == null) return;
|
||||||
@ -171,16 +182,27 @@ public class DirectMessageSettingsFragment extends Fragment {
|
|||||||
final MutableLiveData<Object> resultLiveData = backStackEntry.getSavedStateHandle().getLiveData("result");
|
final MutableLiveData<Object> resultLiveData = backStackEntry.getSavedStateHandle().getLiveData("result");
|
||||||
resultLiveData.observe(getViewLifecycleOwner(), result -> {
|
resultLiveData.observe(getViewLifecycleOwner(), result -> {
|
||||||
LiveData<Resource<Object>> detailsChangeResourceLiveData = null;
|
LiveData<Resource<Object>> detailsChangeResourceLiveData = null;
|
||||||
if ((result instanceof User)) {
|
if ((result instanceof RankedRecipient)) {
|
||||||
// Log.d(TAG, "result: " + result);
|
final RankedRecipient recipient = (RankedRecipient) result;
|
||||||
detailsChangeResourceLiveData = viewModel.addMembers(Collections.singleton((User) result));
|
final User user = getUser(recipient);
|
||||||
|
// Log.d(TAG, "result: " + user);
|
||||||
|
if (user != null) {
|
||||||
|
detailsChangeResourceLiveData = viewModel.addMembers(Collections.singleton(recipient.getUser()));
|
||||||
|
}
|
||||||
} else if ((result instanceof Set)) {
|
} else if ((result instanceof Set)) {
|
||||||
try {
|
try {
|
||||||
// Log.d(TAG, "result: " + result);
|
|
||||||
//noinspection unchecked
|
//noinspection unchecked
|
||||||
detailsChangeResourceLiveData = viewModel.addMembers((Set<User>) result);
|
final Set<RankedRecipient> recipients = (Set<RankedRecipient>) result;
|
||||||
|
final Set<User> users = recipients.stream()
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.map(this::getUser)
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.collect(Collectors.toSet());
|
||||||
|
// Log.d(TAG, "result: " + users);
|
||||||
|
detailsChangeResourceLiveData = viewModel.addMembers(users);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Log.e(TAG, "search users result: ", e);
|
Log.e(TAG, "search users result: ", e);
|
||||||
|
Snackbar.make(binding.getRoot(), e.getMessage(), Snackbar.LENGTH_LONG).show();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (detailsChangeResourceLiveData != null) {
|
if (detailsChangeResourceLiveData != null) {
|
||||||
@ -190,6 +212,17 @@ public class DirectMessageSettingsFragment extends Fragment {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private User getUser(@NonNull final RankedRecipient recipient) {
|
||||||
|
User user = null;
|
||||||
|
if (recipient.getUser() != null) {
|
||||||
|
user = recipient.getUser();
|
||||||
|
} else if (recipient.getThread() != null && !recipient.getThread().isGroup()) {
|
||||||
|
user = recipient.getThread().getUsers().get(0);
|
||||||
|
}
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
private void init() {
|
private void init() {
|
||||||
setupSettings();
|
setupSettings();
|
||||||
setupMembers();
|
setupMembers();
|
||||||
@ -232,13 +265,14 @@ public class DirectMessageSettingsFragment extends Fragment {
|
|||||||
} else {
|
} else {
|
||||||
currentUserIds = new long[0];
|
currentUserIds = new long[0];
|
||||||
}
|
}
|
||||||
final NavDirections directions = DirectMessageSettingsFragmentDirections.actionGlobalUserSearch(
|
final UserSearchNavGraphDirections.ActionGlobalUserSearch actionGlobalUserSearch = UserSearchFragmentDirections
|
||||||
true,
|
.actionGlobalUserSearch()
|
||||||
"Add users",
|
.setTitle(getString(R.string.add_members))
|
||||||
"Add",
|
.setActionLabel(getString(R.string.add))
|
||||||
currentUserIds
|
.setHideUserIds(currentUserIds)
|
||||||
);
|
.setSearchMode(UserSearchFragment.SearchMode.RAVEN)
|
||||||
navController.navigate(directions);
|
.setMultiple(true);
|
||||||
|
navController.navigate(actionGlobalUserSearch);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -34,6 +34,7 @@ import androidx.fragment.app.Fragment;
|
|||||||
import androidx.lifecycle.LiveData;
|
import androidx.lifecycle.LiveData;
|
||||||
import androidx.lifecycle.MediatorLiveData;
|
import androidx.lifecycle.MediatorLiveData;
|
||||||
import androidx.lifecycle.MutableLiveData;
|
import androidx.lifecycle.MutableLiveData;
|
||||||
|
import androidx.lifecycle.Observer;
|
||||||
import androidx.lifecycle.ViewModelProvider;
|
import androidx.lifecycle.ViewModelProvider;
|
||||||
import androidx.lifecycle.ViewModelStoreOwner;
|
import androidx.lifecycle.ViewModelStoreOwner;
|
||||||
import androidx.navigation.NavBackStackEntry;
|
import androidx.navigation.NavBackStackEntry;
|
||||||
@ -53,8 +54,10 @@ import java.io.File;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
import awais.instagrabber.R;
|
import awais.instagrabber.R;
|
||||||
|
import awais.instagrabber.UserSearchNavGraphDirections;
|
||||||
import awais.instagrabber.activities.CameraActivity;
|
import awais.instagrabber.activities.CameraActivity;
|
||||||
import awais.instagrabber.activities.MainActivity;
|
import awais.instagrabber.activities.MainActivity;
|
||||||
import awais.instagrabber.adapters.DirectItemsAdapter;
|
import awais.instagrabber.adapters.DirectItemsAdapter;
|
||||||
@ -75,6 +78,8 @@ import awais.instagrabber.databinding.FragmentDirectMessagesThreadBinding;
|
|||||||
import awais.instagrabber.dialogs.DirectItemReactionDialogFragment;
|
import awais.instagrabber.dialogs.DirectItemReactionDialogFragment;
|
||||||
import awais.instagrabber.dialogs.MediaPickerBottomDialogFragment;
|
import awais.instagrabber.dialogs.MediaPickerBottomDialogFragment;
|
||||||
import awais.instagrabber.fragments.PostViewV2Fragment;
|
import awais.instagrabber.fragments.PostViewV2Fragment;
|
||||||
|
import awais.instagrabber.fragments.UserSearchFragment;
|
||||||
|
import awais.instagrabber.fragments.UserSearchFragmentDirections;
|
||||||
import awais.instagrabber.models.Resource;
|
import awais.instagrabber.models.Resource;
|
||||||
import awais.instagrabber.repositories.requests.StoryViewerOptions;
|
import awais.instagrabber.repositories.requests.StoryViewerOptions;
|
||||||
import awais.instagrabber.repositories.responses.Media;
|
import awais.instagrabber.repositories.responses.Media;
|
||||||
@ -83,6 +88,7 @@ import awais.instagrabber.repositories.responses.directmessages.DirectItem;
|
|||||||
import awais.instagrabber.repositories.responses.directmessages.DirectItemEmojiReaction;
|
import awais.instagrabber.repositories.responses.directmessages.DirectItemEmojiReaction;
|
||||||
import awais.instagrabber.repositories.responses.directmessages.DirectItemStoryShare;
|
import awais.instagrabber.repositories.responses.directmessages.DirectItemStoryShare;
|
||||||
import awais.instagrabber.repositories.responses.directmessages.DirectThread;
|
import awais.instagrabber.repositories.responses.directmessages.DirectThread;
|
||||||
|
import awais.instagrabber.repositories.responses.directmessages.RankedRecipient;
|
||||||
import awais.instagrabber.utils.AppExecutors;
|
import awais.instagrabber.utils.AppExecutors;
|
||||||
import awais.instagrabber.utils.PermissionUtils;
|
import awais.instagrabber.utils.PermissionUtils;
|
||||||
import awais.instagrabber.utils.TextUtils;
|
import awais.instagrabber.utils.TextUtils;
|
||||||
@ -124,6 +130,9 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact
|
|||||||
private boolean isRecording;
|
private boolean isRecording;
|
||||||
private boolean wasKbShowing;
|
private boolean wasKbShowing;
|
||||||
private int keyboardHeight = Utils.convertDpToPx(250);
|
private int keyboardHeight = Utils.convertDpToPx(250);
|
||||||
|
private DirectItemReactionDialogFragment reactionDialogFragment;
|
||||||
|
private DirectItem itemToForward;
|
||||||
|
private MutableLiveData<Object> backStackSavedStateResultLiveData;
|
||||||
|
|
||||||
private final AppExecutors appExecutors = AppExecutors.getInstance();
|
private final AppExecutors appExecutors = AppExecutors.getInstance();
|
||||||
private final Animatable2Compat.AnimationCallback micToSendAnimationCallback = new Animatable2Compat.AnimationCallback() {
|
private final Animatable2Compat.AnimationCallback micToSendAnimationCallback = new Animatable2Compat.AnimationCallback() {
|
||||||
@ -229,13 +238,48 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact
|
|||||||
handleSentMessage(viewModel.unsend(item));
|
handleSentMessage(viewModel.unsend(item));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (itemId == R.id.forward) {
|
||||||
|
itemToForward = item;
|
||||||
|
final UserSearchNavGraphDirections.ActionGlobalUserSearch actionGlobalUserSearch = UserSearchFragmentDirections
|
||||||
|
.actionGlobalUserSearch()
|
||||||
|
.setTitle(getString(R.string.forward))
|
||||||
|
.setActionLabel(getString(R.string.send))
|
||||||
|
.setShowGroups(true)
|
||||||
|
.setMultiple(true)
|
||||||
|
.setSearchMode(UserSearchFragment.SearchMode.RAVEN);
|
||||||
|
final NavController navController = NavHostFragment.findNavController(DirectMessageThreadFragment.this);
|
||||||
|
navController.navigate(actionGlobalUserSearch);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
private final DirectItemLongClickListener directItemLongClickListener = position -> {
|
private final DirectItemLongClickListener directItemLongClickListener = position -> {
|
||||||
// viewModel.setSelectedPosition(position);
|
// viewModel.setSelectedPosition(position);
|
||||||
};
|
};
|
||||||
private DirectItemReactionDialogFragment reactionDialogFragment;
|
private final Observer<Object> backStackSavedStateObserver = result -> {
|
||||||
|
if (result == null) return;
|
||||||
|
if (result instanceof Uri) {
|
||||||
|
final Uri uri = (Uri) result;
|
||||||
|
handleSentMessage(viewModel.sendUri(uri));
|
||||||
|
} else if ((result instanceof RankedRecipient)) {
|
||||||
|
// Log.d(TAG, "result: " + result);
|
||||||
|
if (itemToForward != null) {
|
||||||
|
viewModel.forward((RankedRecipient) result, itemToForward);
|
||||||
|
}
|
||||||
|
} else if ((result instanceof Set)) {
|
||||||
|
try {
|
||||||
|
// Log.d(TAG, "result: " + result);
|
||||||
|
if (itemToForward != null) {
|
||||||
|
//noinspection unchecked
|
||||||
|
viewModel.forward((Set<RankedRecipient>) result, itemToForward);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e(TAG, "forward result: ", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// clear result
|
||||||
|
backStackSavedStateResultLiveData.postValue(null);
|
||||||
|
};
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(@Nullable final Bundle savedInstanceState) {
|
public void onCreate(@Nullable final Bundle savedInstanceState) {
|
||||||
@ -361,6 +405,7 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact
|
|||||||
binding.send.setX(initialSendX);
|
binding.send.setX(initialSendX);
|
||||||
}
|
}
|
||||||
binding.send.stopScale();
|
binding.send.stopScale();
|
||||||
|
setupBackStackResultObserver();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -587,17 +632,14 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact
|
|||||||
setupItemsAdapter(userThreadPair.first, userThreadPair.second);
|
setupItemsAdapter(userThreadPair.first, userThreadPair.second);
|
||||||
});
|
});
|
||||||
viewModel.getItems().observe(getViewLifecycleOwner(), this::submitItemsToAdapter);
|
viewModel.getItems().observe(getViewLifecycleOwner(), this::submitItemsToAdapter);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupBackStackResultObserver() {
|
||||||
final NavController navController = NavHostFragment.findNavController(this);
|
final NavController navController = NavHostFragment.findNavController(this);
|
||||||
final NavBackStackEntry backStackEntry = navController.getCurrentBackStackEntry();
|
final NavBackStackEntry backStackEntry = navController.getCurrentBackStackEntry();
|
||||||
if (backStackEntry != null) {
|
if (backStackEntry != null) {
|
||||||
final MutableLiveData<Object> resultLiveData = backStackEntry.getSavedStateHandle().getLiveData("result");
|
backStackSavedStateResultLiveData = backStackEntry.getSavedStateHandle().getLiveData("result");
|
||||||
resultLiveData.observe(getViewLifecycleOwner(), result -> {
|
backStackSavedStateResultLiveData.observe(getViewLifecycleOwner(), backStackSavedStateObserver);
|
||||||
if (!(result instanceof Uri)) return;
|
|
||||||
final Uri uri = (Uri) result;
|
|
||||||
viewModel.sendUri(uri);
|
|
||||||
// clear result
|
|
||||||
resultLiveData.postValue(null);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
package awais.instagrabber.models.enums;
|
package awais.instagrabber.models.enums;
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
import com.google.gson.annotations.SerializedName;
|
import com.google.gson.annotations.SerializedName;
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
@ -62,4 +64,46 @@ public enum DirectItemType implements Serializable {
|
|||||||
public static DirectItemType valueOf(final int id) {
|
public static DirectItemType valueOf(final int id) {
|
||||||
return map.get(id);
|
return map.get(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public String getName() {
|
||||||
|
switch (this) {
|
||||||
|
case TEXT:
|
||||||
|
return "text";
|
||||||
|
case LIKE:
|
||||||
|
return "like";
|
||||||
|
case LINK:
|
||||||
|
return "link";
|
||||||
|
case MEDIA:
|
||||||
|
return "media";
|
||||||
|
case RAVEN_MEDIA:
|
||||||
|
return "raven_media";
|
||||||
|
case PROFILE:
|
||||||
|
return "profile";
|
||||||
|
case VIDEO_CALL_EVENT:
|
||||||
|
return "video_call_event";
|
||||||
|
case ANIMATED_MEDIA:
|
||||||
|
return "animated_media";
|
||||||
|
case VOICE_MEDIA:
|
||||||
|
return "voice_media";
|
||||||
|
case MEDIA_SHARE:
|
||||||
|
return "media_share";
|
||||||
|
case REEL_SHARE:
|
||||||
|
return "reel_share";
|
||||||
|
case ACTION_LOG:
|
||||||
|
return "action_log";
|
||||||
|
case PLACEHOLDER:
|
||||||
|
return "placeholder";
|
||||||
|
case STORY_SHARE:
|
||||||
|
return "story_share";
|
||||||
|
case CLIP:
|
||||||
|
return "clip";
|
||||||
|
case FELIX_SHARE:
|
||||||
|
return "felix_share";
|
||||||
|
case LOCATION:
|
||||||
|
return "location";
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
@ -4,9 +4,11 @@ import java.util.Map;
|
|||||||
|
|
||||||
import awais.instagrabber.repositories.responses.directmessages.DirectBadgeCount;
|
import awais.instagrabber.repositories.responses.directmessages.DirectBadgeCount;
|
||||||
import awais.instagrabber.repositories.responses.directmessages.DirectInboxResponse;
|
import awais.instagrabber.repositories.responses.directmessages.DirectInboxResponse;
|
||||||
|
import awais.instagrabber.repositories.responses.directmessages.DirectThread;
|
||||||
import awais.instagrabber.repositories.responses.directmessages.DirectThreadBroadcastResponse;
|
import awais.instagrabber.repositories.responses.directmessages.DirectThreadBroadcastResponse;
|
||||||
import awais.instagrabber.repositories.responses.directmessages.DirectThreadDetailsChangeResponse;
|
import awais.instagrabber.repositories.responses.directmessages.DirectThreadDetailsChangeResponse;
|
||||||
import awais.instagrabber.repositories.responses.directmessages.DirectThreadFeedResponse;
|
import awais.instagrabber.repositories.responses.directmessages.DirectThreadFeedResponse;
|
||||||
|
import awais.instagrabber.repositories.responses.directmessages.RankedRecipientsResponse;
|
||||||
import retrofit2.Call;
|
import retrofit2.Call;
|
||||||
import retrofit2.http.FieldMap;
|
import retrofit2.http.FieldMap;
|
||||||
import retrofit2.http.FormUrlEncoded;
|
import retrofit2.http.FormUrlEncoded;
|
||||||
@ -62,4 +64,15 @@ public interface DirectMessagesRepository {
|
|||||||
Call<String> deleteItem(@Path("threadId") String threadId,
|
Call<String> deleteItem(@Path("threadId") String threadId,
|
||||||
@Path("itemId") String itemId,
|
@Path("itemId") String itemId,
|
||||||
@FieldMap final Map<String, String> form);
|
@FieldMap final Map<String, String> form);
|
||||||
|
|
||||||
|
@GET("/api/v1/direct_v2/ranked_recipients/")
|
||||||
|
Call<RankedRecipientsResponse> rankedRecipients(@QueryMap Map<String, String> queryMap);
|
||||||
|
|
||||||
|
@FormUrlEncoded
|
||||||
|
@POST("/api/v1/direct_v2/threads/broadcast/forward/")
|
||||||
|
Call<DirectThreadBroadcastResponse> forward(@FieldMap final Map<String, String> form);
|
||||||
|
|
||||||
|
@FormUrlEncoded
|
||||||
|
@POST("/api/v1/direct_v2/create_group_thread/")
|
||||||
|
Call<DirectThread> createThread(@FieldMap final Map<String, String> signedForm);
|
||||||
}
|
}
|
||||||
|
@ -39,6 +39,7 @@ public class DirectItem implements Cloneable {
|
|||||||
private final int hideInThread;
|
private final int hideInThread;
|
||||||
private Date date;
|
private Date date;
|
||||||
private boolean isPending;
|
private boolean isPending;
|
||||||
|
private boolean showForwardAttribution;
|
||||||
|
|
||||||
public DirectItem(final String itemId,
|
public DirectItem(final String itemId,
|
||||||
final long userId,
|
final long userId,
|
||||||
@ -65,7 +66,8 @@ public class DirectItem implements Cloneable {
|
|||||||
final DirectItem repliedToMessage,
|
final DirectItem repliedToMessage,
|
||||||
final DirectItemVoiceMedia voiceMedia,
|
final DirectItemVoiceMedia voiceMedia,
|
||||||
final Location location,
|
final Location location,
|
||||||
final int hideInThread) {
|
final int hideInThread,
|
||||||
|
final boolean showForwardAttribution) {
|
||||||
this.itemId = itemId;
|
this.itemId = itemId;
|
||||||
this.userId = userId;
|
this.userId = userId;
|
||||||
this.timestamp = timestamp;
|
this.timestamp = timestamp;
|
||||||
@ -92,6 +94,7 @@ public class DirectItem implements Cloneable {
|
|||||||
this.voiceMedia = voiceMedia;
|
this.voiceMedia = voiceMedia;
|
||||||
this.location = location;
|
this.location = location;
|
||||||
this.hideInThread = hideInThread;
|
this.hideInThread = hideInThread;
|
||||||
|
this.showForwardAttribution = showForwardAttribution;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getItemId() {
|
public String getItemId() {
|
||||||
@ -222,6 +225,10 @@ public class DirectItem implements Cloneable {
|
|||||||
this.reactions = reactions;
|
this.reactions = reactions;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean showForwardAttribution() {
|
||||||
|
return showForwardAttribution;
|
||||||
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public Object clone() throws CloneNotSupportedException {
|
public Object clone() throws CloneNotSupportedException {
|
||||||
|
@ -2,13 +2,14 @@ package awais.instagrabber.repositories.responses.directmessages;
|
|||||||
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
import awais.instagrabber.repositories.responses.User;
|
import awais.instagrabber.repositories.responses.User;
|
||||||
|
|
||||||
public class DirectThread {
|
public class DirectThread implements Serializable {
|
||||||
private final String threadId;
|
private final String threadId;
|
||||||
private final String threadV2Id;
|
private final String threadV2Id;
|
||||||
private final List<User> users;
|
private final List<User> users;
|
||||||
|
@ -0,0 +1,46 @@
|
|||||||
|
package awais.instagrabber.repositories.responses.directmessages;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
import awais.instagrabber.repositories.responses.User;
|
||||||
|
|
||||||
|
public class RankedRecipient implements Serializable {
|
||||||
|
private final User user;
|
||||||
|
private final DirectThread thread;
|
||||||
|
|
||||||
|
public RankedRecipient(final User user, final DirectThread thread) {
|
||||||
|
this.user = user;
|
||||||
|
this.thread = thread;
|
||||||
|
}
|
||||||
|
|
||||||
|
public User getUser() {
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DirectThread getThread() {
|
||||||
|
return thread;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static RankedRecipient of(final User user) {
|
||||||
|
return new RankedRecipient(user, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static RankedRecipient of(final DirectThread thread) {
|
||||||
|
return new RankedRecipient(null, thread);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(final Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
|
final RankedRecipient that = (RankedRecipient) o;
|
||||||
|
return Objects.equals(user, that.user) &&
|
||||||
|
Objects.equals(thread, that.thread);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(user, thread);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,50 @@
|
|||||||
|
package awais.instagrabber.repositories.responses.directmessages;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class RankedRecipientsResponse {
|
||||||
|
private final List<RankedRecipient> rankedRecipients;
|
||||||
|
private final long expires;
|
||||||
|
private final boolean filtered;
|
||||||
|
private final String requestId;
|
||||||
|
private final String rankToken;
|
||||||
|
private final String status;
|
||||||
|
|
||||||
|
public RankedRecipientsResponse(final List<RankedRecipient> rankedRecipients,
|
||||||
|
final long expires,
|
||||||
|
final boolean filtered,
|
||||||
|
final String requestId,
|
||||||
|
final String rankToken,
|
||||||
|
final String status) {
|
||||||
|
this.rankedRecipients = rankedRecipients;
|
||||||
|
this.expires = expires;
|
||||||
|
this.filtered = filtered;
|
||||||
|
this.requestId = requestId;
|
||||||
|
this.rankToken = rankToken;
|
||||||
|
this.status = status;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<RankedRecipient> getRankedRecipients() {
|
||||||
|
return rankedRecipients;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getExpires() {
|
||||||
|
return expires;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isFiltered() {
|
||||||
|
return filtered;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getRequestId() {
|
||||||
|
return requestId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getRankToken() {
|
||||||
|
return rankToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getStatus() {
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
}
|
@ -47,8 +47,8 @@ public class DirectItemFactory {
|
|||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
0
|
0,
|
||||||
);
|
false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static DirectItem createImageOrVideo(final long userId,
|
public static DirectItem createImageOrVideo(final long userId,
|
||||||
@ -128,8 +128,8 @@ public class DirectItemFactory {
|
|||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
0
|
0,
|
||||||
);
|
false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static DirectItem createVoice(final long userId,
|
public static DirectItem createVoice(final long userId,
|
||||||
@ -209,7 +209,7 @@ public class DirectItemFactory {
|
|||||||
null,
|
null,
|
||||||
voiceMedia,
|
voiceMedia,
|
||||||
null,
|
null,
|
||||||
0
|
0,
|
||||||
);
|
false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,69 @@
|
|||||||
|
package awais.instagrabber.utils;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.time.temporal.ChronoUnit;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import awais.instagrabber.repositories.responses.directmessages.RankedRecipient;
|
||||||
|
import awais.instagrabber.repositories.responses.directmessages.RankedRecipientsResponse;
|
||||||
|
|
||||||
|
public class RankedRecipientsCache {
|
||||||
|
private static final Object LOCK = new Object();
|
||||||
|
|
||||||
|
private static RankedRecipientsCache instance;
|
||||||
|
|
||||||
|
public static RankedRecipientsCache getInstance() {
|
||||||
|
if (instance == null) {
|
||||||
|
synchronized (LOCK) {
|
||||||
|
if (instance == null) {
|
||||||
|
instance = new RankedRecipientsCache();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
private LocalDateTime lastUpdatedOn;
|
||||||
|
private RankedRecipientsResponse response;
|
||||||
|
private boolean updateInitiated = false;
|
||||||
|
private boolean failed = false;
|
||||||
|
|
||||||
|
private RankedRecipientsCache() {}
|
||||||
|
|
||||||
|
public List<RankedRecipient> getRankedRecipients() {
|
||||||
|
if (response != null) {
|
||||||
|
return response.getRankedRecipients();
|
||||||
|
}
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRankedRecipientsResponse(final RankedRecipientsResponse response) {
|
||||||
|
this.response = response;
|
||||||
|
lastUpdatedOn = LocalDateTime.now();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isExpired() {
|
||||||
|
if (lastUpdatedOn == null || response == null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
final long expiresInSecs = response.getExpires();
|
||||||
|
return LocalDateTime.now().isAfter(lastUpdatedOn.plus(expiresInSecs, ChronoUnit.SECONDS));
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isUpdateInitiated() {
|
||||||
|
return updateInitiated;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUpdateInitiated(final boolean updateInitiated) {
|
||||||
|
this.updateInitiated = updateInitiated;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isFailed() {
|
||||||
|
return failed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFailed(final boolean failed) {
|
||||||
|
this.failed = failed;
|
||||||
|
}
|
||||||
|
}
|
@ -27,12 +27,14 @@ import java.util.Collections;
|
|||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
import java.util.Set;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import awais.instagrabber.customviews.emoji.Emoji;
|
import awais.instagrabber.customviews.emoji.Emoji;
|
||||||
import awais.instagrabber.models.Resource;
|
import awais.instagrabber.models.Resource;
|
||||||
import awais.instagrabber.models.UploadVideoOptions;
|
import awais.instagrabber.models.UploadVideoOptions;
|
||||||
|
import awais.instagrabber.models.enums.DirectItemType;
|
||||||
import awais.instagrabber.repositories.requests.UploadFinishOptions;
|
import awais.instagrabber.repositories.requests.UploadFinishOptions;
|
||||||
import awais.instagrabber.repositories.requests.directmessages.BroadcastOptions.ThreadIdOrUserIds;
|
import awais.instagrabber.repositories.requests.directmessages.BroadcastOptions.ThreadIdOrUserIds;
|
||||||
import awais.instagrabber.repositories.responses.User;
|
import awais.instagrabber.repositories.responses.User;
|
||||||
@ -44,6 +46,7 @@ import awais.instagrabber.repositories.responses.directmessages.DirectThreadBroa
|
|||||||
import awais.instagrabber.repositories.responses.directmessages.DirectThreadBroadcastResponseMessageMetadata;
|
import awais.instagrabber.repositories.responses.directmessages.DirectThreadBroadcastResponseMessageMetadata;
|
||||||
import awais.instagrabber.repositories.responses.directmessages.DirectThreadBroadcastResponsePayload;
|
import awais.instagrabber.repositories.responses.directmessages.DirectThreadBroadcastResponsePayload;
|
||||||
import awais.instagrabber.repositories.responses.directmessages.DirectThreadFeedResponse;
|
import awais.instagrabber.repositories.responses.directmessages.DirectThreadFeedResponse;
|
||||||
|
import awais.instagrabber.repositories.responses.directmessages.RankedRecipient;
|
||||||
import awais.instagrabber.utils.BitmapUtils;
|
import awais.instagrabber.utils.BitmapUtils;
|
||||||
import awais.instagrabber.utils.Constants;
|
import awais.instagrabber.utils.Constants;
|
||||||
import awais.instagrabber.utils.CookieUtils;
|
import awais.instagrabber.utils.CookieUtils;
|
||||||
@ -926,4 +929,94 @@ public class DirectThreadViewModel extends AndroidViewModel {
|
|||||||
Log.e(TAG, "onResponse: ", e);
|
Log.e(TAG, "onResponse: ", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void forward(final Set<RankedRecipient> recipients, final DirectItem itemToForward) {
|
||||||
|
if (recipients == null || itemToForward == null) return;
|
||||||
|
for (final RankedRecipient recipient : recipients) {
|
||||||
|
forward(recipient, itemToForward);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void forward(final RankedRecipient recipient, final DirectItem itemToForward) {
|
||||||
|
if (recipient == null || itemToForward == null) return;
|
||||||
|
if (recipient.getThread() == null && recipient.getUser() != null) {
|
||||||
|
// create thread and forward
|
||||||
|
final Call<DirectThread> createThreadRequest = service.createThread(Collections.singletonList(recipient.getUser().getPk()), null);
|
||||||
|
createThreadRequest.enqueue(new Callback<DirectThread>() {
|
||||||
|
@Override
|
||||||
|
public void onResponse(@NonNull final Call<DirectThread> call, @NonNull final Response<DirectThread> response) {
|
||||||
|
if (!response.isSuccessful()) {
|
||||||
|
if (response.errorBody() != null) {
|
||||||
|
try {
|
||||||
|
final String string = response.errorBody().string();
|
||||||
|
final String msg = String.format(Locale.US,
|
||||||
|
"onResponse: url: %s, responseCode: %d, errorBody: %s",
|
||||||
|
call.request().url().toString(),
|
||||||
|
response.code(),
|
||||||
|
string);
|
||||||
|
Log.e(TAG, msg);
|
||||||
|
} catch (IOException e) {
|
||||||
|
Log.e(TAG, "onResponse: ", e);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Log.e(TAG, "onResponse: request was not successful and response error body was null");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final DirectThread thread = response.body();
|
||||||
|
if (thread == null) {
|
||||||
|
Log.e(TAG, "onResponse: thread is null");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
forward(thread, itemToForward);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(@NonNull final Call<DirectThread> call, @NonNull final Throwable t) {
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (recipient.getThread() != null) {
|
||||||
|
// just forward
|
||||||
|
final DirectThread thread = recipient.getThread();
|
||||||
|
forward(thread, itemToForward);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void forward(@NonNull final DirectThread thread, @NonNull final DirectItem itemToForward) {
|
||||||
|
final DirectItemType itemType = itemToForward.getItemType();
|
||||||
|
final Call<DirectThreadBroadcastResponse> request = service.forward(thread.getThreadId(),
|
||||||
|
itemType.getName(),
|
||||||
|
threadId,
|
||||||
|
itemToForward.getItemId());
|
||||||
|
request.enqueue(new Callback<DirectThreadBroadcastResponse>() {
|
||||||
|
@Override
|
||||||
|
public void onResponse(@NonNull final Call<DirectThreadBroadcastResponse> call,
|
||||||
|
@NonNull final Response<DirectThreadBroadcastResponse> response) {
|
||||||
|
if (response.isSuccessful()) return;
|
||||||
|
if (response.errorBody() != null) {
|
||||||
|
try {
|
||||||
|
final String string = response.errorBody().string();
|
||||||
|
final String msg = String.format(Locale.US,
|
||||||
|
"onResponse: url: %s, responseCode: %d, errorBody: %s",
|
||||||
|
call.request().url().toString(),
|
||||||
|
response.code(),
|
||||||
|
string);
|
||||||
|
Log.e(TAG, msg);
|
||||||
|
} catch (IOException e) {
|
||||||
|
Log.e(TAG, "onResponse: ", e);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Log.e(TAG, "onResponse: request was not successful and response error body was null");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(@NonNull final Call<DirectThreadBroadcastResponse> call, @NonNull final Throwable t) {
|
||||||
|
Log.e(TAG, "onFailure: ", t);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,51 +3,84 @@ package awais.instagrabber.viewmodels;
|
|||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
import androidx.lifecycle.LiveData;
|
import androidx.lifecycle.LiveData;
|
||||||
import androidx.lifecycle.MutableLiveData;
|
import androidx.lifecycle.MutableLiveData;
|
||||||
import androidx.lifecycle.ViewModel;
|
import androidx.lifecycle.ViewModel;
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import awais.instagrabber.fragments.UserSearchFragment;
|
||||||
import awais.instagrabber.models.Resource;
|
import awais.instagrabber.models.Resource;
|
||||||
import awais.instagrabber.repositories.responses.User;
|
import awais.instagrabber.repositories.responses.User;
|
||||||
import awais.instagrabber.repositories.responses.UserSearchResponse;
|
import awais.instagrabber.repositories.responses.UserSearchResponse;
|
||||||
|
import awais.instagrabber.repositories.responses.directmessages.RankedRecipient;
|
||||||
|
import awais.instagrabber.repositories.responses.directmessages.RankedRecipientsResponse;
|
||||||
|
import awais.instagrabber.utils.Constants;
|
||||||
|
import awais.instagrabber.utils.CookieUtils;
|
||||||
import awais.instagrabber.utils.Debouncer;
|
import awais.instagrabber.utils.Debouncer;
|
||||||
|
import awais.instagrabber.utils.RankedRecipientsCache;
|
||||||
|
import awais.instagrabber.utils.TextUtils;
|
||||||
|
import awais.instagrabber.webservices.DirectMessagesService;
|
||||||
import awais.instagrabber.webservices.UserService;
|
import awais.instagrabber.webservices.UserService;
|
||||||
import okhttp3.ResponseBody;
|
import okhttp3.ResponseBody;
|
||||||
import retrofit2.Call;
|
import retrofit2.Call;
|
||||||
import retrofit2.Callback;
|
import retrofit2.Callback;
|
||||||
import retrofit2.Response;
|
import retrofit2.Response;
|
||||||
|
|
||||||
|
import static awais.instagrabber.utils.Utils.settingsHelper;
|
||||||
|
|
||||||
public class UserSearchViewModel extends ViewModel {
|
public class UserSearchViewModel extends ViewModel {
|
||||||
private static final String TAG = UserSearchViewModel.class.getSimpleName();
|
private static final String TAG = UserSearchViewModel.class.getSimpleName();
|
||||||
public static final String DEBOUNCE_KEY = "search";
|
public static final String DEBOUNCE_KEY = "search";
|
||||||
|
|
||||||
private String prevQuery;
|
private String prevQuery;
|
||||||
private String currentQuery;
|
private String currentQuery;
|
||||||
private Call<UserSearchResponse> searchRequest;
|
private Call<?> searchRequest;
|
||||||
|
private long[] hideUserIds;
|
||||||
|
private String[] hideThreadIds;
|
||||||
|
private UserSearchFragment.SearchMode searchMode;
|
||||||
|
private boolean showGroups;
|
||||||
|
private boolean waitingForCache;
|
||||||
|
private boolean showCachedResults;
|
||||||
|
|
||||||
private final MutableLiveData<Resource<List<User>>> users = new MutableLiveData<>();
|
private final MutableLiveData<Resource<List<RankedRecipient>>> recipients = new MutableLiveData<>();
|
||||||
private final MutableLiveData<Boolean> showAction = new MutableLiveData<>(false);
|
private final MutableLiveData<Boolean> showAction = new MutableLiveData<>(false);
|
||||||
private final Debouncer<String> searchDebouncer;
|
private final Debouncer<String> searchDebouncer;
|
||||||
private final Set<User> selectedUsers = new HashSet<>();
|
private final Set<RankedRecipient> selectedRecipients = new HashSet<>();
|
||||||
private final UserService userService;
|
private final UserService userService;
|
||||||
private long[] hideUserIds;
|
private final DirectMessagesService directMessagesService;
|
||||||
|
private final RankedRecipientsCache rankedRecipientsCache;
|
||||||
|
|
||||||
public UserSearchViewModel() {
|
public UserSearchViewModel() {
|
||||||
|
final String cookie = settingsHelper.getString(Constants.COOKIE);
|
||||||
|
final String csrfToken = CookieUtils.getCsrfTokenFromCookie(cookie);
|
||||||
|
final long viewerId = CookieUtils.getUserIdFromCookie(cookie);
|
||||||
|
final String deviceUuid = settingsHelper.getString(Constants.DEVICE_UUID);
|
||||||
|
if (TextUtils.isEmpty(csrfToken) || viewerId <= 0 || TextUtils.isEmpty(deviceUuid)) {
|
||||||
|
throw new IllegalArgumentException("User is not logged in!");
|
||||||
|
}
|
||||||
userService = UserService.getInstance();
|
userService = UserService.getInstance();
|
||||||
|
directMessagesService = DirectMessagesService.getInstance(csrfToken, viewerId, deviceUuid);
|
||||||
|
rankedRecipientsCache = RankedRecipientsCache.getInstance();
|
||||||
|
if ((rankedRecipientsCache.isFailed() || rankedRecipientsCache.isExpired()) && !rankedRecipientsCache.isUpdateInitiated()) {
|
||||||
|
updateRankedRecipientCache();
|
||||||
|
}
|
||||||
final Debouncer.Callback<String> searchCallback = new Debouncer.Callback<String>() {
|
final Debouncer.Callback<String> searchCallback = new Debouncer.Callback<String>() {
|
||||||
@Override
|
@Override
|
||||||
public void call(final String key) {
|
public void call(final String key) {
|
||||||
if (userService == null || (currentQuery != null && currentQuery.equalsIgnoreCase(prevQuery))) return;
|
if (currentQuery != null && currentQuery.equalsIgnoreCase(prevQuery)) return;
|
||||||
searchRequest = userService.search(currentQuery);
|
sendSearchRequest();
|
||||||
handleRequest(searchRequest);
|
|
||||||
prevQuery = currentQuery;
|
prevQuery = currentQuery;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -59,53 +92,202 @@ public class UserSearchViewModel extends ViewModel {
|
|||||||
searchDebouncer = new Debouncer<>(searchCallback, 1000);
|
searchDebouncer = new Debouncer<>(searchCallback, 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
public LiveData<Resource<List<User>>> getUsers() {
|
private void updateRankedRecipientCache() {
|
||||||
return users;
|
rankedRecipientsCache.setUpdateInitiated(true);
|
||||||
}
|
final Call<RankedRecipientsResponse> request = directMessagesService.rankedRecipients(null, null, null);
|
||||||
|
request.enqueue(new Callback<RankedRecipientsResponse>() {
|
||||||
public void search(final String query) {
|
|
||||||
currentQuery = query;
|
|
||||||
users.postValue(Resource.loading(null));
|
|
||||||
searchDebouncer.call(DEBOUNCE_KEY);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void cleanup() {
|
|
||||||
searchDebouncer.terminate();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void handleRequest(final Call<UserSearchResponse> request) {
|
|
||||||
request.enqueue(new Callback<UserSearchResponse>() {
|
|
||||||
@Override
|
@Override
|
||||||
public void onResponse(@NonNull final Call<UserSearchResponse> call, @NonNull final Response<UserSearchResponse> response) {
|
public void onResponse(@NonNull final Call<RankedRecipientsResponse> call, @NonNull final Response<RankedRecipientsResponse> response) {
|
||||||
if (!response.isSuccessful()) {
|
if (!response.isSuccessful()) {
|
||||||
handleErrorResponse(response);
|
handleErrorResponse(response, false);
|
||||||
|
rankedRecipientsCache.setFailed(true);
|
||||||
|
rankedRecipientsCache.setUpdateInitiated(false);
|
||||||
|
continueSearchIfRequired();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final UserSearchResponse userSearchResponse = response.body();
|
if (response.body() == null) {
|
||||||
if (userSearchResponse == null) return;
|
Log.e(TAG, "onResponse: response body is null");
|
||||||
handleResponse(userSearchResponse);
|
rankedRecipientsCache.setUpdateInitiated(false);
|
||||||
|
rankedRecipientsCache.setFailed(true);
|
||||||
|
continueSearchIfRequired();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
rankedRecipientsCache.setRankedRecipientsResponse(response.body());
|
||||||
|
rankedRecipientsCache.setUpdateInitiated(false);
|
||||||
|
continueSearchIfRequired();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onFailure(@NonNull final Call<UserSearchResponse> call, @NonNull final Throwable t) {
|
public void onFailure(@NonNull final Call<RankedRecipientsResponse> call, @NonNull final Throwable t) {
|
||||||
|
Log.e(TAG, "onFailure: ", t);
|
||||||
|
rankedRecipientsCache.setUpdateInitiated(false);
|
||||||
|
rankedRecipientsCache.setFailed(true);
|
||||||
|
continueSearchIfRequired();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleResponse(final UserSearchResponse userSearchResponse) {
|
private void continueSearchIfRequired() {
|
||||||
users.postValue(Resource.success(userSearchResponse
|
if (!waitingForCache) {
|
||||||
.getUsers()
|
if (showCachedResults) {
|
||||||
.stream()
|
recipients.postValue(Resource.success(getCachedRecipients()));
|
||||||
.filter(directUser -> Arrays.binarySearch(hideUserIds, directUser.getPk()) < 0)
|
}
|
||||||
.collect(Collectors.toList())
|
return;
|
||||||
));
|
}
|
||||||
|
waitingForCache = false;
|
||||||
|
sendSearchRequest();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleErrorResponse(final Response<UserSearchResponse> response) {
|
public LiveData<Resource<List<RankedRecipient>>> getRecipients() {
|
||||||
|
return recipients;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void search(@Nullable final String query) {
|
||||||
|
currentQuery = query;
|
||||||
|
if (TextUtils.isEmpty(query)) {
|
||||||
|
cancelSearch();
|
||||||
|
if (showCachedResults) {
|
||||||
|
recipients.postValue(Resource.success(getCachedRecipients()));
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
recipients.postValue(Resource.loading(getCachedRecipients()));
|
||||||
|
searchDebouncer.call(DEBOUNCE_KEY);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sendSearchRequest() {
|
||||||
|
if (!rankedRecipientsCache.isFailed()) { // to avoid infinite loop in case of any network issues
|
||||||
|
if (rankedRecipientsCache.isUpdateInitiated()) {
|
||||||
|
// wait for cache first
|
||||||
|
waitingForCache = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (rankedRecipientsCache.isExpired()) {
|
||||||
|
// update cache first
|
||||||
|
updateRankedRecipientCache();
|
||||||
|
waitingForCache = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
switch (searchMode) {
|
||||||
|
case RAVEN:
|
||||||
|
case RESHARE:
|
||||||
|
rankedRecipientSearch();
|
||||||
|
break;
|
||||||
|
case USER_SEARCH:
|
||||||
|
default:
|
||||||
|
defaultUserSearch();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void defaultUserSearch() {
|
||||||
|
searchRequest = userService.search(currentQuery);
|
||||||
|
//noinspection unchecked
|
||||||
|
handleRequest((Call<UserSearchResponse>) searchRequest);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void rankedRecipientSearch() {
|
||||||
|
searchRequest = directMessagesService.rankedRecipients(searchMode.getName(), showGroups, currentQuery);
|
||||||
|
//noinspection unchecked
|
||||||
|
handleRankedRecipientRequest((Call<RankedRecipientsResponse>) searchRequest);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void handleRankedRecipientRequest(@NonNull final Call<RankedRecipientsResponse> request) {
|
||||||
|
request.enqueue(new Callback<RankedRecipientsResponse>() {
|
||||||
|
@Override
|
||||||
|
public void onResponse(@NonNull final Call<RankedRecipientsResponse> call, @NonNull final Response<RankedRecipientsResponse> response) {
|
||||||
|
if (!response.isSuccessful()) {
|
||||||
|
handleErrorResponse(response, true);
|
||||||
|
searchRequest = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final RankedRecipientsResponse rankedRecipientsResponse = response.body();
|
||||||
|
if (rankedRecipientsResponse == null) {
|
||||||
|
recipients.postValue(Resource.error("Response is null!", getCachedRecipients()));
|
||||||
|
searchRequest = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final List<RankedRecipient> list = rankedRecipientsResponse.getRankedRecipients();
|
||||||
|
recipients.postValue(Resource.success(mergeResponseWithCache(list)));
|
||||||
|
searchRequest = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(@NonNull final Call<RankedRecipientsResponse> call, @NonNull final Throwable t) {
|
||||||
|
Log.e(TAG, "onFailure: ", t);
|
||||||
|
recipients.postValue(Resource.error(t.getMessage(), getCachedRecipients()));
|
||||||
|
searchRequest = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleRequest(@NonNull final Call<UserSearchResponse> request) {
|
||||||
|
request.enqueue(new Callback<UserSearchResponse>() {
|
||||||
|
@Override
|
||||||
|
public void onResponse(@NonNull final Call<UserSearchResponse> call, @NonNull final Response<UserSearchResponse> response) {
|
||||||
|
if (!response.isSuccessful()) {
|
||||||
|
handleErrorResponse(response, true);
|
||||||
|
searchRequest = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final UserSearchResponse userSearchResponse = response.body();
|
||||||
|
if (userSearchResponse == null) {
|
||||||
|
recipients.postValue(Resource.error("Response is null!", getCachedRecipients()));
|
||||||
|
searchRequest = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final List<RankedRecipient> list = userSearchResponse
|
||||||
|
.getUsers()
|
||||||
|
.stream()
|
||||||
|
.map(RankedRecipient::of)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
recipients.postValue(Resource.success(mergeResponseWithCache(list)));
|
||||||
|
searchRequest = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(@NonNull final Call<UserSearchResponse> call, @NonNull final Throwable t) {
|
||||||
|
Log.e(TAG, "onFailure: ", t);
|
||||||
|
recipients.postValue(Resource.error(t.getMessage(), getCachedRecipients()));
|
||||||
|
searchRequest = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<RankedRecipient> mergeResponseWithCache(@NonNull final List<RankedRecipient> list) {
|
||||||
|
final Iterator<RankedRecipient> iterator = list.stream()
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.filter(this::filterValidRecipients)
|
||||||
|
.filter(this::filterOutGroups)
|
||||||
|
.filter(this::filterIdsToHide)
|
||||||
|
.iterator();
|
||||||
|
return ImmutableList.<RankedRecipient>builder()
|
||||||
|
.addAll(getCachedRecipients()) // add cached results first
|
||||||
|
.addAll(iterator)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private List<RankedRecipient> getCachedRecipients() {
|
||||||
|
final List<RankedRecipient> rankedRecipients = rankedRecipientsCache.getRankedRecipients();
|
||||||
|
final List<RankedRecipient> list = rankedRecipients != null ? rankedRecipients : Collections.emptyList();
|
||||||
|
return list.stream()
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.filter(this::filterValidRecipients)
|
||||||
|
.filter(this::filterOutGroups)
|
||||||
|
.filter(this::filterQuery)
|
||||||
|
.filter(this::filterIdsToHide)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleErrorResponse(final Response<?> response, boolean updateResource) {
|
||||||
final ResponseBody errorBody = response.errorBody();
|
final ResponseBody errorBody = response.errorBody();
|
||||||
if (errorBody == null) {
|
if (errorBody == null) {
|
||||||
users.postValue(Resource.error("Request failed!", Collections.emptyList()));
|
if (updateResource) {
|
||||||
|
recipients.postValue(Resource.error("Request failed!", getCachedRecipients()));
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
String errorString;
|
String errorString;
|
||||||
@ -116,24 +298,30 @@ public class UserSearchViewModel extends ViewModel {
|
|||||||
Log.e(TAG, "handleErrorResponse: ", e);
|
Log.e(TAG, "handleErrorResponse: ", e);
|
||||||
errorString = e.getMessage();
|
errorString = e.getMessage();
|
||||||
}
|
}
|
||||||
users.postValue(Resource.error(errorString, Collections.emptyList()));
|
if (updateResource) {
|
||||||
|
recipients.postValue(Resource.error(errorString, getCachedRecipients()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setSelectedUser(final User user, final boolean selected) {
|
public void cleanup() {
|
||||||
|
searchDebouncer.terminate();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSelectedRecipient(final RankedRecipient recipient, final boolean selected) {
|
||||||
if (selected) {
|
if (selected) {
|
||||||
selectedUsers.add(user);
|
selectedRecipients.add(recipient);
|
||||||
} else {
|
} else {
|
||||||
selectedUsers.remove(user);
|
selectedRecipients.remove(recipient);
|
||||||
}
|
}
|
||||||
showAction.postValue(!selectedUsers.isEmpty());
|
showAction.postValue(!selectedRecipients.isEmpty());
|
||||||
}
|
}
|
||||||
|
|
||||||
public Set<User> getSelectedUsers() {
|
public Set<RankedRecipient> getSelectedRecipients() {
|
||||||
return selectedUsers;
|
return selectedRecipients;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void clearResults() {
|
public void clearResults() {
|
||||||
users.postValue(Resource.success(Collections.emptyList()));
|
recipients.postValue(Resource.success(Collections.emptyList()));
|
||||||
prevQuery = "";
|
prevQuery = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -149,7 +337,78 @@ public class UserSearchViewModel extends ViewModel {
|
|||||||
return showAction;
|
return showAction;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setSearchMode(final UserSearchFragment.SearchMode searchMode) {
|
||||||
|
this.searchMode = searchMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setShowGroups(final boolean showGroups) {
|
||||||
|
this.showGroups = showGroups;
|
||||||
|
}
|
||||||
|
|
||||||
public void setHideUserIds(final long[] hideUserIds) {
|
public void setHideUserIds(final long[] hideUserIds) {
|
||||||
this.hideUserIds = hideUserIds;
|
if (hideUserIds != null) {
|
||||||
|
final long[] copy = Arrays.copyOf(hideUserIds, hideUserIds.length);
|
||||||
|
Arrays.sort(copy);
|
||||||
|
this.hideUserIds = copy;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.hideUserIds = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setHideThreadIds(final String[] hideThreadIds) {
|
||||||
|
if (hideThreadIds != null) {
|
||||||
|
final String[] copy = Arrays.copyOf(hideThreadIds, hideThreadIds.length);
|
||||||
|
Arrays.sort(copy);
|
||||||
|
this.hideThreadIds = copy;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.hideThreadIds = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean filterOutGroups(@NonNull RankedRecipient recipient) {
|
||||||
|
// if showGroups is false, remove groups from the list
|
||||||
|
if (showGroups || recipient.getThread() == null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return !recipient.getThread().isGroup();
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean filterValidRecipients(@NonNull RankedRecipient recipient) {
|
||||||
|
// check if both user and thread are null
|
||||||
|
return recipient.getUser() != null || recipient.getThread() != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean filterIdsToHide(@NonNull RankedRecipient recipient) {
|
||||||
|
if (hideThreadIds != null && recipient.getThread() != null) {
|
||||||
|
return Arrays.binarySearch(hideThreadIds, recipient.getThread().getThreadId()) < 0;
|
||||||
|
}
|
||||||
|
if (hideUserIds != null) {
|
||||||
|
long pk = -1;
|
||||||
|
if (recipient.getUser() != null) {
|
||||||
|
pk = recipient.getUser().getPk();
|
||||||
|
} else if (recipient.getThread() != null && !recipient.getThread().isGroup()) {
|
||||||
|
final User user = recipient.getThread().getUsers().get(0);
|
||||||
|
pk = user.getPk();
|
||||||
|
}
|
||||||
|
return Arrays.binarySearch(hideUserIds, pk) < 0;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean filterQuery(@NonNull RankedRecipient recipient) {
|
||||||
|
if (TextUtils.isEmpty(currentQuery)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (recipient.getThread() != null) {
|
||||||
|
return recipient.getThread().getThreadTitle().toLowerCase().contains(currentQuery.toLowerCase());
|
||||||
|
}
|
||||||
|
return recipient.getUser().getUsername().toLowerCase().contains(currentQuery.toLowerCase())
|
||||||
|
|| recipient.getUser().getFullName().toLowerCase().contains(currentQuery.toLowerCase());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void showCachedResults() {
|
||||||
|
this.showCachedResults = true;
|
||||||
|
if (rankedRecipientsCache.isUpdateInitiated()) return;
|
||||||
|
recipients.postValue(Resource.success(getCachedRecipients()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package awais.instagrabber.webservices;
|
package awais.instagrabber.webservices;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableMap;
|
import com.google.common.collect.ImmutableMap;
|
||||||
|
|
||||||
@ -13,6 +14,7 @@ import java.util.List;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import awais.instagrabber.repositories.DirectMessagesRepository;
|
import awais.instagrabber.repositories.DirectMessagesRepository;
|
||||||
import awais.instagrabber.repositories.requests.directmessages.BroadcastOptions;
|
import awais.instagrabber.repositories.requests.directmessages.BroadcastOptions;
|
||||||
@ -26,9 +28,11 @@ import awais.instagrabber.repositories.requests.directmessages.VideoBroadcastOpt
|
|||||||
import awais.instagrabber.repositories.requests.directmessages.VoiceBroadcastOptions;
|
import awais.instagrabber.repositories.requests.directmessages.VoiceBroadcastOptions;
|
||||||
import awais.instagrabber.repositories.responses.directmessages.DirectBadgeCount;
|
import awais.instagrabber.repositories.responses.directmessages.DirectBadgeCount;
|
||||||
import awais.instagrabber.repositories.responses.directmessages.DirectInboxResponse;
|
import awais.instagrabber.repositories.responses.directmessages.DirectInboxResponse;
|
||||||
|
import awais.instagrabber.repositories.responses.directmessages.DirectThread;
|
||||||
import awais.instagrabber.repositories.responses.directmessages.DirectThreadBroadcastResponse;
|
import awais.instagrabber.repositories.responses.directmessages.DirectThreadBroadcastResponse;
|
||||||
import awais.instagrabber.repositories.responses.directmessages.DirectThreadDetailsChangeResponse;
|
import awais.instagrabber.repositories.responses.directmessages.DirectThreadDetailsChangeResponse;
|
||||||
import awais.instagrabber.repositories.responses.directmessages.DirectThreadFeedResponse;
|
import awais.instagrabber.repositories.responses.directmessages.DirectThreadFeedResponse;
|
||||||
|
import awais.instagrabber.repositories.responses.directmessages.RankedRecipientsResponse;
|
||||||
import awais.instagrabber.utils.TextUtils;
|
import awais.instagrabber.utils.TextUtils;
|
||||||
import awais.instagrabber.utils.Utils;
|
import awais.instagrabber.utils.Utils;
|
||||||
import retrofit2.Call;
|
import retrofit2.Call;
|
||||||
@ -247,4 +251,56 @@ public class DirectMessagesService extends BaseService {
|
|||||||
);
|
);
|
||||||
return repository.deleteItem(threadId, itemId, form);
|
return repository.deleteItem(threadId, itemId, form);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Call<RankedRecipientsResponse> rankedRecipients(@Nullable final String mode,
|
||||||
|
@Nullable final Boolean showThreads,
|
||||||
|
@Nullable final String query) {
|
||||||
|
// String correctedMode = mode;
|
||||||
|
// if (TextUtils.isEmpty(mode) || (!mode.equals("raven") && !mode.equals("reshare"))) {
|
||||||
|
// correctedMode = "raven";
|
||||||
|
// }
|
||||||
|
final ImmutableMap.Builder<String, String> builder = ImmutableMap.builder();
|
||||||
|
if (mode != null) {
|
||||||
|
builder.put("mode", mode);
|
||||||
|
}
|
||||||
|
if (query != null) {
|
||||||
|
builder.put("query", query);
|
||||||
|
}
|
||||||
|
if (showThreads != null) {
|
||||||
|
builder.put("showThreads", String.valueOf(showThreads));
|
||||||
|
}
|
||||||
|
return repository.rankedRecipients(builder.build());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Call<DirectThreadBroadcastResponse> forward(@NonNull final String toThreadId,
|
||||||
|
@NonNull final String itemType,
|
||||||
|
@NonNull final String fromThreadId,
|
||||||
|
@NonNull final String itemId) {
|
||||||
|
final ImmutableMap<String, String> form = ImmutableMap.of(
|
||||||
|
"action", "forward_item",
|
||||||
|
"thread_id", toThreadId,
|
||||||
|
"item_type", itemType,
|
||||||
|
"forwarded_from_thread_id", fromThreadId,
|
||||||
|
"forwarded_from_thread_item_id", itemId
|
||||||
|
);
|
||||||
|
return repository.forward(form);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Call<DirectThread> createThread(@NonNull final List<Long> userIds,
|
||||||
|
@Nullable final String threadTitle) {
|
||||||
|
final List<String> userIdStringList = userIds.stream()
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.map(String::valueOf)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
final ImmutableMap.Builder<String, Object> formBuilder = ImmutableMap.<String, Object>builder()
|
||||||
|
.put("_csrftoken", csrfToken)
|
||||||
|
.put("_uuid", deviceUuid)
|
||||||
|
.put("_uid", userId)
|
||||||
|
.put("recipient_users", new JSONArray(userIdStringList).toString());
|
||||||
|
if (threadTitle != null) {
|
||||||
|
formBuilder.put("thread_title", threadTitle);
|
||||||
|
}
|
||||||
|
final Map<String, String> signedForm = Utils.sign(formBuilder.build());
|
||||||
|
return repository.createThread(signedForm);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -29,7 +29,7 @@
|
|||||||
app:layout_constraintHorizontal_chainStyle="packed"
|
app:layout_constraintHorizontal_chainStyle="packed"
|
||||||
app:layout_constraintStart_toEndOf="@id/ivProfilePic"
|
app:layout_constraintStart_toEndOf="@id/ivProfilePic"
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
tools:visibility="visible" />
|
tools:visibility="gone" />
|
||||||
|
|
||||||
<androidx.appcompat.widget.AppCompatTextView
|
<androidx.appcompat.widget.AppCompatTextView
|
||||||
android:id="@+id/reply_info"
|
android:id="@+id/reply_info"
|
||||||
@ -42,6 +42,7 @@
|
|||||||
app:layout_constraintBottom_toTopOf="@id/reply_container"
|
app:layout_constraintBottom_toTopOf="@id/reply_container"
|
||||||
app:layout_constraintStart_toEndOf="@id/quote_line"
|
app:layout_constraintStart_toEndOf="@id/quote_line"
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:layout_goneMarginStart="0dp"
|
||||||
tools:text="Replied to you" />
|
tools:text="Replied to you" />
|
||||||
|
|
||||||
<FrameLayout
|
<FrameLayout
|
||||||
@ -52,6 +53,7 @@
|
|||||||
android:layout_marginEnd="4dp"
|
android:layout_marginEnd="4dp"
|
||||||
android:layout_marginBottom="4dp"
|
android:layout_marginBottom="4dp"
|
||||||
app:layout_constraintBottom_toTopOf="@id/tvUsername"
|
app:layout_constraintBottom_toTopOf="@id/tvUsername"
|
||||||
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintHorizontal_bias="1"
|
app:layout_constraintHorizontal_bias="1"
|
||||||
app:layout_constraintHorizontal_chainStyle="packed"
|
app:layout_constraintHorizontal_chainStyle="packed"
|
||||||
@ -77,7 +79,7 @@
|
|||||||
android:textSize="14sp"
|
android:textSize="14sp"
|
||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
tools:text="Some message"
|
tools:text="Some message"
|
||||||
tools:visibility="visible" />
|
tools:visibility="gone" />
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
|
|
||||||
<awais.instagrabber.customviews.CircularImageView
|
<awais.instagrabber.customviews.CircularImageView
|
||||||
|
@ -24,6 +24,17 @@
|
|||||||
app:roundAsCircle="true"
|
app:roundAsCircle="true"
|
||||||
tools:background="@mipmap/ic_launcher" />
|
tools:background="@mipmap/ic_launcher" />
|
||||||
|
|
||||||
|
<com.facebook.drawee.view.SimpleDraweeView
|
||||||
|
android:id="@+id/profile_pic2"
|
||||||
|
android:layout_width="@dimen/dm_inbox_avatar_size"
|
||||||
|
android:layout_height="@dimen/dm_inbox_avatar_size"
|
||||||
|
app:actualImageScaleType="centerCrop"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:roundAsCircle="true"
|
||||||
|
tools:background="@mipmap/ic_launcher" />
|
||||||
|
|
||||||
<androidx.appcompat.widget.AppCompatTextView
|
<androidx.appcompat.widget.AppCompatTextView
|
||||||
android:id="@+id/full_name"
|
android:id="@+id/full_name"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
@ -55,7 +66,8 @@
|
|||||||
app:layout_constraintEnd_toStartOf="@id/select"
|
app:layout_constraintEnd_toStartOf="@id/select"
|
||||||
app:layout_constraintStart_toEndOf="@id/full_name"
|
app:layout_constraintStart_toEndOf="@id/full_name"
|
||||||
app:layout_constraintTop_toTopOf="@id/full_name"
|
app:layout_constraintTop_toTopOf="@id/full_name"
|
||||||
tools:text="Admin" />
|
tools:text="Admin"
|
||||||
|
tools:visibility="gone" />
|
||||||
|
|
||||||
<androidx.appcompat.widget.AppCompatTextView
|
<androidx.appcompat.widget.AppCompatTextView
|
||||||
android:id="@+id/username"
|
android:id="@+id/username"
|
||||||
@ -68,7 +80,8 @@
|
|||||||
app:layout_constraintEnd_toStartOf="@id/select"
|
app:layout_constraintEnd_toStartOf="@id/select"
|
||||||
app:layout_constraintStart_toStartOf="@id/full_name"
|
app:layout_constraintStart_toStartOf="@id/full_name"
|
||||||
app:layout_constraintTop_toBottomOf="@id/full_name"
|
app:layout_constraintTop_toBottomOf="@id/full_name"
|
||||||
tools:text="username" />
|
tools:text="username"
|
||||||
|
tools:visibility="gone" />
|
||||||
|
|
||||||
<androidx.appcompat.widget.AppCompatImageView
|
<androidx.appcompat.widget.AppCompatImageView
|
||||||
android:id="@+id/select"
|
android:id="@+id/select"
|
||||||
@ -83,7 +96,7 @@
|
|||||||
app:layout_constraintTop_toTopOf="@id/profile_pic"
|
app:layout_constraintTop_toTopOf="@id/profile_pic"
|
||||||
app:srcCompat="@drawable/ic_circle_check"
|
app:srcCompat="@drawable/ic_circle_check"
|
||||||
app:tint="@color/ic_circle_check_tint"
|
app:tint="@color/ic_circle_check_tint"
|
||||||
tools:visibility="gone" />
|
tools:visibility="visible" />
|
||||||
|
|
||||||
<androidx.appcompat.widget.AppCompatImageView
|
<androidx.appcompat.widget.AppCompatImageView
|
||||||
android:id="@+id/secondary_image"
|
android:id="@+id/secondary_image"
|
||||||
|
@ -86,23 +86,23 @@
|
|||||||
<action
|
<action
|
||||||
android:id="@+id/action_global_user_search"
|
android:id="@+id/action_global_user_search"
|
||||||
app:destination="@id/user_search_nav_graph">
|
app:destination="@id/user_search_nav_graph">
|
||||||
<argument
|
<!--<argument-->
|
||||||
android:name="multiple"
|
<!-- android:name="multiple"-->
|
||||||
app:argType="boolean" />
|
<!-- app:argType="boolean" />-->
|
||||||
|
|
||||||
<argument
|
<!--<argument-->
|
||||||
android:name="title"
|
<!-- android:name="title"-->
|
||||||
app:argType="string"
|
<!-- app:argType="string"-->
|
||||||
app:nullable="true" />
|
<!-- app:nullable="true" />-->
|
||||||
|
|
||||||
<argument
|
<!--<argument-->
|
||||||
android:name="action_label"
|
<!-- android:name="action_label"-->
|
||||||
app:argType="string"
|
<!-- app:argType="string"-->
|
||||||
app:nullable="true" />
|
<!-- app:nullable="true" />-->
|
||||||
|
|
||||||
<argument
|
<!--<argument-->
|
||||||
android:name="hideUserIds"
|
<!-- android:name="hideUserIds"-->
|
||||||
app:argType="long[]" />
|
<!-- app:argType="long[]" />-->
|
||||||
</action>
|
</action>
|
||||||
|
|
||||||
<fragment
|
<fragment
|
||||||
|
@ -1,33 +1,61 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
|
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:id="@+id/user_search_nav_graph"
|
android:id="@+id/user_search_nav_graph"
|
||||||
app:startDestination="@id/user_search">
|
app:startDestination="@id/user_search">
|
||||||
|
|
||||||
<fragment
|
<fragment
|
||||||
android:id="@+id/user_search"
|
android:id="@+id/user_search"
|
||||||
android:name="awais.instagrabber.fragments.UserSearchFragment"
|
android:name="awais.instagrabber.fragments.UserSearchFragment"
|
||||||
android:label="@string/search">
|
android:label="@string/search"
|
||||||
|
tools:layout="@layout/fragment_user_search">
|
||||||
<argument
|
<argument
|
||||||
android:name="multiple"
|
android:name="multiple"
|
||||||
|
android:defaultValue="false"
|
||||||
app:argType="boolean" />
|
app:argType="boolean" />
|
||||||
|
|
||||||
<argument
|
<argument
|
||||||
android:name="title"
|
android:name="title"
|
||||||
|
android:defaultValue="@null"
|
||||||
app:argType="string"
|
app:argType="string"
|
||||||
app:nullable="true" />
|
app:nullable="true" />
|
||||||
|
|
||||||
<argument
|
<argument
|
||||||
android:name="action_label"
|
android:name="action_label"
|
||||||
|
android:defaultValue="@null"
|
||||||
app:argType="string"
|
app:argType="string"
|
||||||
app:nullable="true" />
|
app:nullable="true" />
|
||||||
|
|
||||||
|
<argument
|
||||||
|
android:name="show_groups"
|
||||||
|
android:defaultValue="false"
|
||||||
|
app:argType="boolean" />
|
||||||
|
|
||||||
|
<argument
|
||||||
|
android:name="search_mode"
|
||||||
|
android:defaultValue="USER_SEARCH"
|
||||||
|
app:argType="awais.instagrabber.fragments.UserSearchFragment$SearchMode" />
|
||||||
|
|
||||||
<argument
|
<argument
|
||||||
android:name="hideUserIds"
|
android:name="hideUserIds"
|
||||||
app:argType="long[]" />
|
android:defaultValue="@null"
|
||||||
|
app:argType="long[]"
|
||||||
|
app:nullable="true" />
|
||||||
|
|
||||||
|
<argument
|
||||||
|
android:name="hideThreadIds"
|
||||||
|
android:defaultValue="@null"
|
||||||
|
app:argType="string[]"
|
||||||
|
app:nullable="true" />
|
||||||
|
|
||||||
|
|
||||||
</fragment>
|
</fragment>
|
||||||
|
|
||||||
|
<action
|
||||||
|
android:id="@+id/action_global_user_search"
|
||||||
|
app:destination="@id/user_search" />
|
||||||
|
|
||||||
<!--<action-->
|
<!--<action-->
|
||||||
<!-- android:id="@+id/action_global_user_search"-->
|
<!-- android:id="@+id/action_global_user_search"-->
|
||||||
<!-- app:destination="@id/user_search_nav_graph">-->
|
<!-- app:destination="@id/user_search_nav_graph">-->
|
||||||
|
@ -2,4 +2,5 @@
|
|||||||
<resources>
|
<resources>
|
||||||
<item name="reply" type="id" />
|
<item name="reply" type="id" />
|
||||||
<item name="unsend" type="id" />
|
<item name="unsend" type="id" />
|
||||||
|
<item name="forward" type="id" />
|
||||||
</resources>
|
</resources>
|
@ -398,4 +398,7 @@
|
|||||||
<string name="message">Message</string>
|
<string name="message">Message</string>
|
||||||
<string name="reply">Reply</string>
|
<string name="reply">Reply</string>
|
||||||
<string name="tap_to_remove">Tap to remove</string>
|
<string name="tap_to_remove">Tap to remove</string>
|
||||||
|
<string name="forward">Forward</string>
|
||||||
|
<string name="add">Add</string>
|
||||||
|
<string name="send">Send</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
Loading…
Reference in New Issue
Block a user