mirror of
https://github.com/KokaKiwi/BarInsta
synced 2024-11-22 22:57:29 +00:00
Update DM settings page
This commit is contained in:
parent
093ccc9f00
commit
e1d8e02630
@ -73,6 +73,7 @@ import awais.instagrabber.utils.CookieUtils;
|
|||||||
import awais.instagrabber.utils.FlavorTown;
|
import awais.instagrabber.utils.FlavorTown;
|
||||||
import awais.instagrabber.utils.IntentUtils;
|
import awais.instagrabber.utils.IntentUtils;
|
||||||
import awais.instagrabber.utils.TextUtils;
|
import awais.instagrabber.utils.TextUtils;
|
||||||
|
import awais.instagrabber.utils.Utils;
|
||||||
import awais.instagrabber.utils.emoji.EmojiParser;
|
import awais.instagrabber.utils.emoji.EmojiParser;
|
||||||
|
|
||||||
import static awais.instagrabber.utils.NavigationExtensions.setupWithNavController;
|
import static awais.instagrabber.utils.NavigationExtensions.setupWithNavController;
|
||||||
@ -477,6 +478,10 @@ public class MainActivity extends BaseLanguageActivity implements FragmentManage
|
|||||||
@SuppressLint("RestrictedApi") final Deque<NavBackStackEntry> backStack = navController.getBackStack();
|
@SuppressLint("RestrictedApi") final Deque<NavBackStackEntry> backStack = navController.getBackStack();
|
||||||
setupMenu(backStack.size(), destinationId);
|
setupMenu(backStack.size(), destinationId);
|
||||||
binding.bottomNavView.setVisibility(SHOW_BOTTOM_VIEW_DESTINATIONS.contains(destinationId) ? View.VISIBLE : View.GONE);
|
binding.bottomNavView.setVisibility(SHOW_BOTTOM_VIEW_DESTINATIONS.contains(destinationId) ? View.VISIBLE : View.GONE);
|
||||||
|
|
||||||
|
// explicitly hide keyboard when we navigate
|
||||||
|
final View view = getCurrentFocus();
|
||||||
|
Utils.hideKeyboard(view);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,47 +0,0 @@
|
|||||||
package awais.instagrabber.adapters;
|
|
||||||
|
|
||||||
import android.view.LayoutInflater;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.ViewGroup;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import awais.instagrabber.adapters.viewholder.FollowsViewHolder;
|
|
||||||
import awais.instagrabber.databinding.ItemFollowBinding;
|
|
||||||
import awais.instagrabber.models.ProfileModel;
|
|
||||||
|
|
||||||
public final class DirectMessageMembersAdapter extends RecyclerView.Adapter<FollowsViewHolder> {
|
|
||||||
private final List<ProfileModel> profileModels;
|
|
||||||
private final List<Long> admins;
|
|
||||||
private final View.OnClickListener onClickListener;
|
|
||||||
|
|
||||||
public DirectMessageMembersAdapter(final List<ProfileModel> profileModels,
|
|
||||||
final List<Long> admins,
|
|
||||||
final View.OnClickListener onClickListener) {
|
|
||||||
this.profileModels = profileModels;
|
|
||||||
this.admins = admins;
|
|
||||||
this.onClickListener = onClickListener;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
@Override
|
|
||||||
public FollowsViewHolder onCreateViewHolder(@NonNull final ViewGroup parent, final int viewType) {
|
|
||||||
final LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext());
|
|
||||||
final ItemFollowBinding binding = ItemFollowBinding.inflate(layoutInflater, parent, false);
|
|
||||||
return new FollowsViewHolder(binding);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onBindViewHolder(@NonNull final FollowsViewHolder holder, final int position) {
|
|
||||||
final ProfileModel model = profileModels.get(position);
|
|
||||||
holder.bind(model, admins, onClickListener);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getItemCount() {
|
|
||||||
return profileModels.size();
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,183 @@
|
|||||||
|
package awais.instagrabber.adapters;
|
||||||
|
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.StringRes;
|
||||||
|
import androidx.recyclerview.widget.DiffUtil;
|
||||||
|
import androidx.recyclerview.widget.ListAdapter;
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import awais.instagrabber.R;
|
||||||
|
import awais.instagrabber.adapters.viewholder.DirectUserViewHolder;
|
||||||
|
import awais.instagrabber.databinding.ItemFavSectionHeaderBinding;
|
||||||
|
import awais.instagrabber.databinding.LayoutDmUserItemBinding;
|
||||||
|
import awais.instagrabber.repositories.responses.directmessages.DirectUser;
|
||||||
|
|
||||||
|
public final class DirectUsersAdapter extends ListAdapter<DirectUsersAdapter.DirectUserOrHeader, RecyclerView.ViewHolder> {
|
||||||
|
|
||||||
|
private static final int VIEW_TYPE_HEADER = 0;
|
||||||
|
private static final int VIEW_TYPE_USER = 1;
|
||||||
|
private static final DiffUtil.ItemCallback<DirectUserOrHeader> DIFF_CALLBACK = new DiffUtil.ItemCallback<DirectUserOrHeader>() {
|
||||||
|
@Override
|
||||||
|
public boolean areItemsTheSame(@NonNull final DirectUserOrHeader oldItem, @NonNull final DirectUserOrHeader newItem) {
|
||||||
|
final boolean bothHeaders = oldItem.isHeader() && newItem.isHeader();
|
||||||
|
final boolean bothItems = !oldItem.isHeader() && !newItem.isHeader();
|
||||||
|
boolean areSameType = bothHeaders || bothItems;
|
||||||
|
if (!areSameType) return false;
|
||||||
|
if (bothHeaders) {
|
||||||
|
return oldItem.headerTitle == newItem.headerTitle;
|
||||||
|
}
|
||||||
|
if (oldItem.user != null && newItem.user != null) {
|
||||||
|
return oldItem.user.getPk() == newItem.user.getPk();
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean areContentsTheSame(@NonNull final DirectUserOrHeader oldItem, @NonNull final DirectUserOrHeader newItem) {
|
||||||
|
final boolean bothHeaders = oldItem.isHeader() && newItem.isHeader();
|
||||||
|
final boolean bothItems = !oldItem.isHeader() && !newItem.isHeader();
|
||||||
|
boolean areSameType = bothHeaders || bothItems;
|
||||||
|
if (!areSameType) return false;
|
||||||
|
if (bothHeaders) {
|
||||||
|
return oldItem.headerTitle == newItem.headerTitle;
|
||||||
|
}
|
||||||
|
if (oldItem.user != null && newItem.user != null) {
|
||||||
|
return oldItem.user.getUsername().equals(newItem.user.getUsername()) &&
|
||||||
|
oldItem.user.getFullName().equals(newItem.user.getFullName());
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private final long inviterId;
|
||||||
|
private final OnDirectUserClickListener onClickListener;
|
||||||
|
private final OnDirectUserLongClickListener onLongClickListener;
|
||||||
|
private List<Long> adminUserIds;
|
||||||
|
|
||||||
|
public DirectUsersAdapter(final long inviterId,
|
||||||
|
final OnDirectUserClickListener onClickListener,
|
||||||
|
final OnDirectUserLongClickListener onLongClickListener) {
|
||||||
|
super(DIFF_CALLBACK);
|
||||||
|
this.inviterId = inviterId;
|
||||||
|
this.onClickListener = onClickListener;
|
||||||
|
this.onLongClickListener = onLongClickListener;
|
||||||
|
setHasStableIds(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void submitUsers(final List<DirectUser> users, final List<DirectUser> leftUsers) {
|
||||||
|
if (users == null && leftUsers == null) return;
|
||||||
|
final List<DirectUserOrHeader> userOrHeaders = combineLists(users, leftUsers);
|
||||||
|
submitList(userOrHeaders);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<DirectUserOrHeader> combineLists(final List<DirectUser> users, final List<DirectUser> leftUsers) {
|
||||||
|
final ImmutableList.Builder<DirectUserOrHeader> listBuilder = ImmutableList.builder();
|
||||||
|
if (users != null && !users.isEmpty()) {
|
||||||
|
listBuilder.add(new DirectUserOrHeader(R.string.members));
|
||||||
|
users.stream()
|
||||||
|
.map(DirectUserOrHeader::new)
|
||||||
|
.forEach(listBuilder::add);
|
||||||
|
}
|
||||||
|
if (leftUsers != null && !leftUsers.isEmpty()) {
|
||||||
|
listBuilder.add(new DirectUserOrHeader(R.string.dms_left_users));
|
||||||
|
leftUsers.stream()
|
||||||
|
.map(DirectUserOrHeader::new)
|
||||||
|
.forEach(listBuilder::add);
|
||||||
|
}
|
||||||
|
return listBuilder.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull final ViewGroup parent, final int viewType) {
|
||||||
|
final LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext());
|
||||||
|
switch (viewType) {
|
||||||
|
case VIEW_TYPE_USER:
|
||||||
|
final LayoutDmUserItemBinding binding = LayoutDmUserItemBinding.inflate(layoutInflater, parent, false);
|
||||||
|
return new DirectUserViewHolder(binding, onClickListener, onLongClickListener);
|
||||||
|
case VIEW_TYPE_HEADER:
|
||||||
|
default:
|
||||||
|
final ItemFavSectionHeaderBinding headerBinding = ItemFavSectionHeaderBinding.inflate(layoutInflater, parent, false);
|
||||||
|
return new HeaderViewHolder(headerBinding);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBindViewHolder(@NonNull final RecyclerView.ViewHolder holder, final int position) {
|
||||||
|
if (holder instanceof HeaderViewHolder) {
|
||||||
|
((HeaderViewHolder) holder).bind(getItem(position).headerTitle);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (holder instanceof DirectUserViewHolder) {
|
||||||
|
final DirectUser user = getItem(position).user;
|
||||||
|
((DirectUserViewHolder) holder).bind(position,
|
||||||
|
user,
|
||||||
|
user != null && adminUserIds != null && adminUserIds.contains(user.getPk()),
|
||||||
|
user != null && user.getPk() == inviterId,
|
||||||
|
false,
|
||||||
|
false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getItemViewType(final int position) {
|
||||||
|
final DirectUserOrHeader item = getItem(position);
|
||||||
|
return item.isHeader() ? VIEW_TYPE_HEADER : VIEW_TYPE_USER;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getItemId(final int position) {
|
||||||
|
final DirectUserOrHeader item = getItem(position);
|
||||||
|
return item.isHeader() ? item.headerTitle : item.user.getPk();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAdminUserIds(final List<Long> adminUserIds) {
|
||||||
|
this.adminUserIds = adminUserIds;
|
||||||
|
notifyDataSetChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class DirectUserOrHeader {
|
||||||
|
int headerTitle;
|
||||||
|
DirectUser user;
|
||||||
|
|
||||||
|
public DirectUserOrHeader(final int headerTitle) {
|
||||||
|
this.headerTitle = headerTitle;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DirectUserOrHeader(final DirectUser user) {
|
||||||
|
this.user = user;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean isHeader() {
|
||||||
|
return headerTitle > 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class HeaderViewHolder extends RecyclerView.ViewHolder {
|
||||||
|
private final ItemFavSectionHeaderBinding binding;
|
||||||
|
|
||||||
|
public HeaderViewHolder(@NonNull final ItemFavSectionHeaderBinding binding) {
|
||||||
|
super(binding.getRoot());
|
||||||
|
this.binding = binding;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void bind(@StringRes final int headerTitle) {
|
||||||
|
binding.getRoot().setText(headerTitle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface OnDirectUserClickListener {
|
||||||
|
void onClick(int position, DirectUser user, boolean selected);
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface OnDirectUserLongClickListener {
|
||||||
|
boolean onLongClick(int position, DirectUser user);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,85 @@
|
|||||||
|
package awais.instagrabber.adapters;
|
||||||
|
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.recyclerview.widget.DiffUtil;
|
||||||
|
import androidx.recyclerview.widget.ListAdapter;
|
||||||
|
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import awais.instagrabber.adapters.DirectUsersAdapter.OnDirectUserClickListener;
|
||||||
|
import awais.instagrabber.adapters.viewholder.DirectUserViewHolder;
|
||||||
|
import awais.instagrabber.databinding.LayoutDmUserItemBinding;
|
||||||
|
import awais.instagrabber.repositories.responses.directmessages.DirectUser;
|
||||||
|
|
||||||
|
public final class UserSearchResultsAdapter extends ListAdapter<DirectUser, DirectUserViewHolder> {
|
||||||
|
|
||||||
|
private static final DiffUtil.ItemCallback<DirectUser> DIFF_CALLBACK = new DiffUtil.ItemCallback<DirectUser>() {
|
||||||
|
@Override
|
||||||
|
public boolean areItemsTheSame(@NonNull final DirectUser oldItem, @NonNull final DirectUser newItem) {
|
||||||
|
return oldItem.getPk() == newItem.getPk();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean areContentsTheSame(@NonNull final DirectUser oldItem, @NonNull final DirectUser newItem) {
|
||||||
|
return oldItem.getUsername().equals(newItem.getUsername()) &&
|
||||||
|
oldItem.getFullName().equals(newItem.getFullName());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
private final boolean showSelection;
|
||||||
|
private final Set<Long> selectedUserIds;
|
||||||
|
private final OnDirectUserClickListener onUserClickListener;
|
||||||
|
|
||||||
|
public UserSearchResultsAdapter(final boolean showSelection,
|
||||||
|
final OnDirectUserClickListener onUserClickListener) {
|
||||||
|
super(DIFF_CALLBACK);
|
||||||
|
this.showSelection = showSelection;
|
||||||
|
selectedUserIds = showSelection ? new HashSet<>() : null;
|
||||||
|
this.onUserClickListener = onUserClickListener;
|
||||||
|
setHasStableIds(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public DirectUserViewHolder onCreateViewHolder(@NonNull final ViewGroup parent, final int viewType) {
|
||||||
|
final LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext());
|
||||||
|
final LayoutDmUserItemBinding binding = LayoutDmUserItemBinding.inflate(layoutInflater, parent, false);
|
||||||
|
return new DirectUserViewHolder(binding, onUserClickListener, null);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBindViewHolder(@NonNull final DirectUserViewHolder holder, final int position) {
|
||||||
|
final DirectUser user = getItem(position);
|
||||||
|
boolean isSelected = selectedUserIds != null && selectedUserIds.contains(user.getPk());
|
||||||
|
holder.bind(position, user, false, false, showSelection, isSelected);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getItemId(final int position) {
|
||||||
|
return getItem(position).getPk();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSelectedUser(final long userId, final boolean selected) {
|
||||||
|
if (selectedUserIds == null) return;
|
||||||
|
int position = -1;
|
||||||
|
final List<DirectUser> currentList = getCurrentList();
|
||||||
|
for (int i = 0; i < currentList.size(); i++) {
|
||||||
|
if (currentList.get(i).getPk() == userId) {
|
||||||
|
position = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (position < 0) return;
|
||||||
|
if (selected) {
|
||||||
|
selectedUserIds.add(userId);
|
||||||
|
} else {
|
||||||
|
selectedUserIds.remove(userId);
|
||||||
|
}
|
||||||
|
notifyItemChanged(position);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,102 @@
|
|||||||
|
package awais.instagrabber.adapters.viewholder;
|
||||||
|
|
||||||
|
import android.graphics.drawable.Drawable;
|
||||||
|
import android.text.SpannableStringBuilder;
|
||||||
|
import android.text.Spanned;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.view.View;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.appcompat.content.res.AppCompatResources;
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
|
import awais.instagrabber.R;
|
||||||
|
import awais.instagrabber.adapters.DirectUsersAdapter.OnDirectUserClickListener;
|
||||||
|
import awais.instagrabber.adapters.DirectUsersAdapter.OnDirectUserLongClickListener;
|
||||||
|
import awais.instagrabber.customviews.VerticalImageSpan;
|
||||||
|
import awais.instagrabber.databinding.LayoutDmUserItemBinding;
|
||||||
|
import awais.instagrabber.repositories.responses.directmessages.DirectUser;
|
||||||
|
import awais.instagrabber.utils.Utils;
|
||||||
|
|
||||||
|
public class DirectUserViewHolder extends RecyclerView.ViewHolder {
|
||||||
|
private static final String TAG = DirectUserViewHolder.class.getSimpleName();
|
||||||
|
|
||||||
|
private final LayoutDmUserItemBinding binding;
|
||||||
|
private final OnDirectUserClickListener onClickListener;
|
||||||
|
private final OnDirectUserLongClickListener onLongClickListener;
|
||||||
|
private final int drawableSize;
|
||||||
|
|
||||||
|
private VerticalImageSpan verifiedSpan;
|
||||||
|
|
||||||
|
public DirectUserViewHolder(@NonNull final LayoutDmUserItemBinding binding,
|
||||||
|
final OnDirectUserClickListener onClickListener,
|
||||||
|
final OnDirectUserLongClickListener onLongClickListener) {
|
||||||
|
super(binding.getRoot());
|
||||||
|
this.binding = binding;
|
||||||
|
this.onClickListener = onClickListener;
|
||||||
|
this.onLongClickListener = onLongClickListener;
|
||||||
|
drawableSize = Utils.convertDpToPx(24);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void bind(final int position,
|
||||||
|
final DirectUser user,
|
||||||
|
final boolean isAdmin,
|
||||||
|
final boolean isInviter,
|
||||||
|
final boolean showSelection,
|
||||||
|
final boolean isSelected) {
|
||||||
|
if (user == null) return;
|
||||||
|
binding.getRoot().setOnClickListener(v -> {
|
||||||
|
if (onClickListener == null) return;
|
||||||
|
onClickListener.onClick(position, user, isSelected);
|
||||||
|
});
|
||||||
|
binding.getRoot().setOnLongClickListener(v -> {
|
||||||
|
if (onLongClickListener == null) return false;
|
||||||
|
return onLongClickListener.onLongClick(position, user);
|
||||||
|
});
|
||||||
|
setFullName(user);
|
||||||
|
binding.username.setText(user.getUsername());
|
||||||
|
binding.profilePic.setImageURI(user.getProfilePicUrl());
|
||||||
|
setInfo(isAdmin, isInviter);
|
||||||
|
setSelection(showSelection, isSelected);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setFullName(final DirectUser user) {
|
||||||
|
final SpannableStringBuilder sb = new SpannableStringBuilder(user.getFullName());
|
||||||
|
if (user.isVerified()) {
|
||||||
|
if (verifiedSpan == null) {
|
||||||
|
final Drawable verifiedDrawable = AppCompatResources.getDrawable(itemView.getContext(), R.drawable.verified);
|
||||||
|
if (verifiedDrawable != null) {
|
||||||
|
final Drawable drawable = verifiedDrawable.mutate();
|
||||||
|
drawable.setBounds(0, 0, drawableSize, drawableSize);
|
||||||
|
verifiedSpan = new VerticalImageSpan(drawable);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
if (verifiedSpan != null) {
|
||||||
|
sb.append(" ");
|
||||||
|
sb.setSpan(verifiedSpan, sb.length() - 1, sb.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e(TAG, "bind: ", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
binding.fullName.setText(sb);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setInfo(final boolean isAdmin, final boolean isInviter) {
|
||||||
|
if (!isAdmin && !isInviter) {
|
||||||
|
binding.info.setVisibility(View.GONE);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (isAdmin) {
|
||||||
|
binding.info.setText(R.string.admin);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
binding.info.setText(R.string.inviter);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setSelection(final boolean showSelection, final boolean isSelected) {
|
||||||
|
binding.select.setVisibility(showSelection ? View.VISIBLE : View.GONE);
|
||||||
|
binding.getRoot().setSelected(isSelected);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,76 @@
|
|||||||
|
package awais.instagrabber.customviews;
|
||||||
|
|
||||||
|
import android.graphics.Canvas;
|
||||||
|
import android.graphics.Paint;
|
||||||
|
import android.graphics.Rect;
|
||||||
|
import android.graphics.drawable.Drawable;
|
||||||
|
import android.text.style.ImageSpan;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
public class VerticalImageSpan extends ImageSpan {
|
||||||
|
|
||||||
|
public VerticalImageSpan(final Drawable drawable) {
|
||||||
|
super(drawable);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* update the text line height
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public int getSize(@NonNull Paint paint,
|
||||||
|
CharSequence text,
|
||||||
|
int start,
|
||||||
|
int end,
|
||||||
|
Paint.FontMetricsInt fontMetricsInt) {
|
||||||
|
Drawable drawable = getDrawable();
|
||||||
|
Rect rect = drawable.getBounds();
|
||||||
|
if (fontMetricsInt != null) {
|
||||||
|
Paint.FontMetricsInt fmPaint = paint.getFontMetricsInt();
|
||||||
|
int fontHeight = fmPaint.descent - fmPaint.ascent;
|
||||||
|
int drHeight = rect.bottom - rect.top;
|
||||||
|
int centerY = fmPaint.ascent + fontHeight / 2;
|
||||||
|
|
||||||
|
fontMetricsInt.ascent = centerY - drHeight / 2;
|
||||||
|
fontMetricsInt.top = fontMetricsInt.ascent;
|
||||||
|
fontMetricsInt.bottom = centerY + drHeight / 2;
|
||||||
|
fontMetricsInt.descent = fontMetricsInt.bottom;
|
||||||
|
}
|
||||||
|
return rect.right;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* see detail message in android.text.TextLine
|
||||||
|
*
|
||||||
|
* @param canvas the canvas, can be null if not rendering
|
||||||
|
* @param text the text to be draw
|
||||||
|
* @param start the text start position
|
||||||
|
* @param end the text end position
|
||||||
|
* @param x the edge of the replacement closest to the leading margin
|
||||||
|
* @param top the top of the line
|
||||||
|
* @param y the baseline
|
||||||
|
* @param bottom the bottom of the line
|
||||||
|
* @param paint the work paint
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void draw(Canvas canvas,
|
||||||
|
CharSequence text,
|
||||||
|
int start,
|
||||||
|
int end,
|
||||||
|
float x,
|
||||||
|
int top,
|
||||||
|
int y,
|
||||||
|
int bottom,
|
||||||
|
Paint paint) {
|
||||||
|
Drawable drawable = getDrawable();
|
||||||
|
canvas.save();
|
||||||
|
Paint.FontMetricsInt fmPaint = paint.getFontMetricsInt();
|
||||||
|
int fontHeight = fmPaint.descent - fmPaint.ascent;
|
||||||
|
int centerY = y + fmPaint.descent - fontHeight / 2;
|
||||||
|
int transY = centerY - (drawable.getBounds().bottom - drawable.getBounds().top) / 2;
|
||||||
|
canvas.translate(x, transY);
|
||||||
|
drawable.draw(canvas);
|
||||||
|
canvas.restore();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,236 @@
|
|||||||
|
package awais.instagrabber.dialogs;
|
||||||
|
|
||||||
|
import android.app.Dialog;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.util.SparseBooleanArray;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.StringRes;
|
||||||
|
import androidx.appcompat.app.AlertDialog;
|
||||||
|
import androidx.fragment.app.DialogFragment;
|
||||||
|
|
||||||
|
import com.google.common.primitives.Booleans;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
public class MultiOptionDialogFragment<T extends Serializable> extends DialogFragment {
|
||||||
|
private static final String TAG = MultiOptionDialogFragment.class.getSimpleName();
|
||||||
|
|
||||||
|
public enum Type {
|
||||||
|
MULTIPLE,
|
||||||
|
SINGLE_CHECKED,
|
||||||
|
SINGLE
|
||||||
|
}
|
||||||
|
|
||||||
|
private Context context;
|
||||||
|
private Type type;
|
||||||
|
private MultiOptionDialogCallback<T> callback;
|
||||||
|
private MultiOptionDialogSingleCallback<T> singleCallback;
|
||||||
|
private List<Option<?>> options;
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public static <E extends Serializable> MultiOptionDialogFragment<E> newInstance(@StringRes final int title,
|
||||||
|
@NonNull final ArrayList<Option<E>> options) {
|
||||||
|
return newInstance(title, 0, 0, options, Type.SINGLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public static <E extends Serializable> MultiOptionDialogFragment<E> newInstance(@StringRes final int title,
|
||||||
|
@StringRes final int positiveButtonText,
|
||||||
|
@StringRes final int negativeButtonText,
|
||||||
|
@NonNull final ArrayList<Option<E>> options,
|
||||||
|
@NonNull final Type type) {
|
||||||
|
Bundle args = new Bundle();
|
||||||
|
args.putInt("title", title);
|
||||||
|
args.putInt("positiveButtonText", positiveButtonText);
|
||||||
|
args.putInt("negativeButtonText", negativeButtonText);
|
||||||
|
args.putSerializable("options", options);
|
||||||
|
args.putSerializable("type", type);
|
||||||
|
MultiOptionDialogFragment<E> fragment = new MultiOptionDialogFragment<>();
|
||||||
|
fragment.setArguments(args);
|
||||||
|
return fragment;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAttach(@NonNull final Context context) {
|
||||||
|
super.onAttach(context);
|
||||||
|
this.context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||||
|
final Bundle arguments = getArguments();
|
||||||
|
int title = 0;
|
||||||
|
if (arguments != null) {
|
||||||
|
title = arguments.getInt("title");
|
||||||
|
type = (Type) arguments.getSerializable("type");
|
||||||
|
}
|
||||||
|
final AlertDialog.Builder builder = new AlertDialog.Builder(context);
|
||||||
|
if (title > 0) {
|
||||||
|
builder.setTitle(title);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
//noinspection unchecked
|
||||||
|
options = arguments != null ? (List<Option<?>>) arguments.getSerializable("options")
|
||||||
|
: Collections.emptyList();
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e(TAG, "onCreateDialog: ", e);
|
||||||
|
options = Collections.emptyList();
|
||||||
|
}
|
||||||
|
final int negativeButtonText = arguments != null ? arguments.getInt("negativeButtonText", -1) : -1;
|
||||||
|
if (negativeButtonText > 0) {
|
||||||
|
builder.setNegativeButton(negativeButtonText, (dialog, which) -> {
|
||||||
|
if (callback != null) {
|
||||||
|
callback.onCancel();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (singleCallback != null) {
|
||||||
|
singleCallback.onCancel();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (type == Type.MULTIPLE || type == Type.SINGLE_CHECKED) {
|
||||||
|
final int positiveButtonText = arguments != null ? arguments.getInt("positiveButtonText", -1) : -1;
|
||||||
|
if (positiveButtonText > 0) {
|
||||||
|
builder.setPositiveButton(positiveButtonText, (dialog, which) -> {
|
||||||
|
if (callback == null || options == null || options.isEmpty()) return;
|
||||||
|
try {
|
||||||
|
final List<T> selected = new ArrayList<>();
|
||||||
|
final SparseBooleanArray checkedItemPositions = ((AlertDialog) dialog).getListView().getCheckedItemPositions();
|
||||||
|
for (int i = 0; i < checkedItemPositions.size(); i++) {
|
||||||
|
final int position = checkedItemPositions.keyAt(i);
|
||||||
|
final boolean checked = checkedItemPositions.get(position);
|
||||||
|
if (!checked) continue;
|
||||||
|
//noinspection unchecked
|
||||||
|
final Option<T> option = (Option<T>) options.get(position);
|
||||||
|
selected.add(option.value);
|
||||||
|
}
|
||||||
|
callback.onMultipleSelect(selected);
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e(TAG, "onCreateDialog: ", e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (type == Type.MULTIPLE) {
|
||||||
|
if (options != null && !options.isEmpty()) {
|
||||||
|
final String[] items = options.stream()
|
||||||
|
.map(option -> option.label)
|
||||||
|
.toArray(String[]::new);
|
||||||
|
final boolean[] checkedItems = Booleans.toArray(options.stream()
|
||||||
|
.map(option -> option.checked)
|
||||||
|
.collect(Collectors.toList()));
|
||||||
|
builder.setMultiChoiceItems(items, checkedItems, (dialog, which, isChecked) -> {
|
||||||
|
if (callback == null) return;
|
||||||
|
try {
|
||||||
|
final Option<?> option = options.get(which);
|
||||||
|
//noinspection unchecked
|
||||||
|
callback.onCheckChange((T) option.value, isChecked);
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e(TAG, "onCreateDialog: ", e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (options != null && !options.isEmpty()) {
|
||||||
|
final String[] items = options.stream()
|
||||||
|
.map(option -> option.label)
|
||||||
|
.toArray(String[]::new);
|
||||||
|
if (type == Type.SINGLE_CHECKED) {
|
||||||
|
int index = -1;
|
||||||
|
for (int i = 0; i < options.size(); i++) {
|
||||||
|
if (options.get(i).checked) {
|
||||||
|
index = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
builder.setSingleChoiceItems(items, index, (dialog, which) -> {
|
||||||
|
if (callback == null) return;
|
||||||
|
try {
|
||||||
|
final Option<?> option = options.get(which);
|
||||||
|
//noinspection unchecked
|
||||||
|
callback.onCheckChange((T) option.value, true);
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e(TAG, "onCreateDialog: ", e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else if (type == Type.SINGLE) {
|
||||||
|
builder.setItems(items, (dialog, which) -> {
|
||||||
|
if (singleCallback == null) return;
|
||||||
|
try {
|
||||||
|
final Option<?> option = options.get(which);
|
||||||
|
//noinspection unchecked
|
||||||
|
singleCallback.onSelect((T) option.value);
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e(TAG, "onCreateDialog: ", e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return builder.create();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCallback(final MultiOptionDialogCallback<T> callback) {
|
||||||
|
if (callback == null) return;
|
||||||
|
this.callback = callback;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSingleCallback(final MultiOptionDialogSingleCallback<T> callback) {
|
||||||
|
if (callback == null) return;
|
||||||
|
this.singleCallback = callback;
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface MultiOptionDialogCallback<T> {
|
||||||
|
void onSelect(T result);
|
||||||
|
|
||||||
|
void onMultipleSelect(List<T> result);
|
||||||
|
|
||||||
|
void onCheckChange(T item, boolean isChecked);
|
||||||
|
|
||||||
|
void onCancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface MultiOptionDialogSingleCallback<T> {
|
||||||
|
void onSelect(T result);
|
||||||
|
|
||||||
|
void onCancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Option<T extends Serializable> {
|
||||||
|
private final String label;
|
||||||
|
private final T value;
|
||||||
|
private final boolean checked;
|
||||||
|
|
||||||
|
public Option(final String label, final T value) {
|
||||||
|
this.label = label;
|
||||||
|
this.value = value;
|
||||||
|
this.checked = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Option(final String label, final T value, final boolean checked) {
|
||||||
|
this.label = label;
|
||||||
|
this.value = value;
|
||||||
|
this.checked = checked;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getLabel() {
|
||||||
|
return label;
|
||||||
|
}
|
||||||
|
|
||||||
|
public T getValue() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isChecked() {
|
||||||
|
return checked;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -9,7 +9,6 @@ import android.graphics.drawable.ColorDrawable;
|
|||||||
import android.os.AsyncTask;
|
import android.os.AsyncTask;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.Environment;
|
import android.os.Environment;
|
||||||
import android.util.Log;
|
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
@ -20,7 +19,6 @@ import androidx.annotation.NonNull;
|
|||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.core.content.ContextCompat;
|
import androidx.core.content.ContextCompat;
|
||||||
import androidx.fragment.app.DialogFragment;
|
import androidx.fragment.app.DialogFragment;
|
||||||
import androidx.fragment.app.FragmentActivity;
|
|
||||||
|
|
||||||
import com.facebook.drawee.backends.pipeline.Fresco;
|
import com.facebook.drawee.backends.pipeline.Fresco;
|
||||||
import com.facebook.drawee.controller.BaseControllerListener;
|
import com.facebook.drawee.controller.BaseControllerListener;
|
||||||
@ -32,17 +30,14 @@ import java.io.File;
|
|||||||
import awais.instagrabber.R;
|
import awais.instagrabber.R;
|
||||||
import awais.instagrabber.asyncs.ProfilePictureFetcher;
|
import awais.instagrabber.asyncs.ProfilePictureFetcher;
|
||||||
import awais.instagrabber.databinding.DialogProfilepicBinding;
|
import awais.instagrabber.databinding.DialogProfilepicBinding;
|
||||||
import awais.instagrabber.db.entities.Account;
|
|
||||||
import awais.instagrabber.db.repositories.RepositoryCallback;
|
|
||||||
import awais.instagrabber.interfaces.FetchListener;
|
import awais.instagrabber.interfaces.FetchListener;
|
||||||
import awais.instagrabber.models.StoryModel;
|
|
||||||
import awais.instagrabber.repositories.responses.UserInfo;
|
import awais.instagrabber.repositories.responses.UserInfo;
|
||||||
import awais.instagrabber.utils.Constants;
|
import awais.instagrabber.utils.Constants;
|
||||||
import awais.instagrabber.utils.CookieUtils;
|
import awais.instagrabber.utils.CookieUtils;
|
||||||
import awais.instagrabber.utils.DownloadUtils;
|
import awais.instagrabber.utils.DownloadUtils;
|
||||||
import awais.instagrabber.utils.TextUtils;
|
import awais.instagrabber.utils.TextUtils;
|
||||||
import awais.instagrabber.webservices.ProfileService;
|
|
||||||
import awais.instagrabber.webservices.ServiceCallback;
|
import awais.instagrabber.webservices.ServiceCallback;
|
||||||
|
import awais.instagrabber.webservices.UserService;
|
||||||
|
|
||||||
import static awais.instagrabber.utils.Utils.settingsHelper;
|
import static awais.instagrabber.utils.Utils.settingsHelper;
|
||||||
|
|
||||||
@ -128,8 +123,8 @@ public class ProfilePicDialogFragment extends DialogFragment {
|
|||||||
|
|
||||||
private void fetchAvatar() {
|
private void fetchAvatar() {
|
||||||
if (isLoggedIn) {
|
if (isLoggedIn) {
|
||||||
final ProfileService profileService = ProfileService.getInstance();
|
final UserService userService = UserService.getInstance();
|
||||||
profileService.getUserInfo(id, new ServiceCallback<UserInfo>() {
|
userService.getUserInfo(id, new ServiceCallback<UserInfo>() {
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(final UserInfo result) {
|
public void onSuccess(final UserInfo result) {
|
||||||
if (result != null) {
|
if (result != null) {
|
||||||
@ -144,8 +139,9 @@ public class ProfilePicDialogFragment extends DialogFragment {
|
|||||||
getDialog().dismiss();
|
getDialog().dismiss();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
else new ProfilePictureFetcher(name, id, fetchListener, fallbackUrl, false).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
new ProfilePictureFetcher(name, id, fetchListener, fallbackUrl, false).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setupPhoto() {
|
private void setupPhoto() {
|
||||||
|
@ -0,0 +1,270 @@
|
|||||||
|
package awais.instagrabber.fragments;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.view.KeyEvent;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.appcompat.app.ActionBar;
|
||||||
|
import androidx.core.util.Pair;
|
||||||
|
import androidx.fragment.app.Fragment;
|
||||||
|
import androidx.lifecycle.SavedStateHandle;
|
||||||
|
import androidx.lifecycle.ViewModelProvider;
|
||||||
|
import androidx.navigation.NavBackStackEntry;
|
||||||
|
import androidx.navigation.NavController;
|
||||||
|
import androidx.navigation.fragment.NavHostFragment;
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||||
|
import androidx.transition.TransitionManager;
|
||||||
|
|
||||||
|
import com.google.android.material.chip.Chip;
|
||||||
|
import com.google.android.material.snackbar.Snackbar;
|
||||||
|
|
||||||
|
import awais.instagrabber.activities.MainActivity;
|
||||||
|
import awais.instagrabber.adapters.UserSearchResultsAdapter;
|
||||||
|
import awais.instagrabber.customviews.helpers.TextWatcherAdapter;
|
||||||
|
import awais.instagrabber.databinding.FragmentUserSearchBinding;
|
||||||
|
import awais.instagrabber.repositories.responses.directmessages.DirectUser;
|
||||||
|
import awais.instagrabber.utils.TextUtils;
|
||||||
|
import awais.instagrabber.utils.Utils;
|
||||||
|
import awais.instagrabber.utils.ViewUtils;
|
||||||
|
import awais.instagrabber.viewmodels.UserSearchViewModel;
|
||||||
|
|
||||||
|
public class UserSearchFragment extends Fragment {
|
||||||
|
private static final String TAG = UserSearchFragment.class.getSimpleName();
|
||||||
|
|
||||||
|
private FragmentUserSearchBinding binding;
|
||||||
|
private UserSearchViewModel viewModel;
|
||||||
|
private UserSearchResultsAdapter resultsAdapter;
|
||||||
|
private int paddingOffset;
|
||||||
|
|
||||||
|
private final int windowWidth = Utils.displayMetrics.widthPixels;
|
||||||
|
private final int minInputWidth = Utils.convertDpToPx(50);
|
||||||
|
private String actionLabel;
|
||||||
|
private String title;
|
||||||
|
private boolean multiple;
|
||||||
|
private long[] hideUserIds;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(@Nullable final Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public View onCreateView(@NonNull final LayoutInflater inflater, @Nullable final ViewGroup container, @Nullable final Bundle savedInstanceState) {
|
||||||
|
binding = FragmentUserSearchBinding.inflate(inflater, container, false);
|
||||||
|
viewModel = new ViewModelProvider(this).get(UserSearchViewModel.class);
|
||||||
|
return binding.getRoot();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onViewCreated(@NonNull final View view, @Nullable final Bundle savedInstanceState) {
|
||||||
|
paddingOffset = binding.search.getPaddingStart() + binding.search.getPaddingEnd() + binding.group
|
||||||
|
.getPaddingStart() + binding.group.getPaddingEnd() + binding.group.getChipSpacingHorizontal();
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDestroyView() {
|
||||||
|
super.onDestroyView();
|
||||||
|
viewModel.cleanup();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void init() {
|
||||||
|
final Bundle arguments = getArguments();
|
||||||
|
if (arguments != null) {
|
||||||
|
final UserSearchFragmentArgs fragmentArgs = UserSearchFragmentArgs.fromBundle(arguments);
|
||||||
|
actionLabel = fragmentArgs.getActionLabel();
|
||||||
|
title = fragmentArgs.getTitle();
|
||||||
|
multiple = fragmentArgs.getMultiple();
|
||||||
|
viewModel.setHideUserIds(fragmentArgs.getHideUserIds());
|
||||||
|
}
|
||||||
|
setupTitles();
|
||||||
|
setupInput();
|
||||||
|
setupResults();
|
||||||
|
setupObservers();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupTitles() {
|
||||||
|
if (!TextUtils.isEmpty(actionLabel)) {
|
||||||
|
binding.done.setText(actionLabel);
|
||||||
|
}
|
||||||
|
if (!TextUtils.isEmpty(title)) {
|
||||||
|
final MainActivity activity = (MainActivity) getActivity();
|
||||||
|
if (activity != null) {
|
||||||
|
final ActionBar actionBar = activity.getSupportActionBar();
|
||||||
|
if (actionBar != null) {
|
||||||
|
actionBar.setTitle(title);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupResults() {
|
||||||
|
final Context context = getContext();
|
||||||
|
if (context == null) return;
|
||||||
|
binding.results.setLayoutManager(new LinearLayoutManager(context));
|
||||||
|
resultsAdapter = new UserSearchResultsAdapter(multiple, (position, user, selected) -> {
|
||||||
|
if (!multiple) {
|
||||||
|
final NavController navController = NavHostFragment.findNavController(this);
|
||||||
|
final NavBackStackEntry navBackStackEntry = navController.getPreviousBackStackEntry();
|
||||||
|
if (navBackStackEntry == null) return;
|
||||||
|
final SavedStateHandle savedStateHandle = navBackStackEntry.getSavedStateHandle();
|
||||||
|
savedStateHandle.set("result", user);
|
||||||
|
navController.navigateUp();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
viewModel.setSelectedUser(user, !selected);
|
||||||
|
resultsAdapter.setSelectedUser(user.getPk(), !selected);
|
||||||
|
if (!selected) {
|
||||||
|
createUserChip(user);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final View chip = findChip(user.getPk());
|
||||||
|
if (chip == null) return;
|
||||||
|
removeChip(chip);
|
||||||
|
});
|
||||||
|
binding.results.setAdapter(resultsAdapter);
|
||||||
|
binding.done.setOnClickListener(v -> {
|
||||||
|
final NavController navController = NavHostFragment.findNavController(this);
|
||||||
|
final NavBackStackEntry navBackStackEntry = navController.getPreviousBackStackEntry();
|
||||||
|
if (navBackStackEntry == null) return;
|
||||||
|
final SavedStateHandle savedStateHandle = navBackStackEntry.getSavedStateHandle();
|
||||||
|
savedStateHandle.set("result", viewModel.getSelectedUsers());
|
||||||
|
navController.navigateUp();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupInput() {
|
||||||
|
binding.search.addTextChangedListener(new TextWatcherAdapter() {
|
||||||
|
@Override
|
||||||
|
public void onTextChanged(final CharSequence s, final int start, final int before, final int count) {
|
||||||
|
if (TextUtils.isEmpty(s)) {
|
||||||
|
viewModel.cancelSearch();
|
||||||
|
viewModel.clearResults();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
viewModel.search(s.toString().trim());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
binding.search.setOnKeyListener((v, keyCode, event) -> {
|
||||||
|
if (event != null && event.getAction() == KeyEvent.ACTION_DOWN && event.getKeyCode() == KeyEvent.KEYCODE_DEL) {
|
||||||
|
final View chip = getLastChip();
|
||||||
|
if (chip == null) return false;
|
||||||
|
removeSelectedUser(chip);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
binding.group.setOnHierarchyChangeListener(new ViewGroup.OnHierarchyChangeListener() {
|
||||||
|
@Override
|
||||||
|
public void onChildViewAdded(final View parent, final View child) {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onChildViewRemoved(final View parent, final View child) {
|
||||||
|
binding.group.post(() -> {
|
||||||
|
TransitionManager.beginDelayedTransition(binding.getRoot());
|
||||||
|
calculateInputWidth(0);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupObservers() {
|
||||||
|
viewModel.getUsers().observe(getViewLifecycleOwner(), results -> {
|
||||||
|
if (results == null) return;
|
||||||
|
switch (results.status) {
|
||||||
|
case SUCCESS:
|
||||||
|
resultsAdapter.submitList(results.data);
|
||||||
|
break;
|
||||||
|
case ERROR:
|
||||||
|
if (results.message != null) {
|
||||||
|
Snackbar.make(binding.getRoot(), results.message, Snackbar.LENGTH_LONG).show();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case LOADING:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
viewModel.showAction().observe(getViewLifecycleOwner(), showAction -> binding.done.setVisibility(showAction ? View.VISIBLE : View.GONE));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createUserChip(final DirectUser user) {
|
||||||
|
final Context context = getContext();
|
||||||
|
if (context == null) return;
|
||||||
|
final Chip chip = new Chip(context);
|
||||||
|
chip.setTag(user);
|
||||||
|
chip.setText(user.getFullName());
|
||||||
|
chip.setCloseIconVisible(true);
|
||||||
|
chip.setOnCloseIconClickListener(v -> removeSelectedUser(chip));
|
||||||
|
binding.group.post(() -> {
|
||||||
|
final Pair<Integer, Integer> measure = ViewUtils.measure(chip, binding.group);
|
||||||
|
TransitionManager.beginDelayedTransition(binding.getRoot());
|
||||||
|
calculateInputWidth(measure.second != null ? measure.second : 0);
|
||||||
|
binding.group.addView(chip, binding.group.getChildCount() - 1);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void removeSelectedUser(final View chip) {
|
||||||
|
final DirectUser user = (DirectUser) chip.getTag();
|
||||||
|
if (user == null) return;
|
||||||
|
viewModel.setSelectedUser(user, false);
|
||||||
|
resultsAdapter.setSelectedUser(user.getPk(), false);
|
||||||
|
removeChip(chip);
|
||||||
|
}
|
||||||
|
|
||||||
|
private View findChip(final long userId) {
|
||||||
|
final int childCount = binding.group.getChildCount();
|
||||||
|
if (childCount == 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
for (int i = childCount - 1; i >= 0; i--) {
|
||||||
|
final View child = binding.group.getChildAt(i);
|
||||||
|
if (child == null) continue;
|
||||||
|
final DirectUser user = (DirectUser) child.getTag();
|
||||||
|
if (user != null && user.getPk() == userId) {
|
||||||
|
return child;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void removeChip(final View chip) {
|
||||||
|
binding.group.post(() -> {
|
||||||
|
TransitionManager.beginDelayedTransition(binding.getRoot());
|
||||||
|
binding.group.removeView(chip);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void calculateInputWidth(final int newChipWidth) {
|
||||||
|
final View lastChip = getLastChip();
|
||||||
|
int lastRight = lastChip != null ? lastChip.getRight() : 0;
|
||||||
|
final int remainingSpaceInRow = windowWidth - lastRight;
|
||||||
|
if (remainingSpaceInRow < newChipWidth) {
|
||||||
|
// next chip will go to the next row, so assume no chips present
|
||||||
|
lastRight = 0;
|
||||||
|
}
|
||||||
|
final int newRight = lastRight + newChipWidth;
|
||||||
|
final int newInputWidth = windowWidth - newRight - paddingOffset;
|
||||||
|
binding.search.getLayoutParams().width = newInputWidth < minInputWidth ? windowWidth : newInputWidth;
|
||||||
|
binding.search.requestLayout();
|
||||||
|
}
|
||||||
|
|
||||||
|
private View getLastChip() {
|
||||||
|
final int childCount = binding.group.getChildCount();
|
||||||
|
if (childCount == 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
for (int i = childCount - 1; i >= 0; i--) {
|
||||||
|
final View child = binding.group.getChildAt(i);
|
||||||
|
if (child instanceof Chip) {
|
||||||
|
return child;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
@ -15,6 +15,8 @@ import androidx.coordinatorlayout.widget.CoordinatorLayout;
|
|||||||
import androidx.fragment.app.Fragment;
|
import androidx.fragment.app.Fragment;
|
||||||
import androidx.lifecycle.Observer;
|
import androidx.lifecycle.Observer;
|
||||||
import androidx.lifecycle.ViewModelProvider;
|
import androidx.lifecycle.ViewModelProvider;
|
||||||
|
import androidx.lifecycle.ViewModelStoreOwner;
|
||||||
|
import androidx.navigation.NavController;
|
||||||
import androidx.navigation.fragment.NavHostFragment;
|
import androidx.navigation.fragment.NavHostFragment;
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
|
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
|
||||||
@ -54,7 +56,9 @@ public class DirectMessageInboxFragment extends Fragment implements SwipeRefresh
|
|||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
fragmentActivity = (MainActivity) getActivity();
|
fragmentActivity = (MainActivity) getActivity();
|
||||||
if (fragmentActivity != null) {
|
if (fragmentActivity != null) {
|
||||||
viewModel = new ViewModelProvider(fragmentActivity).get(DirectInboxViewModel.class);
|
final NavController navController = NavHostFragment.findNavController(this);
|
||||||
|
final ViewModelStoreOwner viewModelStoreOwner = navController.getViewModelStoreOwner(R.id.direct_messages_nav_graph);
|
||||||
|
viewModel = new ViewModelProvider(viewModelStoreOwner).get(DirectInboxViewModel.class);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,126 +1,117 @@
|
|||||||
package awais.instagrabber.fragments.directmessages;
|
package awais.instagrabber.fragments.directmessages;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.DialogInterface;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.os.AsyncTask;
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.text.Editable;
|
import android.text.Editable;
|
||||||
import android.text.TextWatcher;
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.ArrayAdapter;
|
|
||||||
import android.widget.EditText;
|
|
||||||
import android.widget.LinearLayout;
|
|
||||||
import android.widget.Toast;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.appcompat.app.ActionBar;
|
import androidx.core.util.Pair;
|
||||||
import androidx.appcompat.app.AlertDialog;
|
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
|
||||||
import androidx.appcompat.widget.AppCompatButton;
|
|
||||||
import androidx.appcompat.widget.AppCompatImageView;
|
|
||||||
import androidx.fragment.app.Fragment;
|
import androidx.fragment.app.Fragment;
|
||||||
|
import androidx.fragment.app.FragmentManager;
|
||||||
|
import androidx.lifecycle.LiveData;
|
||||||
|
import androidx.lifecycle.MutableLiveData;
|
||||||
|
import androidx.lifecycle.ViewModelProvider;
|
||||||
|
import androidx.lifecycle.ViewModelStoreOwner;
|
||||||
|
import androidx.navigation.NavBackStackEntry;
|
||||||
|
import androidx.navigation.NavController;
|
||||||
|
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;
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
|
||||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
|
|
||||||
|
|
||||||
import java.io.DataOutputStream;
|
import com.google.android.material.snackbar.Snackbar;
|
||||||
import java.net.HttpURLConnection;
|
|
||||||
import java.net.URL;
|
import java.util.ArrayList;
|
||||||
import java.net.URLEncoder;
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
import awais.instagrabber.R;
|
import awais.instagrabber.R;
|
||||||
import awais.instagrabber.broadcasts.DMRefreshBroadcastReceiver;
|
import awais.instagrabber.adapters.DirectUsersAdapter;
|
||||||
|
import awais.instagrabber.customviews.helpers.TextWatcherAdapter;
|
||||||
import awais.instagrabber.databinding.FragmentDirectMessagesSettingsBinding;
|
import awais.instagrabber.databinding.FragmentDirectMessagesSettingsBinding;
|
||||||
import awais.instagrabber.models.ProfileModel;
|
import awais.instagrabber.dialogs.MultiOptionDialogFragment;
|
||||||
import awais.instagrabber.utils.Constants;
|
import awais.instagrabber.dialogs.MultiOptionDialogFragment.Option;
|
||||||
import awais.instagrabber.utils.Utils;
|
import awais.instagrabber.models.Resource;
|
||||||
|
import awais.instagrabber.repositories.responses.directmessages.DirectThread;
|
||||||
|
import awais.instagrabber.repositories.responses.directmessages.DirectUser;
|
||||||
|
import awais.instagrabber.viewmodels.DirectInboxViewModel;
|
||||||
|
import awais.instagrabber.viewmodels.DirectSettingsViewModel;
|
||||||
|
|
||||||
public class DirectMessageSettingsFragment extends Fragment implements SwipeRefreshLayout.OnRefreshListener {
|
public class DirectMessageSettingsFragment extends Fragment {
|
||||||
private static final String TAG = "DirectMsgsSettingsFrag";
|
private static final String TAG = DirectMessageSettingsFragment.class.getSimpleName();
|
||||||
|
|
||||||
private AppCompatActivity fragmentActivity;
|
private FragmentDirectMessagesSettingsBinding binding;
|
||||||
private RecyclerView userList;
|
private DirectSettingsViewModel viewModel;
|
||||||
private RecyclerView leftUserList;
|
private DirectUsersAdapter usersAdapter;
|
||||||
private EditText titleText;
|
private List<Option<String>> options;
|
||||||
private View leftTitle;
|
|
||||||
private AppCompatImageView titleSend;
|
|
||||||
private String threadId;
|
|
||||||
private String threadTitle;
|
|
||||||
private final String cookie = Utils.settingsHelper.getString(Constants.COOKIE);
|
|
||||||
// private AsyncTask<Void, Void, InboxThreadModel> currentlyRunning;
|
|
||||||
private View.OnClickListener clickListener;
|
|
||||||
private View.OnClickListener basicClickListener;
|
|
||||||
|
|
||||||
// private final FetchListener<InboxThreadModel> fetchListener = new FetchListener<InboxThreadModel>() {
|
|
||||||
// @Override
|
|
||||||
// public void doBefore() {}
|
|
||||||
//
|
|
||||||
// @Override
|
|
||||||
// public void onResult(final InboxThreadModel threadModel) {
|
|
||||||
// if (threadModel == null) return;
|
|
||||||
// final List<Long> adminList = threadModel.getAdmins();
|
|
||||||
// final String userIdFromCookie = CookieUtils.getUserIdFromCookie(cookie);
|
|
||||||
// if (userIdFromCookie == null) return;
|
|
||||||
// final boolean amAdmin = adminList.contains(Long.parseLong(userIdFromCookie));
|
|
||||||
// final DirectMessageMembersAdapter memberAdapter = new DirectMessageMembersAdapter(threadModel.getUsers(),
|
|
||||||
// adminList,
|
|
||||||
// amAdmin ? clickListener : basicClickListener);
|
|
||||||
// userList.setAdapter(memberAdapter);
|
|
||||||
// if (threadModel.getLeftUsers() != null && threadModel.getLeftUsers().size() > 0) {
|
|
||||||
// leftTitle.setVisibility(View.VISIBLE);
|
|
||||||
// final DirectMessageMembersAdapter leftAdapter = new DirectMessageMembersAdapter(threadModel.getLeftUsers(),
|
|
||||||
// null,
|
|
||||||
// basicClickListener);
|
|
||||||
// leftUserList.setAdapter(leftAdapter);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// };
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(@Nullable final Bundle savedInstanceState) {
|
public void onCreate(@Nullable final Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
fragmentActivity = (AppCompatActivity) requireActivity();
|
final NavController navController = NavHostFragment.findNavController(this);
|
||||||
basicClickListener = v -> {
|
final ViewModelStoreOwner viewModelStoreOwner = navController.getViewModelStoreOwner(R.id.direct_messages_nav_graph);
|
||||||
final Object tag = v.getTag();
|
final DirectInboxViewModel inboxViewModel = new ViewModelProvider(viewModelStoreOwner).get(DirectInboxViewModel.class);
|
||||||
if (tag instanceof ProfileModel) {
|
final List<DirectThread> threads = inboxViewModel.getThreads().getValue();
|
||||||
ProfileModel model = (ProfileModel) tag;
|
final Bundle arguments = getArguments();
|
||||||
final Bundle bundle = new Bundle();
|
if (arguments == null) {
|
||||||
bundle.putString("username", "@" + model.getUsername());
|
navController.navigateUp();
|
||||||
NavHostFragment.findNavController(this).navigate(R.id.action_global_profileFragment, bundle);
|
return;
|
||||||
}
|
}
|
||||||
};
|
final DirectMessageSettingsFragmentArgs fragmentArgs = DirectMessageSettingsFragmentArgs.fromBundle(arguments);
|
||||||
|
final String threadId = fragmentArgs.getThreadId();
|
||||||
clickListener = v -> {
|
final Optional<DirectThread> first = threads != null ? threads.stream()
|
||||||
final Object tag = v.getTag();
|
.filter(thread -> thread.getThreadId().equals(threadId))
|
||||||
if (tag instanceof ProfileModel) {
|
.findFirst()
|
||||||
ProfileModel model = (ProfileModel) tag;
|
: Optional.empty();
|
||||||
final Context context = getContext();
|
if (!first.isPresent()) {
|
||||||
if (context == null) return;
|
navController.navigateUp();
|
||||||
final ArrayAdapter<String> adapter = new ArrayAdapter<>(context, android.R.layout.simple_list_item_1, new String[]{
|
return;
|
||||||
getString(R.string.open_profile),
|
}
|
||||||
getString(R.string.dms_action_kick),
|
viewModel = new ViewModelProvider(this).get(DirectSettingsViewModel.class);
|
||||||
});
|
viewModel.setViewer(inboxViewModel.getViewer());
|
||||||
final DialogInterface.OnClickListener clickListener = (d, w) -> {
|
viewModel.setThread(first.get());
|
||||||
if (w == 0) {
|
// basicClickListener = v -> {
|
||||||
final Bundle bundle = new Bundle();
|
// final Object tag = v.getTag();
|
||||||
bundle.putString("username", "@" + model.getUsername());
|
// if (tag instanceof ProfileModel) {
|
||||||
NavHostFragment.findNavController(this).navigate(R.id.action_global_profileFragment, bundle);
|
// ProfileModel model = (ProfileModel) tag;
|
||||||
} else if (w == 1) {
|
// final Bundle bundle = new Bundle();
|
||||||
new ChangeSettings(titleText.getText().toString()).execute("remove_users", model.getId());
|
// bundle.putString("username", "@" + model.getUsername());
|
||||||
onRefresh();
|
// NavHostFragment.findNavController(this).navigate(R.id.action_global_profileFragment, bundle);
|
||||||
}
|
// }
|
||||||
};
|
// };
|
||||||
new AlertDialog.Builder(context)
|
//
|
||||||
.setAdapter(adapter, clickListener)
|
// clickListener = v -> {
|
||||||
.show();
|
// final Object tag = v.getTag();
|
||||||
}
|
// if (tag instanceof ProfileModel) {
|
||||||
};
|
// ProfileModel model = (ProfileModel) tag;
|
||||||
|
// final Context context = getContext();
|
||||||
|
// if (context == null) return;
|
||||||
|
// final ArrayAdapter<String> adapter = new ArrayAdapter<>(context, android.R.layout.simple_list_item_1, new String[]{
|
||||||
|
// getString(R.string.open_profile),
|
||||||
|
// getString(R.string.dms_action_kick),
|
||||||
|
// });
|
||||||
|
// final DialogInterface.OnClickListener clickListener = (d, w) -> {
|
||||||
|
// if (w == 0) {
|
||||||
|
// final Bundle bundle = new Bundle();
|
||||||
|
// bundle.putString("username", "@" + model.getUsername());
|
||||||
|
// NavHostFragment.findNavController(this).navigate(R.id.action_global_profileFragment, bundle);
|
||||||
|
// } else if (w == 1) {
|
||||||
|
// new ChangeSettings(titleText.getText().toString()).execute("remove_users", model.getId());
|
||||||
|
// onRefresh();
|
||||||
|
// }
|
||||||
|
// };
|
||||||
|
// new AlertDialog.Builder(context)
|
||||||
|
// .setAdapter(adapter, clickListener)
|
||||||
|
// .show();
|
||||||
|
// }
|
||||||
|
// };
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
@ -128,155 +119,239 @@ public class DirectMessageSettingsFragment extends Fragment implements SwipeRefr
|
|||||||
public View onCreateView(@NonNull final LayoutInflater inflater,
|
public View onCreateView(@NonNull final LayoutInflater inflater,
|
||||||
final ViewGroup container,
|
final ViewGroup container,
|
||||||
final Bundle savedInstanceState) {
|
final Bundle savedInstanceState) {
|
||||||
final FragmentDirectMessagesSettingsBinding binding = FragmentDirectMessagesSettingsBinding.inflate(inflater, container, false);
|
binding = FragmentDirectMessagesSettingsBinding.inflate(inflater, container, false);
|
||||||
final LinearLayout root = binding.getRoot();
|
// final String threadId = DirectMessageSettingsFragmentArgs.fromBundle(getArguments()).getThreadId();
|
||||||
final Context context = getContext();
|
// threadTitle = DirectMessageSettingsFragmentArgs.fromBundle(getArguments()).getTitle();
|
||||||
if (context == null) return root;
|
// binding.swipeRefreshLayout.setEnabled(false);
|
||||||
final LinearLayoutManager layoutManager = new LinearLayoutManager(context) {
|
|
||||||
@Override
|
|
||||||
public boolean canScrollVertically() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
final LinearLayoutManager layoutManagerDos = new LinearLayoutManager(context) {
|
|
||||||
@Override
|
|
||||||
public boolean canScrollVertically() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
if (getArguments() == null) {
|
|
||||||
return root;
|
|
||||||
}
|
|
||||||
threadId = DirectMessageSettingsFragmentArgs.fromBundle(getArguments()).getThreadId();
|
|
||||||
threadTitle = DirectMessageSettingsFragmentArgs.fromBundle(getArguments()).getTitle();
|
|
||||||
binding.swipeRefreshLayout.setEnabled(false);
|
|
||||||
|
|
||||||
final ActionBar actionBar = fragmentActivity.getSupportActionBar();
|
// final ActionBar actionBar = fragmentActivity.getSupportActionBar();
|
||||||
if (actionBar != null) {
|
// if (actionBar != null) {
|
||||||
actionBar.setTitle(threadTitle);
|
// actionBar.setTitle(threadTitle);
|
||||||
}
|
// }
|
||||||
|
|
||||||
userList = binding.userList;
|
// titleSend.setOnClickListener(v -> new ChangeSettings(titleText.getText().toString()).execute("update_title"));
|
||||||
userList.setHasFixedSize(true);
|
|
||||||
userList.setLayoutManager(layoutManager);
|
|
||||||
|
|
||||||
leftUserList = binding.leftUserList;
|
// binding.titleText.addTextChangedListener(new TextWatcherAdapter() {
|
||||||
leftUserList.setHasFixedSize(true);
|
// @Override
|
||||||
leftUserList.setLayoutManager(layoutManagerDos);
|
// public void onTextChanged(CharSequence s, int start, int before, int count) {
|
||||||
|
// binding.titleSend.setVisibility(s.toString().equals(threadTitle) ? View.GONE : View.VISIBLE);
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
|
||||||
leftTitle = binding.leftTitle;
|
// final AppCompatButton btnLeave = binding.btnLeave;
|
||||||
|
// btnLeave.setOnClickListener(v -> new AlertDialog.Builder(context)
|
||||||
titleText = binding.titleText;
|
// .setTitle(R.string.dms_action_leave_question)
|
||||||
titleText.setText(threadTitle);
|
// .setPositiveButton(R.string.yes, (x, y) -> new ChangeSettings(titleText.getText().toString()).execute("leave"))
|
||||||
|
// .setNegativeButton(R.string.no, null)
|
||||||
titleSend = binding.titleSend;
|
// .show());
|
||||||
titleSend.setOnClickListener(v -> new ChangeSettings(titleText.getText().toString()).execute("update_title"));
|
|
||||||
|
|
||||||
titleText.addTextChangedListener(new TextWatcher() {
|
|
||||||
@Override
|
|
||||||
public void afterTextChanged(Editable s) {}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onTextChanged(CharSequence s, int start, int before, int count) {
|
|
||||||
titleSend.setVisibility(s.toString().equals(threadTitle) ? View.GONE : View.VISIBLE);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
final AppCompatButton btnLeave = binding.btnLeave;
|
|
||||||
btnLeave.setOnClickListener(v -> new AlertDialog.Builder(context)
|
|
||||||
.setTitle(R.string.dms_action_leave_question)
|
|
||||||
.setPositiveButton(R.string.yes,
|
|
||||||
(x, y) -> new ChangeSettings(titleText.getText().toString()).execute("leave"))
|
|
||||||
.setNegativeButton(R.string.no, null)
|
|
||||||
.show());
|
|
||||||
|
|
||||||
// currentlyRunning = new DirectMessageInboxThreadFetcher(threadId, null, null, fetchListener).execute();
|
// currentlyRunning = new DirectMessageInboxThreadFetcher(threadId, null, null, fetchListener).execute();
|
||||||
return root;
|
return binding.getRoot();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onRefresh() {
|
public void onViewCreated(@NonNull final View view, @Nullable final Bundle savedInstanceState) {
|
||||||
stopCurrentExecutor();
|
init();
|
||||||
// currentlyRunning = new DirectMessageInboxThreadFetcher(threadId, null, null, fetchListener).execute();
|
setupObservers();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void stopCurrentExecutor() {
|
private void setupObservers() {
|
||||||
// if (currentlyRunning != null) {
|
viewModel.getUsers().observe(getViewLifecycleOwner(), users -> {
|
||||||
// try {
|
if (usersAdapter == null) return;
|
||||||
// currentlyRunning.cancel(true);
|
usersAdapter.submitUsers(users.first, users.second);
|
||||||
// } catch (final Exception e) {
|
});
|
||||||
// if (BuildConfig.DEBUG) {
|
viewModel.getTitle().observe(getViewLifecycleOwner(), title -> binding.titleEdit.setText(title));
|
||||||
// Log.e(TAG, "", e);
|
viewModel.getAdminUserIds().observe(getViewLifecycleOwner(), adminUserIds -> {
|
||||||
// }
|
if (usersAdapter == null) return;
|
||||||
// }
|
usersAdapter.setAdminUserIds(adminUserIds);
|
||||||
// }
|
});
|
||||||
}
|
final NavController navController = NavHostFragment.findNavController(this);
|
||||||
|
final NavBackStackEntry backStackEntry = navController.getCurrentBackStackEntry();
|
||||||
class ChangeSettings extends AsyncTask<String, Void, Void> {
|
if (backStackEntry != null) {
|
||||||
String action, argument;
|
final MutableLiveData<Object> resultLiveData = backStackEntry.getSavedStateHandle().getLiveData("result");
|
||||||
boolean ok = false;
|
resultLiveData.observe(getViewLifecycleOwner(), result -> {
|
||||||
private final String text;
|
LiveData<Resource<Object>> detailsChangeResourceLiveData = null;
|
||||||
|
if ((result instanceof DirectUser)) {
|
||||||
public ChangeSettings(final String text) {
|
// Log.d(TAG, "result: " + result);
|
||||||
this.text = text;
|
detailsChangeResourceLiveData = viewModel.addMembers(Collections.singleton((DirectUser) result));
|
||||||
}
|
} else if ((result instanceof Set)) {
|
||||||
|
try {
|
||||||
protected Void doInBackground(String... rawAction) {
|
// Log.d(TAG, "result: " + result);
|
||||||
action = rawAction[0];
|
//noinspection unchecked
|
||||||
if (rawAction.length == 2) argument = rawAction[1];
|
detailsChangeResourceLiveData = viewModel.addMembers((Set<DirectUser>) result);
|
||||||
final String url = "https://i.instagram.com/api/v1/direct_v2/threads/" + threadId + "/" + action + "/";
|
} catch (Exception e) {
|
||||||
try {
|
Log.e(TAG, "search users result: ", e);
|
||||||
String urlParameters = "_csrftoken=" + cookie.split("csrftoken=")[1].split(";")[0]
|
}
|
||||||
+ "&_uuid=" + Utils.settingsHelper.getString(Constants.DEVICE_UUID);
|
|
||||||
if (action.equals("update_title")) {
|
|
||||||
urlParameters += "&title=" + URLEncoder.encode(text, "UTF-8")
|
|
||||||
.replaceAll("\\+", "%20").replaceAll("%21", "!").replaceAll("%27", "'")
|
|
||||||
.replaceAll("%28", "(").replaceAll("%29", ")").replaceAll("%7E", "~");
|
|
||||||
} else if (action.startsWith("remove_users"))
|
|
||||||
urlParameters += ("&user_ids=[" + argument + "]");
|
|
||||||
final HttpURLConnection urlConnection = (HttpURLConnection) new URL(url).openConnection();
|
|
||||||
urlConnection.setRequestMethod("POST");
|
|
||||||
urlConnection.setUseCaches(false);
|
|
||||||
urlConnection.setRequestProperty("User-Agent", Constants.I_USER_AGENT);
|
|
||||||
urlConnection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
|
|
||||||
urlConnection.setRequestProperty("Content-Length", Integer.toString(urlParameters.getBytes().length));
|
|
||||||
urlConnection.setDoOutput(true);
|
|
||||||
DataOutputStream wr = new DataOutputStream(urlConnection.getOutputStream());
|
|
||||||
wr.writeBytes(urlParameters);
|
|
||||||
wr.flush();
|
|
||||||
wr.close();
|
|
||||||
urlConnection.connect();
|
|
||||||
if (urlConnection.getResponseCode() == HttpURLConnection.HTTP_OK) {
|
|
||||||
ok = true;
|
|
||||||
}
|
}
|
||||||
urlConnection.disconnect();
|
if (detailsChangeResourceLiveData != null) {
|
||||||
} catch (Throwable ex) {
|
observeDetailsChange(detailsChangeResourceLiveData);
|
||||||
Log.e("austin_debug", "unsend: " + ex);
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void init() {
|
||||||
|
setupSettings();
|
||||||
|
setupMembers();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupSettings() {
|
||||||
|
binding.groupSettings.setVisibility(viewModel.isGroup() ? View.VISIBLE : View.GONE);
|
||||||
|
if (!viewModel.isGroup()) return;
|
||||||
|
binding.titleEdit.addTextChangedListener(new TextWatcherAdapter() {
|
||||||
|
@Override
|
||||||
|
public void onTextChanged(final CharSequence s, final int start, final int before, final int count) {
|
||||||
|
if (s.toString().trim().equals(viewModel.getTitle().getValue())) {
|
||||||
|
binding.titleEditInputLayout.setSuffixText(null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
binding.titleEditInputLayout.setSuffixText(getString(R.string.save));
|
||||||
}
|
}
|
||||||
return null;
|
});
|
||||||
}
|
binding.titleEditInputLayout.getSuffixTextView().setOnClickListener(v -> {
|
||||||
|
final Editable text = binding.titleEdit.getText();
|
||||||
@Override
|
if (text == null) return;
|
||||||
protected void onPostExecute(Void result) {
|
final String newTitle = text.toString().trim();
|
||||||
final Context context = getContext();
|
if (newTitle.equals(viewModel.getTitle().getValue())) return;
|
||||||
if (context == null) return;
|
observeDetailsChange(viewModel.updateTitle(newTitle));
|
||||||
if (ok) {
|
});
|
||||||
Toast.makeText(context, R.string.dms_action_success, Toast.LENGTH_SHORT).show();
|
binding.addMembers.setOnClickListener(v -> {
|
||||||
if (action.equals("update_title")) {
|
if (!isAdded()) return;
|
||||||
threadTitle = titleText.getText().toString();
|
final NavController navController = NavHostFragment.findNavController(this);
|
||||||
titleSend.setVisibility(View.GONE);
|
final NavDestination currentDestination = navController.getCurrentDestination();
|
||||||
titleText.clearFocus();
|
if (currentDestination == null) return;
|
||||||
DirectMessageThreadFragment.hasSentSomething = true;
|
if (currentDestination.getId() != R.id.directMessagesSettingsFragment) return;
|
||||||
} else if (action.equals("leave")) {
|
final Pair<List<DirectUser>, List<DirectUser>> users = viewModel.getUsers().getValue();
|
||||||
context.sendBroadcast(new Intent(DMRefreshBroadcastReceiver.ACTION_REFRESH_DM));
|
final long[] currentUserIds;
|
||||||
NavHostFragment.findNavController(DirectMessageSettingsFragment.this).popBackStack(R.id.directMessagesInboxFragment, false);
|
if (users != null && users.first != null) {
|
||||||
} else {
|
final List<DirectUser> currentMembers = users.first;
|
||||||
DirectMessageThreadFragment.hasSentSomething = true;
|
currentUserIds = currentMembers.stream()
|
||||||
}
|
.mapToLong(DirectUser::getPk)
|
||||||
} else Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show();
|
.sorted()
|
||||||
}
|
.toArray();
|
||||||
|
} else {
|
||||||
|
currentUserIds = new long[0];
|
||||||
|
}
|
||||||
|
final NavDirections directions = DirectMessageSettingsFragmentDirections.actionGlobalUserSearch(
|
||||||
|
true,
|
||||||
|
"Add users",
|
||||||
|
"Add",
|
||||||
|
currentUserIds
|
||||||
|
);
|
||||||
|
navController.navigate(directions);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void setupMembers() {
|
||||||
|
final Context context = getContext();
|
||||||
|
if (context == null) return;
|
||||||
|
binding.users.setLayoutManager(new LinearLayoutManager(context));
|
||||||
|
final DirectUser inviter = viewModel.getThread().getInviter();
|
||||||
|
usersAdapter = new DirectUsersAdapter(
|
||||||
|
inviter != null ? inviter.getPk() : -1,
|
||||||
|
(position, user, selected) -> {
|
||||||
|
// navigate to profile
|
||||||
|
},
|
||||||
|
(position, user) -> {
|
||||||
|
final ArrayList<Option<String>> options = viewModel.createUserOptions(user);
|
||||||
|
if (options == null || options.isEmpty()) return true;
|
||||||
|
final MultiOptionDialogFragment<String> fragment = MultiOptionDialogFragment.newInstance(-1, options);
|
||||||
|
fragment.setSingleCallback(new MultiOptionDialogFragment.MultiOptionDialogSingleCallback<String>() {
|
||||||
|
@Override
|
||||||
|
public void onSelect(final String action) {
|
||||||
|
if (action == null) return;
|
||||||
|
observeDetailsChange(viewModel.doAction(user, action));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCancel() {}
|
||||||
|
});
|
||||||
|
final FragmentManager fragmentManager = getChildFragmentManager();
|
||||||
|
fragment.show(fragmentManager, "actions");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
binding.users.setAdapter(usersAdapter);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void observeDetailsChange(@NonNull final LiveData<Resource<Object>> detailsChangeResourceLiveData) {
|
||||||
|
detailsChangeResourceLiveData.observe(getViewLifecycleOwner(), resource -> {
|
||||||
|
if (resource == null) return;
|
||||||
|
switch (resource.status) {
|
||||||
|
case SUCCESS:
|
||||||
|
case LOADING:
|
||||||
|
break;
|
||||||
|
case ERROR:
|
||||||
|
if (resource.message != null) {
|
||||||
|
Snackbar.make(binding.getRoot(), resource.message, Snackbar.LENGTH_LONG).show();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// class ChangeSettings extends AsyncTask<String, Void, Void> {
|
||||||
|
// String action, argument;
|
||||||
|
// boolean ok = false;
|
||||||
|
// private final String text;
|
||||||
|
//
|
||||||
|
// public ChangeSettings(final String text) {
|
||||||
|
// this.text = text;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// protected Void doInBackground(String... rawAction) {
|
||||||
|
// action = rawAction[0];
|
||||||
|
// if (rawAction.length == 2) argument = rawAction[1];
|
||||||
|
// final String url = "https://i.instagram.com/api/v1/direct_v2/threads/" + threadId + "/" + action + "/";
|
||||||
|
// try {
|
||||||
|
// String urlParameters = "_csrftoken=" + cookie.split("csrftoken=")[1].split(";")[0]
|
||||||
|
// + "&_uuid=" + Utils.settingsHelper.getString(Constants.DEVICE_UUID);
|
||||||
|
// if (action.equals("update_title")) {
|
||||||
|
// urlParameters += "&title=" + URLEncoder.encode(text, "UTF-8")
|
||||||
|
// .replaceAll("\\+", "%20").replaceAll("%21", "!").replaceAll("%27", "'")
|
||||||
|
// .replaceAll("%28", "(").replaceAll("%29", ")").replaceAll("%7E", "~");
|
||||||
|
// } else if (action.startsWith("remove_users"))
|
||||||
|
// urlParameters += ("&user_ids=[" + argument + "]");
|
||||||
|
// final HttpURLConnection urlConnection = (HttpURLConnection) new URL(url).openConnection();
|
||||||
|
// urlConnection.setRequestMethod("POST");
|
||||||
|
// urlConnection.setUseCaches(false);
|
||||||
|
// urlConnection.setRequestProperty("User-Agent", Constants.I_USER_AGENT);
|
||||||
|
// urlConnection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
|
||||||
|
// urlConnection.setRequestProperty("Content-Length", Integer.toString(urlParameters.getBytes().length));
|
||||||
|
// urlConnection.setDoOutput(true);
|
||||||
|
// DataOutputStream wr = new DataOutputStream(urlConnection.getOutputStream());
|
||||||
|
// wr.writeBytes(urlParameters);
|
||||||
|
// wr.flush();
|
||||||
|
// wr.close();
|
||||||
|
// urlConnection.connect();
|
||||||
|
// if (urlConnection.getResponseCode() == HttpURLConnection.HTTP_OK) {
|
||||||
|
// ok = true;
|
||||||
|
// }
|
||||||
|
// urlConnection.disconnect();
|
||||||
|
// } catch (Throwable ex) {
|
||||||
|
// Log.e("austin_debug", "unsend: " + ex);
|
||||||
|
// }
|
||||||
|
// return null;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// @Override
|
||||||
|
// protected void onPostExecute(Void result) {
|
||||||
|
// final Context context = getContext();
|
||||||
|
// if (context == null) return;
|
||||||
|
// if (ok) {
|
||||||
|
// Toast.makeText(context, R.string.dms_action_success, Toast.LENGTH_SHORT).show();
|
||||||
|
// if (action.equals("update_title")) {
|
||||||
|
// threadTitle = titleText.getText().toString();
|
||||||
|
// titleSend.setVisibility(View.GONE);
|
||||||
|
// titleText.clearFocus();
|
||||||
|
// DirectMessageThreadFragment.hasSentSomething = true;
|
||||||
|
// } else if (action.equals("leave")) {
|
||||||
|
// context.sendBroadcast(new Intent(DMRefreshBroadcastReceiver.ACTION_REFRESH_DM));
|
||||||
|
// NavHostFragment.findNavController(DirectMessageSettingsFragment.this).popBackStack(R.id.directMessagesInboxFragment, false);
|
||||||
|
// } else {
|
||||||
|
// DirectMessageThreadFragment.hasSentSomething = true;
|
||||||
|
// }
|
||||||
|
// } else Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show();
|
||||||
|
// }
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
@ -32,6 +32,7 @@ import androidx.core.content.ContextCompat;
|
|||||||
import androidx.fragment.app.Fragment;
|
import androidx.fragment.app.Fragment;
|
||||||
import androidx.lifecycle.MutableLiveData;
|
import androidx.lifecycle.MutableLiveData;
|
||||||
import androidx.lifecycle.ViewModelProvider;
|
import androidx.lifecycle.ViewModelProvider;
|
||||||
|
import androidx.lifecycle.ViewModelStoreOwner;
|
||||||
import androidx.navigation.NavBackStackEntry;
|
import androidx.navigation.NavBackStackEntry;
|
||||||
import androidx.navigation.NavController;
|
import androidx.navigation.NavController;
|
||||||
import androidx.navigation.NavDirections;
|
import androidx.navigation.NavDirections;
|
||||||
@ -249,8 +250,8 @@ public class DirectMessageThreadFragment extends Fragment {
|
|||||||
public boolean onOptionsItemSelected(@NonNull final MenuItem item) {
|
public boolean onOptionsItemSelected(@NonNull final MenuItem item) {
|
||||||
final int itemId = item.getItemId();
|
final int itemId = item.getItemId();
|
||||||
if (itemId == R.id.info) {
|
if (itemId == R.id.info) {
|
||||||
// final NavDirections action = DirectMessageThreadFragmentDirections.actionDMThreadFragmentToDMSettingsFragment(threadId, threadTitle);
|
final NavDirections action = DirectMessageThreadFragmentDirections.actionDMThreadFragmentToDMSettingsFragment(viewModel.getThreadId());
|
||||||
// NavHostFragment.findNavController(this).navigate(action);
|
NavHostFragment.findNavController(this).navigate(action);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (itemId == R.id.mark_as_seen) {
|
if (itemId == R.id.mark_as_seen) {
|
||||||
@ -372,7 +373,9 @@ public class DirectMessageThreadFragment extends Fragment {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void getInitialData() {
|
private void getInitialData() {
|
||||||
final DirectInboxViewModel threadListViewModel = new ViewModelProvider(fragmentActivity).get(DirectInboxViewModel.class);
|
final NavController navController = NavHostFragment.findNavController(this);
|
||||||
|
final ViewModelStoreOwner viewModelStoreOwner = navController.getViewModelStoreOwner(R.id.direct_messages_nav_graph);
|
||||||
|
final DirectInboxViewModel threadListViewModel = new ViewModelProvider(viewModelStoreOwner).get(DirectInboxViewModel.class);
|
||||||
final List<DirectThread> threads = threadListViewModel.getThreads().getValue();
|
final List<DirectThread> threads = threadListViewModel.getThreads().getValue();
|
||||||
final Optional<DirectThread> first = threads != null
|
final Optional<DirectThread> first = threads != null
|
||||||
? threads.stream()
|
? threads.stream()
|
||||||
|
@ -37,8 +37,8 @@ import awais.instagrabber.utils.Constants;
|
|||||||
import awais.instagrabber.utils.CookieUtils;
|
import awais.instagrabber.utils.CookieUtils;
|
||||||
import awais.instagrabber.utils.FlavorTown;
|
import awais.instagrabber.utils.FlavorTown;
|
||||||
import awais.instagrabber.utils.TextUtils;
|
import awais.instagrabber.utils.TextUtils;
|
||||||
import awais.instagrabber.webservices.ProfileService;
|
|
||||||
import awais.instagrabber.webservices.ServiceCallback;
|
import awais.instagrabber.webservices.ServiceCallback;
|
||||||
|
import awais.instagrabber.webservices.UserService;
|
||||||
|
|
||||||
import static awais.instagrabber.utils.Utils.settingsHelper;
|
import static awais.instagrabber.utils.Utils.settingsHelper;
|
||||||
|
|
||||||
@ -199,8 +199,8 @@ public class MorePreferencesFragment extends BasePreferencesFragment {
|
|||||||
|
|
||||||
// adds cookies to database for quick access
|
// adds cookies to database for quick access
|
||||||
final String uid = CookieUtils.getUserIdFromCookie(cookie);
|
final String uid = CookieUtils.getUserIdFromCookie(cookie);
|
||||||
final ProfileService profileService = ProfileService.getInstance();
|
final UserService userService = UserService.getInstance();
|
||||||
profileService.getUserInfo(uid, new ServiceCallback<UserInfo>() {
|
userService.getUserInfo(uid, new ServiceCallback<UserInfo>() {
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(final UserInfo result) {
|
public void onSuccess(final UserInfo result) {
|
||||||
// Log.d(TAG, "adding userInfo: " + result);
|
// Log.d(TAG, "adding userInfo: " + result);
|
||||||
|
@ -5,6 +5,7 @@ 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.DirectThreadBroadcastResponse;
|
import awais.instagrabber.repositories.responses.directmessages.DirectThreadBroadcastResponse;
|
||||||
|
import awais.instagrabber.repositories.responses.directmessages.DirectThreadDetailsChangeResponse;
|
||||||
import awais.instagrabber.repositories.responses.directmessages.DirectThreadFeedResponse;
|
import awais.instagrabber.repositories.responses.directmessages.DirectThreadFeedResponse;
|
||||||
import retrofit2.Call;
|
import retrofit2.Call;
|
||||||
import retrofit2.http.FieldMap;
|
import retrofit2.http.FieldMap;
|
||||||
@ -30,4 +31,29 @@ public interface DirectMessagesRepository {
|
|||||||
@POST("/api/v1/direct_v2/threads/broadcast/{item}/")
|
@POST("/api/v1/direct_v2/threads/broadcast/{item}/")
|
||||||
Call<DirectThreadBroadcastResponse> broadcast(@Path("item") String item,
|
Call<DirectThreadBroadcastResponse> broadcast(@Path("item") String item,
|
||||||
@FieldMap final Map<String, String> signedForm);
|
@FieldMap final Map<String, String> signedForm);
|
||||||
|
|
||||||
|
@FormUrlEncoded
|
||||||
|
@POST("/api/v1/direct_v2/threads/{threadId}/add_user/")
|
||||||
|
Call<DirectThreadDetailsChangeResponse> addUsers(@Path("threadId") String threadId,
|
||||||
|
@FieldMap final Map<String, String> form);
|
||||||
|
|
||||||
|
@FormUrlEncoded
|
||||||
|
@POST("/api/v1/direct_v2/threads/{threadId}/remove_users/")
|
||||||
|
Call<String> removeUsers(@Path("threadId") String threadId,
|
||||||
|
@FieldMap final Map<String, String> form);
|
||||||
|
|
||||||
|
@FormUrlEncoded
|
||||||
|
@POST("/api/v1/direct_v2/threads/{threadId}/update_title/")
|
||||||
|
Call<DirectThreadDetailsChangeResponse> updateTitle(@Path("threadId") String threadId,
|
||||||
|
@FieldMap final Map<String, String> form);
|
||||||
|
|
||||||
|
@FormUrlEncoded
|
||||||
|
@POST("/api/v1/direct_v2/threads/{threadId}/add_admins/")
|
||||||
|
Call<String> addAdmins(@Path("threadId") String threadId,
|
||||||
|
@FieldMap final Map<String, String> form);
|
||||||
|
|
||||||
|
@FormUrlEncoded
|
||||||
|
@POST("/api/v1/direct_v2/threads/{threadId}/remove_admins/")
|
||||||
|
Call<String> removeAdmins(@Path("threadId") String threadId,
|
||||||
|
@FieldMap final Map<String, String> form);
|
||||||
}
|
}
|
||||||
|
@ -9,9 +9,6 @@ import retrofit2.http.QueryMap;
|
|||||||
|
|
||||||
public interface ProfileRepository {
|
public interface ProfileRepository {
|
||||||
|
|
||||||
@GET("/api/v1/users/{uid}/info/")
|
|
||||||
Call<String> getUserInfo(@Path("uid") final String uid);
|
|
||||||
|
|
||||||
@GET("/api/v1/feed/user/{uid}/")
|
@GET("/api/v1/feed/user/{uid}/")
|
||||||
Call<String> fetch(@Path("uid") final String uid, @QueryMap Map<String, String> queryParams);
|
Call<String> fetch(@Path("uid") final String uid, @QueryMap Map<String, String> queryParams);
|
||||||
|
|
||||||
|
@ -0,0 +1,17 @@
|
|||||||
|
package awais.instagrabber.repositories;
|
||||||
|
|
||||||
|
import awais.instagrabber.repositories.responses.UserSearchResponse;
|
||||||
|
import retrofit2.Call;
|
||||||
|
import retrofit2.http.GET;
|
||||||
|
import retrofit2.http.Path;
|
||||||
|
import retrofit2.http.Query;
|
||||||
|
|
||||||
|
public interface UserRepository {
|
||||||
|
|
||||||
|
@GET("/api/v1/users/{uid}/info/")
|
||||||
|
Call<String> getUserInfo(@Path("uid") final String uid);
|
||||||
|
|
||||||
|
@GET("/api/v1/users/search/")
|
||||||
|
Call<UserSearchResponse> search(@Query("timezone_offset") float timezoneOffset,
|
||||||
|
@Query("q") String query);
|
||||||
|
}
|
@ -0,0 +1,35 @@
|
|||||||
|
package awais.instagrabber.repositories.responses;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import awais.instagrabber.repositories.responses.directmessages.DirectUser;
|
||||||
|
|
||||||
|
public class UserSearchResponse {
|
||||||
|
private final int numResults;
|
||||||
|
private final List<DirectUser> users;
|
||||||
|
private final boolean hasMore;
|
||||||
|
private final String status;
|
||||||
|
|
||||||
|
public UserSearchResponse(final int numResults, final List<DirectUser> users, final boolean hasMore, final String status) {
|
||||||
|
this.numResults = numResults;
|
||||||
|
this.users = users;
|
||||||
|
this.hasMore = hasMore;
|
||||||
|
this.status = status;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getNumResults() {
|
||||||
|
return numResults;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<DirectUser> getUsers() {
|
||||||
|
return users;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasMore() {
|
||||||
|
return hasMore;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getStatus() {
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,7 @@
|
|||||||
package awais.instagrabber.repositories.responses.directmessages;
|
package awais.instagrabber.repositories.responses.directmessages;
|
||||||
|
|
||||||
public class DirectInboxResponse {
|
public class DirectInboxResponse {
|
||||||
|
private final DirectUser viewer;
|
||||||
private final DirectInbox inbox;
|
private final DirectInbox inbox;
|
||||||
private final long seqId;
|
private final long seqId;
|
||||||
private final long snapshotAtMs;
|
private final long snapshotAtMs;
|
||||||
@ -8,12 +9,14 @@ public class DirectInboxResponse {
|
|||||||
private final DirectUser mostRecentInviter;
|
private final DirectUser mostRecentInviter;
|
||||||
private final String status;
|
private final String status;
|
||||||
|
|
||||||
public DirectInboxResponse(final DirectInbox inbox,
|
public DirectInboxResponse(final DirectUser viewer,
|
||||||
|
final DirectInbox inbox,
|
||||||
final long seqId,
|
final long seqId,
|
||||||
final long snapshotAtMs,
|
final long snapshotAtMs,
|
||||||
final int pendingRequestsTotal,
|
final int pendingRequestsTotal,
|
||||||
final DirectUser mostRecentInviter,
|
final DirectUser mostRecentInviter,
|
||||||
final String status) {
|
final String status) {
|
||||||
|
this.viewer = viewer;
|
||||||
this.inbox = inbox;
|
this.inbox = inbox;
|
||||||
this.seqId = seqId;
|
this.seqId = seqId;
|
||||||
this.snapshotAtMs = snapshotAtMs;
|
this.snapshotAtMs = snapshotAtMs;
|
||||||
@ -22,6 +25,10 @@ public class DirectInboxResponse {
|
|||||||
this.status = status;
|
this.status = status;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public DirectUser getViewer() {
|
||||||
|
return viewer;
|
||||||
|
}
|
||||||
|
|
||||||
public DirectInbox getInbox() {
|
public DirectInbox getInbox() {
|
||||||
return inbox;
|
return inbox;
|
||||||
}
|
}
|
||||||
|
@ -10,8 +10,8 @@ public class DirectThread {
|
|||||||
private final String threadId;
|
private final String threadId;
|
||||||
private final String threadV2Id;
|
private final String threadV2Id;
|
||||||
private final List<DirectUser> users;
|
private final List<DirectUser> users;
|
||||||
private final List<String> leftUsers;
|
private final List<DirectUser> leftUsers;
|
||||||
private final List<String> adminUserIds;
|
private final List<Long> adminUserIds;
|
||||||
private final List<DirectItem> items;
|
private final List<DirectItem> items;
|
||||||
private final long lastActivityAt;
|
private final long lastActivityAt;
|
||||||
private final boolean muted;
|
private final boolean muted;
|
||||||
@ -42,8 +42,8 @@ public class DirectThread {
|
|||||||
public DirectThread(final String threadId,
|
public DirectThread(final String threadId,
|
||||||
final String threadV2Id,
|
final String threadV2Id,
|
||||||
final List<DirectUser> users,
|
final List<DirectUser> users,
|
||||||
final List<String> leftUsers,
|
final List<DirectUser> leftUsers,
|
||||||
final List<String> adminUserIds,
|
final List<Long> adminUserIds,
|
||||||
final List<DirectItem> items,
|
final List<DirectItem> items,
|
||||||
final long lastActivityAt,
|
final long lastActivityAt,
|
||||||
final boolean muted,
|
final boolean muted,
|
||||||
@ -115,11 +115,11 @@ public class DirectThread {
|
|||||||
return users;
|
return users;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<String> getLeftUsers() {
|
public List<DirectUser> getLeftUsers() {
|
||||||
return leftUsers;
|
return leftUsers;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<String> getAdminUserIds() {
|
public List<Long> getAdminUserIds() {
|
||||||
return adminUserIds;
|
return adminUserIds;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,19 @@
|
|||||||
|
package awais.instagrabber.repositories.responses.directmessages;
|
||||||
|
|
||||||
|
public class DirectThreadDetailsChangeResponse {
|
||||||
|
private final DirectThread thread;
|
||||||
|
private final String status;
|
||||||
|
|
||||||
|
public DirectThreadDetailsChangeResponse(final DirectThread thread, final String status) {
|
||||||
|
this.thread = thread;
|
||||||
|
this.status = status;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DirectThread getThread() {
|
||||||
|
return thread;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getStatus() {
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
}
|
@ -1,8 +1,11 @@
|
|||||||
package awais.instagrabber.repositories.responses.directmessages;
|
package awais.instagrabber.repositories.responses.directmessages;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
import awais.instagrabber.models.ProfileModel;
|
import awais.instagrabber.models.ProfileModel;
|
||||||
|
|
||||||
public class DirectUser {
|
public class DirectUser implements Serializable {
|
||||||
private final long pk;
|
private final long pk;
|
||||||
private final String username;
|
private final String username;
|
||||||
private final String fullName;
|
private final String fullName;
|
||||||
@ -104,8 +107,8 @@ public class DirectUser {
|
|||||||
profileModel.isPrivate(),
|
profileModel.isPrivate(),
|
||||||
false,
|
false,
|
||||||
profileModel.isRequested(),
|
profileModel.isRequested(),
|
||||||
false
|
false,
|
||||||
),
|
profileModel.isRestricted()),
|
||||||
profileModel.isVerified(),
|
profileModel.isVerified(),
|
||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
@ -113,4 +116,18 @@ public class DirectUser {
|
|||||||
null
|
null
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(final Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
|
final DirectUser that = (DirectUser) o;
|
||||||
|
return pk == that.pk &&
|
||||||
|
Objects.equals(username, that.username);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(pk, username);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,25 +1,31 @@
|
|||||||
package awais.instagrabber.repositories.responses.directmessages;
|
package awais.instagrabber.repositories.responses.directmessages;
|
||||||
|
|
||||||
public class DirectUserFriendshipStatus {
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
public class DirectUserFriendshipStatus implements Serializable {
|
||||||
private final boolean following;
|
private final boolean following;
|
||||||
private final boolean blocking;
|
private final boolean blocking;
|
||||||
private final boolean isPrivate;
|
private final boolean isPrivate;
|
||||||
private final boolean incomingRequest;
|
private final boolean incomingRequest;
|
||||||
private final boolean outgoingRequest;
|
private final boolean outgoingRequest;
|
||||||
private final boolean isBestie;
|
private final boolean isBestie;
|
||||||
|
private final boolean isRestricted;
|
||||||
|
|
||||||
public DirectUserFriendshipStatus(final boolean following,
|
public DirectUserFriendshipStatus(final boolean following,
|
||||||
final boolean blocking,
|
final boolean blocking,
|
||||||
final boolean isPrivate,
|
final boolean isPrivate,
|
||||||
final boolean incomingRequest,
|
final boolean incomingRequest,
|
||||||
final boolean outgoingRequest,
|
final boolean outgoingRequest,
|
||||||
final boolean isBestie) {
|
final boolean isBestie, final boolean isRestricted) {
|
||||||
this.following = following;
|
this.following = following;
|
||||||
this.blocking = blocking;
|
this.blocking = blocking;
|
||||||
this.isPrivate = isPrivate;
|
this.isPrivate = isPrivate;
|
||||||
this.incomingRequest = incomingRequest;
|
this.incomingRequest = incomingRequest;
|
||||||
this.outgoingRequest = outgoingRequest;
|
this.outgoingRequest = outgoingRequest;
|
||||||
this.isBestie = isBestie;
|
this.isBestie = isBestie;
|
||||||
|
this.isRestricted = isRestricted;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isFollowing() {
|
public boolean isFollowing() {
|
||||||
@ -46,15 +52,21 @@ public class DirectUserFriendshipStatus {
|
|||||||
return isBestie;
|
return isBestie;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isRestricted() {
|
||||||
|
return isRestricted;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "DirectInboxFeedResponseFriendshipStatus{" +
|
return "DirectInboxFeedResponseFriendshipStatus{" +
|
||||||
"following=" + following +
|
"following=" + following +
|
||||||
", blocking=" + blocking +
|
", blocking=" + blocking +
|
||||||
", is_private=" + isPrivate +
|
", isPrivate=" + isPrivate +
|
||||||
", incomingRequest=" + incomingRequest +
|
", incomingRequest=" + incomingRequest +
|
||||||
", outgoingRequest=" + outgoingRequest +
|
", outgoingRequest=" + outgoingRequest +
|
||||||
", isBestie=" + isBestie +
|
", isBestie=" + isBestie +
|
||||||
|
", isRestricted" + isRestricted +
|
||||||
'}';
|
'}';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
90
app/src/main/java/awais/instagrabber/utils/Debouncer.java
Normal file
90
app/src/main/java/awais/instagrabber/utils/Debouncer.java
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
package awais.instagrabber.utils;
|
||||||
|
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
|
import java.util.concurrent.ScheduledFuture;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
public class Debouncer<T> {
|
||||||
|
private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
|
||||||
|
private final ConcurrentHashMap<T, TimerTask> delayedMap = new ConcurrentHashMap<>();
|
||||||
|
private final ConcurrentHashMap<T, ScheduledFuture<?>> futureMap = new ConcurrentHashMap<>();
|
||||||
|
private final Callback<T> callback;
|
||||||
|
private final int interval;
|
||||||
|
|
||||||
|
public Debouncer(Callback<T> c, int interval) {
|
||||||
|
this.callback = c;
|
||||||
|
this.interval = interval;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void call(T key) {
|
||||||
|
TimerTask task = new TimerTask(key);
|
||||||
|
|
||||||
|
TimerTask prev;
|
||||||
|
do {
|
||||||
|
prev = delayedMap.putIfAbsent(key, task);
|
||||||
|
if (prev == null) {
|
||||||
|
final ScheduledFuture<?> future = scheduler.schedule(task, interval, TimeUnit.MILLISECONDS);
|
||||||
|
futureMap.put(key, future);
|
||||||
|
}
|
||||||
|
} while (prev != null && !prev.extend()); // Exit only if new task was added to map, or existing task was extended successfully
|
||||||
|
}
|
||||||
|
|
||||||
|
public void terminate() {
|
||||||
|
scheduler.shutdownNow();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void cancel(final T key) {
|
||||||
|
delayedMap.remove(key);
|
||||||
|
final ScheduledFuture<?> future = futureMap.get(key);
|
||||||
|
if (future != null) {
|
||||||
|
future.cancel(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The task that wakes up when the wait time elapses
|
||||||
|
private class TimerTask implements Runnable {
|
||||||
|
private final T key;
|
||||||
|
private long dueTime;
|
||||||
|
private final Object lock = new Object();
|
||||||
|
|
||||||
|
public TimerTask(T key) {
|
||||||
|
this.key = key;
|
||||||
|
extend();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean extend() {
|
||||||
|
synchronized (lock) {
|
||||||
|
if (dueTime < 0) // Task has been shutdown
|
||||||
|
return false;
|
||||||
|
dueTime = System.currentTimeMillis() + interval;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void run() {
|
||||||
|
synchronized (lock) {
|
||||||
|
long remaining = dueTime - System.currentTimeMillis();
|
||||||
|
if (remaining > 0) { // Re-schedule task
|
||||||
|
scheduler.schedule(this, remaining, TimeUnit.MILLISECONDS);
|
||||||
|
} else { // Mark as terminated and invoke callback
|
||||||
|
dueTime = -1;
|
||||||
|
try {
|
||||||
|
callback.call(key);
|
||||||
|
} catch (Exception e) {
|
||||||
|
callback.onError(e);
|
||||||
|
} finally {
|
||||||
|
delayedMap.remove(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface Callback<T> {
|
||||||
|
void call(T key);
|
||||||
|
|
||||||
|
void onError(Throwable t);
|
||||||
|
}
|
||||||
|
}
|
@ -24,6 +24,7 @@ import android.view.Gravity;
|
|||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.Window;
|
import android.view.Window;
|
||||||
import android.view.WindowManager;
|
import android.view.WindowManager;
|
||||||
|
import android.view.inputmethod.InputMethodManager;
|
||||||
import android.webkit.MimeTypeMap;
|
import android.webkit.MimeTypeMap;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
@ -340,4 +341,17 @@ public final class Utils {
|
|||||||
callback
|
callback
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void hideKeyboard(final View view) {
|
||||||
|
if (view == null) return;
|
||||||
|
final Context context = view.getContext();
|
||||||
|
if (context == null) return;
|
||||||
|
try {
|
||||||
|
final InputMethodManager manager = (InputMethodManager) context.getSystemService(Activity.INPUT_METHOD_SERVICE);
|
||||||
|
if (manager == null) return;
|
||||||
|
manager.hideSoftInputFromWindow(view.getWindowToken(), 0);
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e(TAG, "hideKeyboard: ", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,10 +5,14 @@ import android.graphics.drawable.Drawable;
|
|||||||
import android.graphics.drawable.GradientDrawable;
|
import android.graphics.drawable.GradientDrawable;
|
||||||
import android.graphics.drawable.ShapeDrawable;
|
import android.graphics.drawable.ShapeDrawable;
|
||||||
import android.graphics.drawable.shapes.RoundRectShape;
|
import android.graphics.drawable.shapes.RoundRectShape;
|
||||||
|
import android.view.View;
|
||||||
import android.widget.FrameLayout;
|
import android.widget.FrameLayout;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
import androidx.annotation.ColorInt;
|
import androidx.annotation.ColorInt;
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
import androidx.core.content.res.ResourcesCompat;
|
import androidx.core.content.res.ResourcesCompat;
|
||||||
|
import androidx.core.util.Pair;
|
||||||
|
|
||||||
public final class ViewUtils {
|
public final class ViewUtils {
|
||||||
|
|
||||||
@ -53,4 +57,16 @@ public final class ViewUtils {
|
|||||||
private static int getSize(float size) {
|
private static int getSize(float size) {
|
||||||
return (int) (size < 0 ? size : Utils.convertDpToPx(size));
|
return (int) (size < 0 ? size : Utils.convertDpToPx(size));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Pair<Integer, Integer> measure(@NonNull final View view, @NonNull final View parent) {
|
||||||
|
view.measure(
|
||||||
|
View.MeasureSpec.makeMeasureSpec(parent.getWidth(), View.MeasureSpec.UNSPECIFIED),
|
||||||
|
View.MeasureSpec.makeMeasureSpec(parent.getHeight(), View.MeasureSpec.UNSPECIFIED)
|
||||||
|
);
|
||||||
|
return new Pair<>(view.getMeasuredHeight(), view.getMeasuredWidth());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static float getTextViewValueWidth(final TextView textView, final String text) {
|
||||||
|
return textView.getPaint().measureText(text);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,6 +14,7 @@ import awais.instagrabber.repositories.responses.directmessages.DirectBadgeCount
|
|||||||
import awais.instagrabber.repositories.responses.directmessages.DirectInbox;
|
import awais.instagrabber.repositories.responses.directmessages.DirectInbox;
|
||||||
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.DirectThread;
|
||||||
|
import awais.instagrabber.repositories.responses.directmessages.DirectUser;
|
||||||
import awais.instagrabber.utils.Constants;
|
import awais.instagrabber.utils.Constants;
|
||||||
import awais.instagrabber.utils.CookieUtils;
|
import awais.instagrabber.utils.CookieUtils;
|
||||||
import awais.instagrabber.utils.TextUtils;
|
import awais.instagrabber.utils.TextUtils;
|
||||||
@ -38,6 +39,7 @@ public class DirectInboxViewModel extends ViewModel {
|
|||||||
private long seqId;
|
private long seqId;
|
||||||
private String cursor;
|
private String cursor;
|
||||||
private boolean hasOlder = true;
|
private boolean hasOlder = true;
|
||||||
|
private DirectUser viewer;
|
||||||
|
|
||||||
public DirectInboxViewModel() {
|
public DirectInboxViewModel() {
|
||||||
final String cookie = settingsHelper.getString(Constants.COOKIE);
|
final String cookie = settingsHelper.getString(Constants.COOKIE);
|
||||||
@ -76,6 +78,10 @@ public class DirectInboxViewModel extends ViewModel {
|
|||||||
return fetchingInbox;
|
return fetchingInbox;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public DirectUser getViewer() {
|
||||||
|
return viewer;
|
||||||
|
}
|
||||||
|
|
||||||
public void fetchInbox() {
|
public void fetchInbox() {
|
||||||
if ((fetchingInbox.getValue() != null && fetchingInbox.getValue()) || !hasOlder) return;
|
if ((fetchingInbox.getValue() != null && fetchingInbox.getValue()) || !hasOlder) return;
|
||||||
stopCurrentInboxRequest();
|
stopCurrentInboxRequest();
|
||||||
@ -108,6 +114,9 @@ public class DirectInboxViewModel extends ViewModel {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
seqId = response.getSeqId();
|
seqId = response.getSeqId();
|
||||||
|
if (viewer == null) {
|
||||||
|
viewer = response.getViewer();
|
||||||
|
}
|
||||||
final DirectInbox inbox = response.getInbox();
|
final DirectInbox inbox = response.getInbox();
|
||||||
final List<DirectThread> threads = inbox.getThreads();
|
final List<DirectThread> threads = inbox.getThreads();
|
||||||
if (!TextUtils.isEmpty(cursor)) {
|
if (!TextUtils.isEmpty(cursor)) {
|
||||||
|
@ -0,0 +1,457 @@
|
|||||||
|
package awais.instagrabber.viewmodels;
|
||||||
|
|
||||||
|
import android.app.Application;
|
||||||
|
import android.content.res.Resources;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.StringRes;
|
||||||
|
import androidx.core.util.Pair;
|
||||||
|
import androidx.lifecycle.AndroidViewModel;
|
||||||
|
import androidx.lifecycle.LiveData;
|
||||||
|
import androidx.lifecycle.MutableLiveData;
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
|
|
||||||
|
import org.json.JSONException;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import awais.instagrabber.R;
|
||||||
|
import awais.instagrabber.dialogs.MultiOptionDialogFragment.Option;
|
||||||
|
import awais.instagrabber.models.Resource;
|
||||||
|
import awais.instagrabber.repositories.responses.FriendshipRepoChangeRootResponse;
|
||||||
|
import awais.instagrabber.repositories.responses.FriendshipRepoRestrictRootResponse;
|
||||||
|
import awais.instagrabber.repositories.responses.directmessages.DirectThread;
|
||||||
|
import awais.instagrabber.repositories.responses.directmessages.DirectThreadDetailsChangeResponse;
|
||||||
|
import awais.instagrabber.repositories.responses.directmessages.DirectUser;
|
||||||
|
import awais.instagrabber.utils.Constants;
|
||||||
|
import awais.instagrabber.utils.CookieUtils;
|
||||||
|
import awais.instagrabber.utils.TextUtils;
|
||||||
|
import awais.instagrabber.webservices.DirectMessagesService;
|
||||||
|
import awais.instagrabber.webservices.FriendshipService;
|
||||||
|
import awais.instagrabber.webservices.ServiceCallback;
|
||||||
|
import okhttp3.ResponseBody;
|
||||||
|
import retrofit2.Call;
|
||||||
|
import retrofit2.Callback;
|
||||||
|
import retrofit2.Response;
|
||||||
|
|
||||||
|
import static awais.instagrabber.utils.Utils.settingsHelper;
|
||||||
|
|
||||||
|
public class DirectSettingsViewModel extends AndroidViewModel {
|
||||||
|
private static final String TAG = DirectSettingsViewModel.class.getSimpleName();
|
||||||
|
private static final String ACTION_KICK = "kick";
|
||||||
|
private static final String ACTION_MAKE_ADMIN = "make_admin";
|
||||||
|
private static final String ACTION_REMOVE_ADMIN = "remove_admin";
|
||||||
|
private static final String ACTION_BLOCK = "block";
|
||||||
|
private static final String ACTION_UNBLOCK = "unblock";
|
||||||
|
// private static final String ACTION_REPORT = "report";
|
||||||
|
private static final String ACTION_RESTRICT = "restrict";
|
||||||
|
private static final String ACTION_UNRESTRICT = "unrestrict";
|
||||||
|
|
||||||
|
private final MutableLiveData<Pair<List<DirectUser>, List<DirectUser>>> users = new MutableLiveData<>(
|
||||||
|
new Pair<>(Collections.emptyList(), Collections.emptyList()));
|
||||||
|
private final MutableLiveData<String> title = new MutableLiveData<>("");
|
||||||
|
private final MutableLiveData<List<Long>> adminUserIds = new MutableLiveData<>(Collections.emptyList());
|
||||||
|
private final DirectMessagesService directMessagesService;
|
||||||
|
|
||||||
|
private DirectThread thread;
|
||||||
|
private final String userId;
|
||||||
|
private boolean viewerIsAdmin;
|
||||||
|
private final Resources resources;
|
||||||
|
private final FriendshipService friendshipService;
|
||||||
|
private final String csrfToken;
|
||||||
|
private DirectUser viewer;
|
||||||
|
|
||||||
|
public DirectSettingsViewModel(final Application application) {
|
||||||
|
super(application);
|
||||||
|
final String cookie = settingsHelper.getString(Constants.COOKIE);
|
||||||
|
userId = CookieUtils.getUserIdFromCookie(cookie);
|
||||||
|
final String deviceUuid = settingsHelper.getString(Constants.DEVICE_UUID);
|
||||||
|
csrfToken = CookieUtils.getCsrfTokenFromCookie(cookie);
|
||||||
|
if (TextUtils.isEmpty(csrfToken) || TextUtils.isEmpty(userId) || TextUtils.isEmpty(deviceUuid)) {
|
||||||
|
throw new IllegalArgumentException("User is not logged in!");
|
||||||
|
}
|
||||||
|
directMessagesService = DirectMessagesService.getInstance(csrfToken, userId, deviceUuid);
|
||||||
|
friendshipService = FriendshipService.getInstance();
|
||||||
|
resources = getApplication().getResources();
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public DirectThread getThread() {
|
||||||
|
return thread;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setThread(@NonNull final DirectThread thread) {
|
||||||
|
this.thread = thread;
|
||||||
|
List<DirectUser> users = thread.getUsers();
|
||||||
|
if (viewer != null) {
|
||||||
|
final ImmutableList.Builder<DirectUser> builder = ImmutableList.<DirectUser>builder().add(viewer);
|
||||||
|
if (users != null) {
|
||||||
|
builder.addAll(users);
|
||||||
|
}
|
||||||
|
users = builder.build();
|
||||||
|
}
|
||||||
|
this.users.postValue(new Pair<>(users, thread.getLeftUsers()));
|
||||||
|
setTitle(thread.getThreadTitle());
|
||||||
|
final List<Long> adminUserIds = thread.getAdminUserIds();
|
||||||
|
this.adminUserIds.postValue(adminUserIds);
|
||||||
|
viewerIsAdmin = adminUserIds.contains(Long.parseLong(userId));
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isGroup() {
|
||||||
|
if (thread != null) {
|
||||||
|
return thread.isGroup();
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LiveData<Pair<List<DirectUser>, List<DirectUser>>> getUsers() {
|
||||||
|
return users;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LiveData<String> getTitle() {
|
||||||
|
return title;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTitle(final String title) {
|
||||||
|
if (title == null) {
|
||||||
|
this.title.postValue("");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.title.postValue(title.trim());
|
||||||
|
}
|
||||||
|
|
||||||
|
public LiveData<List<Long>> getAdminUserIds() {
|
||||||
|
return adminUserIds;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LiveData<Resource<Object>> updateTitle(final String newTitle) {
|
||||||
|
final MutableLiveData<Resource<Object>> data = new MutableLiveData<>();
|
||||||
|
final Call<DirectThreadDetailsChangeResponse> addUsersRequest = directMessagesService
|
||||||
|
.updateTitle(thread.getThreadId(), newTitle.trim());
|
||||||
|
handleDetailsChangeRequest(data, addUsersRequest);
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LiveData<Resource<Object>> addMembers(final Set<DirectUser> users) {
|
||||||
|
final MutableLiveData<Resource<Object>> data = new MutableLiveData<>();
|
||||||
|
final Call<DirectThreadDetailsChangeResponse> addUsersRequest = directMessagesService
|
||||||
|
.addUsers(thread.getThreadId(), users.stream().map(DirectUser::getPk).collect(Collectors.toList()));
|
||||||
|
handleDetailsChangeRequest(data, addUsersRequest);
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LiveData<Resource<Object>> removeMember(final DirectUser user) {
|
||||||
|
final MutableLiveData<Resource<Object>> data = new MutableLiveData<>();
|
||||||
|
final Call<String> request = directMessagesService
|
||||||
|
.removeUsers(thread.getThreadId(), Collections.singleton(user.getPk()));
|
||||||
|
request.enqueue(new Callback<String>() {
|
||||||
|
@Override
|
||||||
|
public void onResponse(@NonNull final Call<String> call, @NonNull final Response<String> response) {
|
||||||
|
if (!response.isSuccessful()) {
|
||||||
|
handleAdminChangeResponseError(response, data);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Pair<List<DirectUser>, List<DirectUser>> usersValue = users.getValue();
|
||||||
|
if (usersValue == null) {
|
||||||
|
usersValue = new Pair<>(Collections.emptyList(), Collections.emptyList());
|
||||||
|
}
|
||||||
|
List<DirectUser> activeUsers = usersValue.first;
|
||||||
|
if (activeUsers == null) {
|
||||||
|
activeUsers = Collections.emptyList();
|
||||||
|
}
|
||||||
|
final List<DirectUser> updatedActiveUsers = activeUsers.stream()
|
||||||
|
.filter(user1 -> user1.getPk() != user.getPk())
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
List<DirectUser> leftUsers = usersValue.second;
|
||||||
|
if (leftUsers == null) {
|
||||||
|
leftUsers = Collections.emptyList();
|
||||||
|
}
|
||||||
|
final ImmutableList<DirectUser> updateLeftUsers = ImmutableList.<DirectUser>builder()
|
||||||
|
.addAll(leftUsers)
|
||||||
|
.add(user)
|
||||||
|
.build();
|
||||||
|
users.postValue(new Pair<>(updatedActiveUsers, updateLeftUsers));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(@NonNull final Call<String> call, @NonNull final Throwable t) {
|
||||||
|
Log.e(TAG, "onFailure: ", t);
|
||||||
|
data.postValue(Resource.error(t.getMessage(), null));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
private LiveData<Resource<Object>> makeAdmin(final DirectUser user) {
|
||||||
|
final MutableLiveData<Resource<Object>> data = new MutableLiveData<>();
|
||||||
|
if (isAdmin(user)) return data;
|
||||||
|
final Call<String> request = directMessagesService.addAdmins(thread.getThreadId(), Collections.singleton(user.getPk()));
|
||||||
|
request.enqueue(new Callback<String>() {
|
||||||
|
@Override
|
||||||
|
public void onResponse(@NonNull final Call<String> call, @NonNull final Response<String> response) {
|
||||||
|
if (!response.isSuccessful()) {
|
||||||
|
handleAdminChangeResponseError(response, data);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final List<Long> currentAdmins = adminUserIds.getValue();
|
||||||
|
adminUserIds.postValue(ImmutableList.<Long>builder()
|
||||||
|
.addAll(currentAdmins != null ? currentAdmins : Collections.emptyList())
|
||||||
|
.add(user.getPk())
|
||||||
|
.build());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(@NonNull final Call<String> call, @NonNull final Throwable t) {
|
||||||
|
Log.e(TAG, "onFailure: ", t);
|
||||||
|
data.postValue(Resource.error(t.getMessage(), null));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
private LiveData<Resource<Object>> removeAdmin(final DirectUser user) {
|
||||||
|
final MutableLiveData<Resource<Object>> data = new MutableLiveData<>();
|
||||||
|
if (!isAdmin(user)) return data;
|
||||||
|
final Call<String> request = directMessagesService.removeAdmins(thread.getThreadId(), Collections.singleton(user.getPk()));
|
||||||
|
request.enqueue(new Callback<String>() {
|
||||||
|
@Override
|
||||||
|
public void onResponse(@NonNull final Call<String> call, @NonNull final Response<String> response) {
|
||||||
|
if (!response.isSuccessful()) {
|
||||||
|
handleAdminChangeResponseError(response, data);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final List<Long> currentAdmins = adminUserIds.getValue();
|
||||||
|
if (currentAdmins == null) return;
|
||||||
|
adminUserIds.postValue(currentAdmins.stream()
|
||||||
|
.filter(userId1 -> userId1 != user.getPk())
|
||||||
|
.collect(Collectors.toList()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(@NonNull final Call<String> call, @NonNull final Throwable t) {
|
||||||
|
Log.e(TAG, "onFailure: ", t);
|
||||||
|
data.postValue(Resource.error(t.getMessage(), null));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleAdminChangeResponseError(@NonNull final Response<String> response,
|
||||||
|
final MutableLiveData<Resource<Object>> data) {
|
||||||
|
final ResponseBody errorBody = response.errorBody();
|
||||||
|
if (errorBody == null) {
|
||||||
|
handleErrorResponse(response, data);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
final JSONObject json = new JSONObject(errorBody.string());
|
||||||
|
if (json.has("message")) {
|
||||||
|
data.postValue(Resource.error(json.getString("message"), null));
|
||||||
|
}
|
||||||
|
} catch (IOException | JSONException e) {
|
||||||
|
Log.e(TAG, "onResponse: ", e);
|
||||||
|
data.postValue(Resource.error(e.getMessage(), null));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private LiveData<Resource<Object>> blockUser(final DirectUser user) {
|
||||||
|
final MutableLiveData<Resource<Object>> data = new MutableLiveData<>();
|
||||||
|
friendshipService.block(userId, String.valueOf(user.getPk()), csrfToken, new ServiceCallback<FriendshipRepoChangeRootResponse>() {
|
||||||
|
@Override
|
||||||
|
public void onSuccess(final FriendshipRepoChangeRootResponse result) {
|
||||||
|
// refresh thread
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(final Throwable t) {
|
||||||
|
Log.e(TAG, "onFailure: ", t);
|
||||||
|
data.postValue(Resource.error(t.getMessage(), null));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
private LiveData<Resource<Object>> unblockUser(final DirectUser user) {
|
||||||
|
final MutableLiveData<Resource<Object>> data = new MutableLiveData<>();
|
||||||
|
friendshipService.unblock(userId, String.valueOf(user.getPk()), csrfToken, new ServiceCallback<FriendshipRepoChangeRootResponse>() {
|
||||||
|
@Override
|
||||||
|
public void onSuccess(final FriendshipRepoChangeRootResponse result) {
|
||||||
|
// refresh thread
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(final Throwable t) {
|
||||||
|
Log.e(TAG, "onFailure: ", t);
|
||||||
|
data.postValue(Resource.error(t.getMessage(), null));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
private LiveData<Resource<Object>> restrictUser(final DirectUser user) {
|
||||||
|
final MutableLiveData<Resource<Object>> data = new MutableLiveData<>();
|
||||||
|
friendshipService.toggleRestrict(String.valueOf(user.getPk()), true, csrfToken, new ServiceCallback<FriendshipRepoRestrictRootResponse>() {
|
||||||
|
@Override
|
||||||
|
public void onSuccess(final FriendshipRepoRestrictRootResponse result) {
|
||||||
|
// refresh thread
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(final Throwable t) {
|
||||||
|
Log.e(TAG, "onFailure: ", t);
|
||||||
|
data.postValue(Resource.error(t.getMessage(), null));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
private LiveData<Resource<Object>> unRestrictUser(final DirectUser user) {
|
||||||
|
final MutableLiveData<Resource<Object>> data = new MutableLiveData<>();
|
||||||
|
friendshipService.toggleRestrict(String.valueOf(user.getPk()), false, csrfToken, new ServiceCallback<FriendshipRepoRestrictRootResponse>() {
|
||||||
|
@Override
|
||||||
|
public void onSuccess(final FriendshipRepoRestrictRootResponse result) {
|
||||||
|
// refresh thread
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(final Throwable t) {
|
||||||
|
Log.e(TAG, "onFailure: ", t);
|
||||||
|
data.postValue(Resource.error(t.getMessage(), null));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleDetailsChangeRequest(final MutableLiveData<Resource<Object>> data,
|
||||||
|
final Call<DirectThreadDetailsChangeResponse> addUsersRequest) {
|
||||||
|
addUsersRequest.enqueue(new Callback<DirectThreadDetailsChangeResponse>() {
|
||||||
|
@Override
|
||||||
|
public void onResponse(@NonNull final Call<DirectThreadDetailsChangeResponse> call,
|
||||||
|
@NonNull final Response<DirectThreadDetailsChangeResponse> response) {
|
||||||
|
if (!response.isSuccessful()) {
|
||||||
|
handleErrorResponse(response, data);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final DirectThreadDetailsChangeResponse addUserResponse = response.body();
|
||||||
|
if (addUserResponse == null) {
|
||||||
|
data.postValue(Resource.error("Response is null", null));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
data.postValue(Resource.success(new Object()));
|
||||||
|
final DirectThread thread = addUserResponse.getThread();
|
||||||
|
if (thread != null) {
|
||||||
|
setThread(thread);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(@NonNull final Call<DirectThreadDetailsChangeResponse> call, @NonNull final Throwable t) {
|
||||||
|
Log.e(TAG, "onFailure: ", t);
|
||||||
|
data.postValue(Resource.error(t.getMessage(), null));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleErrorResponse(@NonNull final Response<?> response,
|
||||||
|
final MutableLiveData<Resource<Object>> data) {
|
||||||
|
final ResponseBody errorBody = response.errorBody();
|
||||||
|
if (errorBody == null) {
|
||||||
|
data.postValue(Resource.error("Request failed!", null));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
data.postValue(Resource.error(errorBody.string(), null));
|
||||||
|
} catch (IOException e) {
|
||||||
|
Log.e(TAG, "onResponse: ", e);
|
||||||
|
data.postValue(Resource.error(e.getMessage(), null));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ArrayList<Option<String>> createUserOptions(final DirectUser user) {
|
||||||
|
final ArrayList<Option<String>> options = new ArrayList<>();
|
||||||
|
if (user == null || isSelf(user) || hasLeft(user)) {
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
if (viewerIsAdmin) {
|
||||||
|
options.add(new Option<>(getString(R.string.dms_action_kick), ACTION_KICK));
|
||||||
|
|
||||||
|
final boolean isAdmin = isAdmin(user);
|
||||||
|
options.add(new Option<>(
|
||||||
|
isAdmin ? getString(R.string.dms_action_remove_admin) : getString(R.string.dms_action_make_admin),
|
||||||
|
isAdmin ? ACTION_REMOVE_ADMIN : ACTION_MAKE_ADMIN
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
final boolean blocking = user.getFriendshipStatus().isBlocking();
|
||||||
|
options.add(new Option<>(
|
||||||
|
blocking ? getString(R.string.unblock) : getString(R.string.block),
|
||||||
|
blocking ? ACTION_UNBLOCK : ACTION_BLOCK
|
||||||
|
));
|
||||||
|
|
||||||
|
// options.add(new Option<>(getString(R.string.report), ACTION_REPORT));
|
||||||
|
|
||||||
|
if (!isGroup()) {
|
||||||
|
final boolean restricted = user.getFriendshipStatus().isRestricted();
|
||||||
|
options.add(new Option<>(
|
||||||
|
restricted ? getString(R.string.unrestrict) : getString(R.string.restrict),
|
||||||
|
restricted ? ACTION_UNRESTRICT : ACTION_RESTRICT
|
||||||
|
));
|
||||||
|
}
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean hasLeft(final DirectUser user) {
|
||||||
|
final Pair<List<DirectUser>, List<DirectUser>> users = this.users.getValue();
|
||||||
|
if (users == null || users.second == null) return false;
|
||||||
|
return users.second.contains(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isAdmin(final DirectUser user) {
|
||||||
|
final List<Long> adminUserIdsValue = adminUserIds.getValue();
|
||||||
|
return adminUserIdsValue != null && adminUserIdsValue.contains(user.getPk());
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isSelf(final DirectUser user) {
|
||||||
|
return user.getPk() == Long.parseLong(userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getString(@StringRes final int resId) {
|
||||||
|
return resources.getString(resId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public LiveData<Resource<Object>> doAction(final DirectUser user, final String action) {
|
||||||
|
if (user == null || action == null) return null;
|
||||||
|
switch (action) {
|
||||||
|
case ACTION_KICK:
|
||||||
|
return removeMember(user);
|
||||||
|
case ACTION_MAKE_ADMIN:
|
||||||
|
return makeAdmin(user);
|
||||||
|
case ACTION_REMOVE_ADMIN:
|
||||||
|
return removeAdmin(user);
|
||||||
|
case ACTION_BLOCK:
|
||||||
|
return blockUser(user);
|
||||||
|
case ACTION_UNBLOCK:
|
||||||
|
return unblockUser(user);
|
||||||
|
// case ACTION_REPORT:
|
||||||
|
// break;
|
||||||
|
case ACTION_RESTRICT:
|
||||||
|
return restrictUser(user);
|
||||||
|
case ACTION_UNRESTRICT:
|
||||||
|
return unRestrictUser(user);
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setViewer(final DirectUser viewer) {
|
||||||
|
this.viewer = viewer;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,155 @@
|
|||||||
|
package awais.instagrabber.viewmodels;
|
||||||
|
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.lifecycle.LiveData;
|
||||||
|
import androidx.lifecycle.MutableLiveData;
|
||||||
|
import androidx.lifecycle.ViewModel;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import awais.instagrabber.models.Resource;
|
||||||
|
import awais.instagrabber.repositories.responses.UserSearchResponse;
|
||||||
|
import awais.instagrabber.repositories.responses.directmessages.DirectUser;
|
||||||
|
import awais.instagrabber.utils.Debouncer;
|
||||||
|
import awais.instagrabber.webservices.UserService;
|
||||||
|
import okhttp3.ResponseBody;
|
||||||
|
import retrofit2.Call;
|
||||||
|
import retrofit2.Callback;
|
||||||
|
import retrofit2.Response;
|
||||||
|
|
||||||
|
public class UserSearchViewModel extends ViewModel {
|
||||||
|
private static final String TAG = UserSearchViewModel.class.getSimpleName();
|
||||||
|
public static final String DEBOUNCE_KEY = "search";
|
||||||
|
|
||||||
|
private String prevQuery;
|
||||||
|
private String currentQuery;
|
||||||
|
private Call<UserSearchResponse> searchRequest;
|
||||||
|
|
||||||
|
private final MutableLiveData<Resource<List<DirectUser>>> users = new MutableLiveData<>();
|
||||||
|
private final MutableLiveData<Boolean> showAction = new MutableLiveData<>(false);
|
||||||
|
private final Debouncer<String> searchDebouncer;
|
||||||
|
private final Set<DirectUser> selectedUsers = new HashSet<>();
|
||||||
|
private final UserService userService;
|
||||||
|
private long[] hideUserIds;
|
||||||
|
|
||||||
|
public UserSearchViewModel() {
|
||||||
|
userService = UserService.getInstance();
|
||||||
|
final Debouncer.Callback<String> searchCallback = new Debouncer.Callback<String>() {
|
||||||
|
@Override
|
||||||
|
public void call(final String key) {
|
||||||
|
if (userService == null || (currentQuery != null && currentQuery.equalsIgnoreCase(prevQuery))) return;
|
||||||
|
searchRequest = userService.search(currentQuery);
|
||||||
|
handleRequest(searchRequest);
|
||||||
|
prevQuery = currentQuery;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onError(final Throwable t) {
|
||||||
|
Log.e(TAG, "onError: ", t);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
searchDebouncer = new Debouncer<>(searchCallback, 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
public LiveData<Resource<List<DirectUser>>> getUsers() {
|
||||||
|
return users;
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
public void onResponse(@NonNull final Call<UserSearchResponse> call, @NonNull final Response<UserSearchResponse> response) {
|
||||||
|
if (!response.isSuccessful()) {
|
||||||
|
handleErrorResponse(response);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final UserSearchResponse userSearchResponse = response.body();
|
||||||
|
if (userSearchResponse == null) return;
|
||||||
|
handleResponse(userSearchResponse);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(@NonNull final Call<UserSearchResponse> call, @NonNull final Throwable t) {
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleResponse(final UserSearchResponse userSearchResponse) {
|
||||||
|
users.postValue(Resource.success(userSearchResponse
|
||||||
|
.getUsers()
|
||||||
|
.stream()
|
||||||
|
.filter(directUser -> Arrays.binarySearch(hideUserIds, directUser.getPk()) < 0)
|
||||||
|
.collect(Collectors.toList())
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleErrorResponse(final Response<UserSearchResponse> response) {
|
||||||
|
final ResponseBody errorBody = response.errorBody();
|
||||||
|
if (errorBody == null) {
|
||||||
|
users.postValue(Resource.error("Request failed!", Collections.emptyList()));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String errorString;
|
||||||
|
try {
|
||||||
|
errorString = errorBody.string();
|
||||||
|
Log.e(TAG, "handleErrorResponse: " + errorString);
|
||||||
|
} catch (IOException e) {
|
||||||
|
Log.e(TAG, "handleErrorResponse: ", e);
|
||||||
|
errorString = e.getMessage();
|
||||||
|
}
|
||||||
|
users.postValue(Resource.error(errorString, Collections.emptyList()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSelectedUser(final DirectUser user, final boolean selected) {
|
||||||
|
if (selected) {
|
||||||
|
selectedUsers.add(user);
|
||||||
|
} else {
|
||||||
|
selectedUsers.remove(user);
|
||||||
|
}
|
||||||
|
showAction.postValue(!selectedUsers.isEmpty());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Set<DirectUser> getSelectedUsers() {
|
||||||
|
return selectedUsers;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void clearResults() {
|
||||||
|
users.postValue(Resource.success(Collections.emptyList()));
|
||||||
|
prevQuery = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
public void cancelSearch() {
|
||||||
|
searchDebouncer.cancel(DEBOUNCE_KEY);
|
||||||
|
if (searchRequest != null) {
|
||||||
|
searchRequest.cancel();
|
||||||
|
searchRequest = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public LiveData<Boolean> showAction() {
|
||||||
|
return showAction;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setHideUserIds(final long[] hideUserIds) {
|
||||||
|
this.hideUserIds = hideUserIds;
|
||||||
|
}
|
||||||
|
}
|
@ -7,6 +7,7 @@ import com.google.common.collect.ImmutableMap;
|
|||||||
import org.json.JSONArray;
|
import org.json.JSONArray;
|
||||||
|
|
||||||
import java.io.UnsupportedEncodingException;
|
import java.io.UnsupportedEncodingException;
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@ -25,6 +26,7 @@ import awais.instagrabber.repositories.requests.directmessages.VoiceBroadcastOpt
|
|||||||
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.DirectThreadBroadcastResponse;
|
import awais.instagrabber.repositories.responses.directmessages.DirectThreadBroadcastResponse;
|
||||||
|
import awais.instagrabber.repositories.responses.directmessages.DirectThreadDetailsChangeResponse;
|
||||||
import awais.instagrabber.repositories.responses.directmessages.DirectThreadFeedResponse;
|
import awais.instagrabber.repositories.responses.directmessages.DirectThreadFeedResponse;
|
||||||
import awais.instagrabber.utils.TextUtils;
|
import awais.instagrabber.utils.TextUtils;
|
||||||
import awais.instagrabber.utils.Utils;
|
import awais.instagrabber.utils.Utils;
|
||||||
@ -175,4 +177,54 @@ public class DirectMessagesService extends BaseService {
|
|||||||
final Map<String, String> signedForm = Utils.sign(form);
|
final Map<String, String> signedForm = Utils.sign(form);
|
||||||
return repository.broadcast(broadcastOptions.getItemType().getValue(), signedForm);
|
return repository.broadcast(broadcastOptions.getItemType().getValue(), signedForm);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Call<DirectThreadDetailsChangeResponse> addUsers(final String threadId,
|
||||||
|
final Collection<Long> userIds) {
|
||||||
|
final ImmutableMap<String, String> form = ImmutableMap.of(
|
||||||
|
"_csrftoken", csrfToken,
|
||||||
|
"_uuid", deviceUuid,
|
||||||
|
"user_ids", new JSONArray(userIds).toString()
|
||||||
|
);
|
||||||
|
return repository.addUsers(threadId, form);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Call<String> removeUsers(final String threadId,
|
||||||
|
final Collection<Long> userIds) {
|
||||||
|
final ImmutableMap<String, String> form = ImmutableMap.of(
|
||||||
|
"_csrftoken", csrfToken,
|
||||||
|
"_uuid", deviceUuid,
|
||||||
|
"user_ids", new JSONArray(userIds).toString()
|
||||||
|
);
|
||||||
|
return repository.removeUsers(threadId, form);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Call<DirectThreadDetailsChangeResponse> updateTitle(final String threadId,
|
||||||
|
final String title) {
|
||||||
|
final ImmutableMap<String, String> form = ImmutableMap.of(
|
||||||
|
"_csrftoken", csrfToken,
|
||||||
|
"_uuid", deviceUuid,
|
||||||
|
"title", title
|
||||||
|
);
|
||||||
|
return repository.updateTitle(threadId, form);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Call<String> addAdmins(final String threadId,
|
||||||
|
final Collection<Long> userIds) {
|
||||||
|
final ImmutableMap<String, String> form = ImmutableMap.of(
|
||||||
|
"_csrftoken", csrfToken,
|
||||||
|
"_uuid", deviceUuid,
|
||||||
|
"user_ids", new JSONArray(userIds).toString()
|
||||||
|
);
|
||||||
|
return repository.addAdmins(threadId, form);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Call<String> removeAdmins(final String threadId,
|
||||||
|
final Collection<Long> userIds) {
|
||||||
|
final ImmutableMap<String, String> form = ImmutableMap.of(
|
||||||
|
"_csrftoken", csrfToken,
|
||||||
|
"_uuid", deviceUuid,
|
||||||
|
"user_ids", new JSONArray(userIds).toString()
|
||||||
|
);
|
||||||
|
return repository.removeAdmins(threadId, form);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,8 +17,6 @@ import java.util.List;
|
|||||||
import awais.instagrabber.models.FeedModel;
|
import awais.instagrabber.models.FeedModel;
|
||||||
import awais.instagrabber.repositories.ProfileRepository;
|
import awais.instagrabber.repositories.ProfileRepository;
|
||||||
import awais.instagrabber.repositories.responses.PostsFetchResponse;
|
import awais.instagrabber.repositories.responses.PostsFetchResponse;
|
||||||
import awais.instagrabber.repositories.responses.UserInfo;
|
|
||||||
import awais.instagrabber.utils.Constants;
|
|
||||||
import awais.instagrabber.utils.ResponseBodyUtils;
|
import awais.instagrabber.utils.ResponseBodyUtils;
|
||||||
import awais.instagrabber.utils.TextUtils;
|
import awais.instagrabber.utils.TextUtils;
|
||||||
import retrofit2.Call;
|
import retrofit2.Call;
|
||||||
@ -47,39 +45,6 @@ public class ProfileService extends BaseService {
|
|||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void getUserInfo(final String uid, final ServiceCallback<UserInfo> callback) {
|
|
||||||
final Call<String> request = repository.getUserInfo(uid);
|
|
||||||
request.enqueue(new Callback<String>() {
|
|
||||||
@Override
|
|
||||||
public void onResponse(@NonNull final Call<String> call, @NonNull final Response<String> response) {
|
|
||||||
final String body = response.body();
|
|
||||||
if (body == null) return;
|
|
||||||
try {
|
|
||||||
final JSONObject jsonObject = new JSONObject(body);
|
|
||||||
final JSONObject user = jsonObject.optJSONObject(Constants.EXTRAS_USER);
|
|
||||||
if (user == null) return;
|
|
||||||
// Log.d(TAG, "user: " + user.toString());
|
|
||||||
final UserInfo userInfo = new UserInfo(
|
|
||||||
uid,
|
|
||||||
user.getString(Constants.EXTRAS_USERNAME),
|
|
||||||
user.optString("full_name"),
|
|
||||||
user.optString("profile_pic_url"),
|
|
||||||
user.has("hd_profile_pic_url_info")
|
|
||||||
? user.getJSONObject("hd_profile_pic_url_info").optString("url") : null
|
|
||||||
);
|
|
||||||
callback.onSuccess(userInfo);
|
|
||||||
} catch (JSONException e) {
|
|
||||||
Log.e(TAG, "Error parsing json", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onFailure(@NonNull final Call<String> call, @NonNull final Throwable t) {
|
|
||||||
callback.onFailure(t);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public void fetchPosts(final String userId,
|
public void fetchPosts(final String userId,
|
||||||
final String maxId,
|
final String maxId,
|
||||||
final ServiceCallback<PostsFetchResponse> callback) {
|
final ServiceCallback<PostsFetchResponse> callback) {
|
||||||
|
@ -160,8 +160,8 @@ public class StoriesService extends BaseService {
|
|||||||
highlightNode.getString("title"),
|
highlightNode.getString("title"),
|
||||||
highlightNode.getString(Constants.EXTRAS_ID),
|
highlightNode.getString(Constants.EXTRAS_ID),
|
||||||
highlightNode.getJSONObject("cover_media")
|
highlightNode.getJSONObject("cover_media")
|
||||||
.getJSONObject("cropped_image_version")
|
.getJSONObject("cropped_image_version")
|
||||||
.getString("url"),
|
.getString("url"),
|
||||||
highlightNode.getLong("latest_reel_media"),
|
highlightNode.getLong("latest_reel_media"),
|
||||||
highlightNode.getInt("media_count")
|
highlightNode.getInt("media_count")
|
||||||
));
|
));
|
||||||
@ -188,7 +188,7 @@ public class StoriesService extends BaseService {
|
|||||||
form.put("include_suggested_highlights", "false");
|
form.put("include_suggested_highlights", "false");
|
||||||
form.put("is_in_archive_home", "true");
|
form.put("is_in_archive_home", "true");
|
||||||
form.put("include_cover", "1");
|
form.put("include_cover", "1");
|
||||||
form.put("timezone_offset", String.valueOf(TimeZone.getDefault().getRawOffset() / 1000));
|
form.put("timezone_offset", String.valueOf((float) TimeZone.getDefault().getRawOffset() / 1000));
|
||||||
if (!TextUtils.isEmpty(maxId)) {
|
if (!TextUtils.isEmpty(maxId)) {
|
||||||
form.put("max_id", maxId); // NOT TESTED
|
form.put("max_id", maxId); // NOT TESTED
|
||||||
}
|
}
|
||||||
@ -338,40 +338,40 @@ public class StoriesService extends BaseService {
|
|||||||
|
|
||||||
// RespondAction.java
|
// RespondAction.java
|
||||||
public void respondToQuestion(final String storyId,
|
public void respondToQuestion(final String storyId,
|
||||||
final String stickerId,
|
final String stickerId,
|
||||||
final String answer,
|
final String answer,
|
||||||
final String userId,
|
final String userId,
|
||||||
final String csrfToken,
|
final String csrfToken,
|
||||||
final ServiceCallback<StoryStickerResponse> callback) {
|
final ServiceCallback<StoryStickerResponse> callback) {
|
||||||
respondToSticker(storyId, stickerId, "story_question_response", "response", answer, userId, csrfToken, callback);
|
respondToSticker(storyId, stickerId, "story_question_response", "response", answer, userId, csrfToken, callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
// QuizAction.java
|
// QuizAction.java
|
||||||
public void respondToQuiz(final String storyId,
|
public void respondToQuiz(final String storyId,
|
||||||
final String stickerId,
|
final String stickerId,
|
||||||
final int answer,
|
final int answer,
|
||||||
final String userId,
|
final String userId,
|
||||||
final String csrfToken,
|
final String csrfToken,
|
||||||
final ServiceCallback<StoryStickerResponse> callback) {
|
final ServiceCallback<StoryStickerResponse> callback) {
|
||||||
respondToSticker(storyId, stickerId, "story_quiz_answer", "answer", String.valueOf(answer), userId, csrfToken, callback);
|
respondToSticker(storyId, stickerId, "story_quiz_answer", "answer", String.valueOf(answer), userId, csrfToken, callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
// VoteAction.java
|
// VoteAction.java
|
||||||
public void respondToPoll(final String storyId,
|
public void respondToPoll(final String storyId,
|
||||||
final String stickerId,
|
final String stickerId,
|
||||||
final int answer,
|
final int answer,
|
||||||
final String userId,
|
final String userId,
|
||||||
final String csrfToken,
|
final String csrfToken,
|
||||||
final ServiceCallback<StoryStickerResponse> callback) {
|
final ServiceCallback<StoryStickerResponse> callback) {
|
||||||
respondToSticker(storyId, stickerId, "story_poll_vote", "vote", String.valueOf(answer), userId, csrfToken, callback);
|
respondToSticker(storyId, stickerId, "story_poll_vote", "vote", String.valueOf(answer), userId, csrfToken, callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void respondToSlider(final String storyId,
|
public void respondToSlider(final String storyId,
|
||||||
final String stickerId,
|
final String stickerId,
|
||||||
final double answer,
|
final double answer,
|
||||||
final String userId,
|
final String userId,
|
||||||
final String csrfToken,
|
final String csrfToken,
|
||||||
final ServiceCallback<StoryStickerResponse> callback) {
|
final ServiceCallback<StoryStickerResponse> callback) {
|
||||||
respondToSticker(storyId, stickerId, "story_slider_vote", "vote", String.valueOf(answer), userId, csrfToken, callback);
|
respondToSticker(storyId, stickerId, "story_slider_vote", "vote", String.valueOf(answer), userId, csrfToken, callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,79 @@
|
|||||||
|
package awais.instagrabber.webservices;
|
||||||
|
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import org.json.JSONException;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
|
import java.util.TimeZone;
|
||||||
|
|
||||||
|
import awais.instagrabber.repositories.UserRepository;
|
||||||
|
import awais.instagrabber.repositories.responses.UserInfo;
|
||||||
|
import awais.instagrabber.repositories.responses.UserSearchResponse;
|
||||||
|
import awais.instagrabber.utils.Constants;
|
||||||
|
import retrofit2.Call;
|
||||||
|
import retrofit2.Callback;
|
||||||
|
import retrofit2.Response;
|
||||||
|
import retrofit2.Retrofit;
|
||||||
|
|
||||||
|
public class UserService extends BaseService {
|
||||||
|
private static final String TAG = UserService.class.getSimpleName();
|
||||||
|
|
||||||
|
private final UserRepository repository;
|
||||||
|
|
||||||
|
private static UserService instance;
|
||||||
|
|
||||||
|
private UserService() {
|
||||||
|
final Retrofit retrofit = getRetrofitBuilder()
|
||||||
|
.baseUrl("https://i.instagram.com")
|
||||||
|
.build();
|
||||||
|
repository = retrofit.create(UserRepository.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static UserService getInstance() {
|
||||||
|
if (instance == null) {
|
||||||
|
instance = new UserService();
|
||||||
|
}
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void getUserInfo(final String uid, final ServiceCallback<UserInfo> callback) {
|
||||||
|
final Call<String> request = repository.getUserInfo(uid);
|
||||||
|
request.enqueue(new Callback<String>() {
|
||||||
|
@Override
|
||||||
|
public void onResponse(@NonNull final Call<String> call, @NonNull final Response<String> response) {
|
||||||
|
final String body = response.body();
|
||||||
|
if (body == null) return;
|
||||||
|
try {
|
||||||
|
final JSONObject jsonObject = new JSONObject(body);
|
||||||
|
final JSONObject user = jsonObject.optJSONObject(Constants.EXTRAS_USER);
|
||||||
|
if (user == null) return;
|
||||||
|
// Log.d(TAG, "user: " + user.toString());
|
||||||
|
final UserInfo userInfo = new UserInfo(
|
||||||
|
uid,
|
||||||
|
user.getString(Constants.EXTRAS_USERNAME),
|
||||||
|
user.optString("full_name"),
|
||||||
|
user.optString("profile_pic_url"),
|
||||||
|
user.has("hd_profile_pic_url_info")
|
||||||
|
? user.getJSONObject("hd_profile_pic_url_info").optString("url") : null
|
||||||
|
);
|
||||||
|
callback.onSuccess(userInfo);
|
||||||
|
} catch (JSONException e) {
|
||||||
|
Log.e(TAG, "Error parsing json", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(@NonNull final Call<String> call, @NonNull final Throwable t) {
|
||||||
|
callback.onFailure(t);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public Call<UserSearchResponse> search(final String query) {
|
||||||
|
final float timezoneOffset = (float) TimeZone.getDefault().getRawOffset() / 1000;
|
||||||
|
return repository.search(timezoneOffset, query);
|
||||||
|
}
|
||||||
|
}
|
5
app/src/main/res/color/ic_circle_check_tint.xml
Normal file
5
app/src/main/res/color/ic_circle_check_tint.xml
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item android:color="@color/blue_400" android:state_selected="true" />
|
||||||
|
<item android:color="?colorControlNormal" />
|
||||||
|
</selector>
|
21
app/src/main/res/drawable/bg_user_search_input.xml
Normal file
21
app/src/main/res/drawable/bg_user_search_input.xml
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item
|
||||||
|
android:bottom="1dp"
|
||||||
|
android:left="-2dp"
|
||||||
|
android:right="-2dp"
|
||||||
|
android:top="-2dp">
|
||||||
|
<shape android:shape="rectangle">
|
||||||
|
<stroke
|
||||||
|
android:width="1dp"
|
||||||
|
android:color="?colorControlNormal" />
|
||||||
|
|
||||||
|
<solid android:color="#00FFFFFF" />
|
||||||
|
|
||||||
|
<!--<padding android:left="0dp"-->
|
||||||
|
<!-- android:right="0dp"-->
|
||||||
|
<!-- android:top="0dp"-->
|
||||||
|
<!-- android:bottom="0dp" />-->
|
||||||
|
</shape>
|
||||||
|
</item>
|
||||||
|
</layer-list>
|
5
app/src/main/res/drawable/ic_circle_check.xml
Normal file
5
app/src/main/res/drawable/ic_circle_check.xml
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item android:drawable="@drawable/ic_round_check_circle_24" android:state_selected="true" />
|
||||||
|
<item android:drawable="@drawable/ic_radio_button_unchecked_24" />
|
||||||
|
</selector>
|
10
app/src/main/res/drawable/ic_radio_button_unchecked_24.xml
Normal file
10
app/src/main/res/drawable/ic_radio_button_unchecked_24.xml
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24"
|
||||||
|
android:tint="?attr/colorControlNormal">
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/white"
|
||||||
|
android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,20c-4.42,0 -8,-3.58 -8,-8s3.58,-8 8,-8 8,3.58 8,8 -3.58,8 -8,8z"/>
|
||||||
|
</vector>
|
10
app/src/main/res/drawable/ic_round_check_circle_24.xml
Normal file
10
app/src/main/res/drawable/ic_round_check_circle_24.xml
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24"
|
||||||
|
android:tint="?attr/colorControlNormal">
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/white"
|
||||||
|
android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM9.29,16.29L5.7,12.7c-0.39,-0.39 -0.39,-1.02 0,-1.41 0.39,-0.39 1.02,-0.39 1.41,0L10,14.17l6.88,-6.88c0.39,-0.39 1.02,-0.39 1.41,0 0.39,0.39 0.39,1.02 0,1.41l-7.59,7.59c-0.38,0.39 -1.02,0.39 -1.41,0z"/>
|
||||||
|
</vector>
|
@ -1,94 +1,154 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<androidx.coordinatorlayout.widget.CoordinatorLayout 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:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:orientation="vertical">
|
android:focusableInTouchMode="true">
|
||||||
|
|
||||||
<LinearLayout
|
<com.google.android.material.appbar.AppBarLayout
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content">
|
|
||||||
|
|
||||||
<EditText
|
|
||||||
android:id="@+id/titleText"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_weight="1"
|
|
||||||
android:gravity="bottom"
|
|
||||||
android:importantForAutofill="no"
|
|
||||||
android:inputType="textMultiLine"
|
|
||||||
android:maxLength="2200"
|
|
||||||
android:maxLines="10"
|
|
||||||
android:paddingStart="8dp"
|
|
||||||
android:paddingLeft="8dp"
|
|
||||||
android:paddingEnd="4dp"
|
|
||||||
android:paddingRight="4dp"
|
|
||||||
android:scrollHorizontally="false" />
|
|
||||||
|
|
||||||
<androidx.appcompat.widget.AppCompatImageView
|
|
||||||
android:id="@+id/titleSend"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_gravity="center"
|
|
||||||
android:background="?selectableItemBackgroundBorderless"
|
|
||||||
android:clickable="true"
|
|
||||||
android:focusable="true"
|
|
||||||
android:paddingStart="4dp"
|
|
||||||
android:paddingLeft="4dp"
|
|
||||||
android:paddingTop="4dp"
|
|
||||||
android:paddingEnd="8dp"
|
|
||||||
android:paddingRight="8dp"
|
|
||||||
android:paddingBottom="4dp"
|
|
||||||
android:visibility="gone"
|
|
||||||
app:srcCompat="@drawable/ic_submit" />
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<androidx.appcompat.widget.AppCompatButton
|
|
||||||
android:id="@+id/btnLeave"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginLeft="8dp"
|
android:background="@null"
|
||||||
android:layout_marginStart="6dp"
|
android:elevation="0dp"
|
||||||
android:layout_marginRight="8dp"
|
app:elevation="0dp">
|
||||||
android:text="@string/dms_action_leave"
|
|
||||||
android:textColor="@color/btn_red_text_color"
|
|
||||||
android:textSize="18sp"
|
|
||||||
app:backgroundTint="@color/btn_red_background" />
|
|
||||||
|
|
||||||
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
<com.google.android.material.appbar.CollapsingToolbarLayout
|
||||||
android:id="@+id/swipeRefreshLayout"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="0dp"
|
|
||||||
android:layout_weight="1">
|
|
||||||
|
|
||||||
<androidx.core.widget.NestedScrollView
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent">
|
android:layout_height="wrap_content"
|
||||||
<LinearLayout
|
app:layout_scrollFlags="scroll">
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
android:id="@+id/settings_parent"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="wrap_content">
|
||||||
android:orientation="vertical">
|
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
android:id="@+id/userList"
|
android:id="@+id/title_edit_input_layout"
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:nestedScrollingEnabled="false"/>
|
|
||||||
<androidx.appcompat.widget.AppCompatTextView
|
|
||||||
android:id="@+id/leftTitle"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
android:animateLayoutChanges="true"
|
||||||
|
android:hint="@string/title"
|
||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
|
app:boxBackgroundColor="@android:color/transparent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:suffixText="@string/save"
|
||||||
|
app:suffixTextColor="@color/blue_600"
|
||||||
|
tools:visibility="visible">
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
|
android:id="@+id/title_edit"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:autofillHints="no"
|
||||||
|
android:inputType="text"
|
||||||
|
android:maxLength="2200"
|
||||||
|
android:scrollHorizontally="false"
|
||||||
|
tools:text="test" />
|
||||||
|
|
||||||
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
|
<com.google.android.material.button.MaterialButton
|
||||||
|
android:id="@+id/mute_messages_label"
|
||||||
|
style="@style/Widget.MaterialComponents.Button.TextButton"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="0dp"
|
||||||
android:gravity="center_vertical"
|
android:gravity="center_vertical"
|
||||||
android:padding="5dp"
|
android:paddingStart="16dp"
|
||||||
android:text="@string/dms_left_users"
|
android:paddingEnd="16dp"
|
||||||
|
android:text="@string/mute_messages"
|
||||||
|
android:textAppearance="@style/TextAppearance.MaterialComponents.Body1"
|
||||||
android:textColor="?android:textColorPrimary"
|
android:textColor="?android:textColorPrimary"
|
||||||
android:textSize="24sp"/>
|
app:layout_constraintBottom_toBottomOf="@id/mute_messages"
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
android:id="@+id/leftUserList"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="@id/mute_messages" />
|
||||||
|
|
||||||
|
<com.google.android.material.switchmaterial.SwitchMaterial
|
||||||
|
android:id="@+id/mute_messages"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:paddingStart="0dp"
|
||||||
|
android:paddingEnd="8dp"
|
||||||
|
app:layout_constraintBottom_toTopOf="@id/mute_mentions"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/title_edit_input_layout" />
|
||||||
|
|
||||||
|
<com.google.android.material.button.MaterialButton
|
||||||
|
android:id="@+id/mute_mentions_label"
|
||||||
|
style="@style/Widget.MaterialComponents.Button.TextButton"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:paddingStart="16dp"
|
||||||
|
android:paddingEnd="16dp"
|
||||||
|
android:text="@string/mute_mentions"
|
||||||
|
android:textAppearance="@style/TextAppearance.MaterialComponents.Body1"
|
||||||
|
android:textColor="?android:textColorPrimary"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@id/mute_mentions"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="@id/mute_mentions" />
|
||||||
|
|
||||||
|
<com.google.android.material.switchmaterial.SwitchMaterial
|
||||||
|
android:id="@+id/mute_mentions"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:paddingStart="0dp"
|
||||||
|
android:paddingEnd="8dp"
|
||||||
|
app:layout_constraintBottom_toTopOf="@id/leave"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/mute_messages" />
|
||||||
|
|
||||||
|
<com.google.android.material.button.MaterialButton
|
||||||
|
android:id="@+id/leave"
|
||||||
|
style="@style/Widget.MaterialComponents.Button.TextButton"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="wrap_content"
|
||||||
android:nestedScrollingEnabled="false"/>
|
android:gravity="center_vertical"
|
||||||
</LinearLayout>
|
android:paddingStart="16dp"
|
||||||
</androidx.core.widget.NestedScrollView>
|
android:paddingTop="8dp"
|
||||||
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
|
android:paddingEnd="16dp"
|
||||||
</LinearLayout>
|
android:paddingBottom="8dp"
|
||||||
|
android:text="@string/dms_action_leave"
|
||||||
|
android:textAppearance="@style/TextAppearance.MaterialComponents.Body1"
|
||||||
|
android:textColor="@color/red_600"
|
||||||
|
app:layout_constraintBottom_toTopOf="@id/add_members"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/mute_mentions" />
|
||||||
|
|
||||||
|
<com.google.android.material.button.MaterialButton
|
||||||
|
android:id="@+id/add_members"
|
||||||
|
style="@style/Widget.MaterialComponents.Button.TextButton"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:paddingStart="16dp"
|
||||||
|
android:paddingTop="8dp"
|
||||||
|
android:paddingEnd="16dp"
|
||||||
|
android:paddingBottom="8dp"
|
||||||
|
android:text="@string/add_members"
|
||||||
|
android:textAppearance="@style/TextAppearance.MaterialComponents.Body1"
|
||||||
|
android:textColor="?android:textColorPrimary"
|
||||||
|
app:icon="@drawable/ic_add"
|
||||||
|
app:iconTint="?android:textColorPrimary"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/leave" />
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.Group
|
||||||
|
android:id="@+id/group_settings"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
app:constraint_referenced_ids="title_edit_input_layout, mute_mentions_label, mute_mentions, leave, add_members" />
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
|
</com.google.android.material.appbar.CollapsingToolbarLayout>
|
||||||
|
</com.google.android.material.appbar.AppBarLayout>
|
||||||
|
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/users"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
|
||||||
|
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
51
app/src/main/res/layout/fragment_user_search.xml
Normal file
51
app/src/main/res/layout/fragment_user_search.xml
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:focusableInTouchMode="true">
|
||||||
|
|
||||||
|
<com.google.android.material.chip.ChipGroup
|
||||||
|
android:id="@+id/group"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="@drawable/bg_user_search_input"
|
||||||
|
android:padding="10dp"
|
||||||
|
app:layout_constraintBottom_toTopOf="@id/results"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent">
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatEditText
|
||||||
|
android:id="@+id/search"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="48dp"
|
||||||
|
android:background="@android:color/transparent"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:hint="@string/search"
|
||||||
|
android:importantForAutofill="no"
|
||||||
|
android:inputType="textNoSuggestions"
|
||||||
|
android:maxLines="1"
|
||||||
|
tools:text="test" />
|
||||||
|
</com.google.android.material.chip.ChipGroup>
|
||||||
|
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/results"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
app:layout_constraintBottom_toTopOf="@id/done"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/group" />
|
||||||
|
|
||||||
|
<com.google.android.material.button.MaterialButton
|
||||||
|
android:id="@+id/done"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="8dp"
|
||||||
|
android:layout_marginEnd="8dp"
|
||||||
|
android:text="@string/done"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/results"
|
||||||
|
tools:visibility="visible" />
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
@ -17,7 +17,6 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="60dp"
|
android:layout_height="60dp"
|
||||||
android:layout_marginStart="66dp"
|
android:layout_marginStart="66dp"
|
||||||
android:layout_marginLeft="66dp"
|
|
||||||
android:layout_marginEnd="26dp"
|
android:layout_marginEnd="26dp"
|
||||||
android:orientation="vertical">
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
95
app/src/main/res/layout/layout_dm_user_item.xml
Normal file
95
app/src/main/res/layout/layout_dm_user_item.xml
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="72dp"
|
||||||
|
android:background="?android:selectableItemBackground"
|
||||||
|
android:clickable="true"
|
||||||
|
android:focusable="true"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:paddingStart="16dp"
|
||||||
|
android:paddingEnd="16dp">
|
||||||
|
|
||||||
|
<com.facebook.drawee.view.SimpleDraweeView
|
||||||
|
android:id="@+id/profile_pic"
|
||||||
|
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_constraintEnd_toStartOf="@id/full_name"
|
||||||
|
app:layout_constraintHorizontal_chainStyle="spread_inside"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:roundAsCircle="true"
|
||||||
|
tools:background="@mipmap/ic_launcher" />
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatTextView
|
||||||
|
android:id="@+id/full_name"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="16dp"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:gravity="bottom"
|
||||||
|
android:singleLine="true"
|
||||||
|
android:textAppearance="@style/TextAppearance.MaterialComponents.Subtitle1"
|
||||||
|
app:layout_constraintBottom_toTopOf="@id/username"
|
||||||
|
app:layout_constraintEnd_toStartOf="@id/info"
|
||||||
|
app:layout_constraintStart_toEndOf="@id/profile_pic"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:layout_constraintVertical_chainStyle="packed"
|
||||||
|
tools:text="Long name......................." />
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatTextView
|
||||||
|
android:id="@+id/info"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:gravity="bottom"
|
||||||
|
android:paddingStart="4dp"
|
||||||
|
android:paddingEnd="0dp"
|
||||||
|
android:singleLine="true"
|
||||||
|
android:textAppearance="@style/TextAppearance.MaterialComponents.Caption"
|
||||||
|
android:textColor="@color/blue_400"
|
||||||
|
app:layout_constraintBaseline_toBaselineOf="@id/thread_title"
|
||||||
|
app:layout_constraintBottom_toTopOf="@id/username"
|
||||||
|
app:layout_constraintEnd_toStartOf="@id/select"
|
||||||
|
app:layout_constraintStart_toEndOf="@id/full_name"
|
||||||
|
app:layout_constraintTop_toTopOf="@id/full_name"
|
||||||
|
tools:text="Admin" />
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatTextView
|
||||||
|
android:id="@+id/username"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:textAppearance="@style/TextAppearance.AppCompat.Caption"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toStartOf="@id/select"
|
||||||
|
app:layout_constraintStart_toStartOf="@id/full_name"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/full_name"
|
||||||
|
tools:text="username" />
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatImageView
|
||||||
|
android:id="@+id/select"
|
||||||
|
android:layout_width="30dp"
|
||||||
|
android:layout_height="30dp"
|
||||||
|
android:layout_marginStart="4dp"
|
||||||
|
android:duplicateParentState="true"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@id/profile_pic"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toEndOf="@id/info"
|
||||||
|
app:layout_constraintTop_toTopOf="@id/profile_pic"
|
||||||
|
app:srcCompat="@drawable/ic_circle_check"
|
||||||
|
app:tint="@color/ic_circle_check_tint"
|
||||||
|
tools:visibility="visible" />
|
||||||
|
|
||||||
|
<include
|
||||||
|
layout="@layout/item_pref_divider"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="1dp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="@id/thread_title" />
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
@ -73,14 +73,38 @@
|
|||||||
<action
|
<action
|
||||||
android:id="@+id/action_global_likesViewerFragment"
|
android:id="@+id/action_global_likesViewerFragment"
|
||||||
app:destination="@id/likes_nav_graph">
|
app:destination="@id/likes_nav_graph">
|
||||||
<argument
|
<argument
|
||||||
android:name="postId"
|
android:name="postId"
|
||||||
app:argType="string"
|
app:argType="string"
|
||||||
app:nullable="false" />
|
app:nullable="false" />
|
||||||
<argument
|
<argument
|
||||||
android:name="isComment"
|
android:name="isComment"
|
||||||
app:argType="boolean"
|
app:argType="boolean"
|
||||||
app:nullable="false" />
|
app:nullable="false" />
|
||||||
|
</action>
|
||||||
|
|
||||||
|
<include app:graph="@navigation/user_search_nav_graph" />
|
||||||
|
|
||||||
|
<action
|
||||||
|
android:id="@+id/action_global_user_search"
|
||||||
|
app:destination="@id/user_search_nav_graph">
|
||||||
|
<argument
|
||||||
|
android:name="multiple"
|
||||||
|
app:argType="boolean" />
|
||||||
|
|
||||||
|
<argument
|
||||||
|
android:name="title"
|
||||||
|
app:argType="string"
|
||||||
|
app:nullable="true" />
|
||||||
|
|
||||||
|
<argument
|
||||||
|
android:name="action_label"
|
||||||
|
app:argType="string"
|
||||||
|
app:nullable="true" />
|
||||||
|
|
||||||
|
<argument
|
||||||
|
android:name="hideUserIds"
|
||||||
|
app:argType="long[]" />
|
||||||
</action>
|
</action>
|
||||||
|
|
||||||
<fragment
|
<fragment
|
||||||
@ -112,20 +136,23 @@
|
|||||||
<fragment
|
<fragment
|
||||||
android:id="@+id/directMessagesSettingsFragment"
|
android:id="@+id/directMessagesSettingsFragment"
|
||||||
android:name="awais.instagrabber.fragments.directmessages.DirectMessageSettingsFragment"
|
android:name="awais.instagrabber.fragments.directmessages.DirectMessageSettingsFragment"
|
||||||
android:label="@string/action_settings"
|
android:label="@string/details"
|
||||||
tools:layout="@layout/fragment_direct_messages_settings">
|
tools:layout="@layout/fragment_direct_messages_settings">
|
||||||
<argument
|
<argument
|
||||||
android:name="threadId"
|
android:name="threadId"
|
||||||
app:argType="string" />
|
app:argType="string" />
|
||||||
<argument
|
<action
|
||||||
android:name="title"
|
android:id="@+id/action_settings_to_inbox"
|
||||||
app:argType="string" />
|
app:destination="@id/directMessagesInboxFragment"
|
||||||
|
app:popUpTo="@+id/directMessagesInboxFragment"
|
||||||
|
app:popUpToInclusive="true" />
|
||||||
|
|
||||||
</fragment>
|
</fragment>
|
||||||
<fragment
|
<fragment
|
||||||
android:id="@+id/imageEditFragment"
|
android:id="@+id/imageEditFragment"
|
||||||
android:name="awais.instagrabber.fragments.imageedit.ImageEditFragment"
|
android:name="awais.instagrabber.fragments.imageedit.ImageEditFragment"
|
||||||
tools:layout="@layout/fragment_image_edit"
|
android:label="Edit Photo"
|
||||||
android:label="Edit Photo">
|
tools:layout="@layout/fragment_image_edit">
|
||||||
<argument
|
<argument
|
||||||
android:name="uri"
|
android:name="uri"
|
||||||
app:argType="android.net.Uri"
|
app:argType="android.net.Uri"
|
||||||
|
48
app/src/main/res/navigation/user_search_nav_graph.xml
Normal file
48
app/src/main/res/navigation/user_search_nav_graph.xml
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:id="@+id/user_search_nav_graph"
|
||||||
|
app:startDestination="@id/user_search">
|
||||||
|
|
||||||
|
<fragment
|
||||||
|
android:id="@+id/user_search"
|
||||||
|
android:name="awais.instagrabber.fragments.UserSearchFragment"
|
||||||
|
android:label="@string/search">
|
||||||
|
<argument
|
||||||
|
android:name="multiple"
|
||||||
|
app:argType="boolean" />
|
||||||
|
|
||||||
|
<argument
|
||||||
|
android:name="title"
|
||||||
|
app:argType="string"
|
||||||
|
app:nullable="true" />
|
||||||
|
|
||||||
|
<argument
|
||||||
|
android:name="action_label"
|
||||||
|
app:argType="string"
|
||||||
|
app:nullable="true" />
|
||||||
|
<argument
|
||||||
|
android:name="hideUserIds"
|
||||||
|
app:argType="long[]" />
|
||||||
|
|
||||||
|
|
||||||
|
</fragment>
|
||||||
|
|
||||||
|
<!--<action
|
||||||
|
android:id="@+id/action_global_user_search"
|
||||||
|
app:destination="@id/user_search">
|
||||||
|
<argument
|
||||||
|
android:name="multiple"
|
||||||
|
app:argType="boolean" />
|
||||||
|
|
||||||
|
<argument
|
||||||
|
android:name="title"
|
||||||
|
app:argType="string"
|
||||||
|
app:nullable="true" />
|
||||||
|
|
||||||
|
<argument
|
||||||
|
android:name="action_label"
|
||||||
|
app:argType="string"
|
||||||
|
app:nullable="true" />
|
||||||
|
</action>-->
|
||||||
|
</navigation>
|
@ -176,7 +176,7 @@
|
|||||||
<string name="dms_inbox_raven_media_screenshot">Screenshotted</string>
|
<string name="dms_inbox_raven_media_screenshot">Screenshotted</string>
|
||||||
<string name="dms_inbox_raven_media_cant_deliver">Cannot deliver</string>
|
<string name="dms_inbox_raven_media_cant_deliver">Cannot deliver</string>
|
||||||
<string name="dms_action_success">Great success!</string>
|
<string name="dms_action_success">Great success!</string>
|
||||||
<string name="dms_action_leave">Leave</string>
|
<string name="dms_action_leave">Leave chat</string>
|
||||||
<string name="dms_action_leave_question">Leave this chat?</string>
|
<string name="dms_action_leave_question">Leave this chat?</string>
|
||||||
<string name="dms_action_kick">Kick</string>
|
<string name="dms_action_kick">Kick</string>
|
||||||
<string name="dms_left_users">Left users</string>
|
<string name="dms_left_users">Left users</string>
|
||||||
@ -378,4 +378,16 @@
|
|||||||
<item quantity="other">%s stories</item>
|
<item quantity="other">%s stories</item>
|
||||||
</plurals>
|
</plurals>
|
||||||
<string name="download_permission">Storage permission not granted!</string>
|
<string name="download_permission">Storage permission not granted!</string>
|
||||||
|
<string name="details">Details</string>
|
||||||
|
<string name="title">Title</string>
|
||||||
|
<string name="members">Members</string>
|
||||||
|
<string name="admin">Admin</string>
|
||||||
|
<string name="inviter">Inviter</string>
|
||||||
|
<string name="mute_messages">Mute messages</string>
|
||||||
|
<string name="mute_mentions">Mute mentions</string>
|
||||||
|
<string name="add_members">Add members</string>
|
||||||
|
<string name="search">Search</string>
|
||||||
|
<string name="done">Done</string>
|
||||||
|
<string name="dms_action_make_admin">Make Admin</string>
|
||||||
|
<string name="dms_action_remove_admin">Remove as Admin</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
Loading…
Reference in New Issue
Block a user