mirror of
				https://github.com/KokaKiwi/BarInsta
				synced 2025-11-04 05:25:35 +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.IntentUtils;
 | 
			
		||||
import awais.instagrabber.utils.TextUtils;
 | 
			
		||||
import awais.instagrabber.utils.Utils;
 | 
			
		||||
import awais.instagrabber.utils.emoji.EmojiParser;
 | 
			
		||||
 | 
			
		||||
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();
 | 
			
		||||
            setupMenu(backStack.size(), destinationId);
 | 
			
		||||
            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.Bundle;
 | 
			
		||||
import android.os.Environment;
 | 
			
		||||
import android.util.Log;
 | 
			
		||||
import android.view.LayoutInflater;
 | 
			
		||||
import android.view.View;
 | 
			
		||||
import android.view.ViewGroup;
 | 
			
		||||
@ -20,7 +19,6 @@ import androidx.annotation.NonNull;
 | 
			
		||||
import androidx.annotation.Nullable;
 | 
			
		||||
import androidx.core.content.ContextCompat;
 | 
			
		||||
import androidx.fragment.app.DialogFragment;
 | 
			
		||||
import androidx.fragment.app.FragmentActivity;
 | 
			
		||||
 | 
			
		||||
import com.facebook.drawee.backends.pipeline.Fresco;
 | 
			
		||||
import com.facebook.drawee.controller.BaseControllerListener;
 | 
			
		||||
@ -32,17 +30,14 @@ import java.io.File;
 | 
			
		||||
import awais.instagrabber.R;
 | 
			
		||||
import awais.instagrabber.asyncs.ProfilePictureFetcher;
 | 
			
		||||
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.models.StoryModel;
 | 
			
		||||
import awais.instagrabber.repositories.responses.UserInfo;
 | 
			
		||||
import awais.instagrabber.utils.Constants;
 | 
			
		||||
import awais.instagrabber.utils.CookieUtils;
 | 
			
		||||
import awais.instagrabber.utils.DownloadUtils;
 | 
			
		||||
import awais.instagrabber.utils.TextUtils;
 | 
			
		||||
import awais.instagrabber.webservices.ProfileService;
 | 
			
		||||
import awais.instagrabber.webservices.ServiceCallback;
 | 
			
		||||
import awais.instagrabber.webservices.UserService;
 | 
			
		||||
 | 
			
		||||
import static awais.instagrabber.utils.Utils.settingsHelper;
 | 
			
		||||
 | 
			
		||||
@ -128,8 +123,8 @@ public class ProfilePicDialogFragment extends DialogFragment {
 | 
			
		||||
 | 
			
		||||
    private void fetchAvatar() {
 | 
			
		||||
        if (isLoggedIn) {
 | 
			
		||||
            final ProfileService profileService = ProfileService.getInstance();
 | 
			
		||||
            profileService.getUserInfo(id, new ServiceCallback<UserInfo>() {
 | 
			
		||||
            final UserService userService = UserService.getInstance();
 | 
			
		||||
            userService.getUserInfo(id, new ServiceCallback<UserInfo>() {
 | 
			
		||||
                @Override
 | 
			
		||||
                public void onSuccess(final UserInfo result) {
 | 
			
		||||
                    if (result != null) {
 | 
			
		||||
@ -144,8 +139,9 @@ public class ProfilePicDialogFragment extends DialogFragment {
 | 
			
		||||
                    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() {
 | 
			
		||||
 | 
			
		||||
@ -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.lifecycle.Observer;
 | 
			
		||||
import androidx.lifecycle.ViewModelProvider;
 | 
			
		||||
import androidx.lifecycle.ViewModelStoreOwner;
 | 
			
		||||
import androidx.navigation.NavController;
 | 
			
		||||
import androidx.navigation.fragment.NavHostFragment;
 | 
			
		||||
import androidx.recyclerview.widget.LinearLayoutManager;
 | 
			
		||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
 | 
			
		||||
@ -54,7 +56,9 @@ public class DirectMessageInboxFragment extends Fragment implements SwipeRefresh
 | 
			
		||||
        super.onCreate(savedInstanceState);
 | 
			
		||||
        fragmentActivity = (MainActivity) getActivity();
 | 
			
		||||
        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;
 | 
			
		||||
 | 
			
		||||
import android.content.Context;
 | 
			
		||||
import android.content.DialogInterface;
 | 
			
		||||
import android.content.Intent;
 | 
			
		||||
import android.os.AsyncTask;
 | 
			
		||||
import android.os.Bundle;
 | 
			
		||||
import android.text.Editable;
 | 
			
		||||
import android.text.TextWatcher;
 | 
			
		||||
import android.util.Log;
 | 
			
		||||
import android.view.LayoutInflater;
 | 
			
		||||
import android.view.View;
 | 
			
		||||
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.Nullable;
 | 
			
		||||
import androidx.appcompat.app.ActionBar;
 | 
			
		||||
import androidx.appcompat.app.AlertDialog;
 | 
			
		||||
import androidx.appcompat.app.AppCompatActivity;
 | 
			
		||||
import androidx.appcompat.widget.AppCompatButton;
 | 
			
		||||
import androidx.appcompat.widget.AppCompatImageView;
 | 
			
		||||
import androidx.core.util.Pair;
 | 
			
		||||
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.recyclerview.widget.LinearLayoutManager;
 | 
			
		||||
import androidx.recyclerview.widget.RecyclerView;
 | 
			
		||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
 | 
			
		||||
 | 
			
		||||
import java.io.DataOutputStream;
 | 
			
		||||
import java.net.HttpURLConnection;
 | 
			
		||||
import java.net.URL;
 | 
			
		||||
import java.net.URLEncoder;
 | 
			
		||||
import com.google.android.material.snackbar.Snackbar;
 | 
			
		||||
 | 
			
		||||
import java.util.ArrayList;
 | 
			
		||||
import java.util.Collections;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.Optional;
 | 
			
		||||
import java.util.Set;
 | 
			
		||||
 | 
			
		||||
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.models.ProfileModel;
 | 
			
		||||
import awais.instagrabber.utils.Constants;
 | 
			
		||||
import awais.instagrabber.utils.Utils;
 | 
			
		||||
import awais.instagrabber.dialogs.MultiOptionDialogFragment;
 | 
			
		||||
import awais.instagrabber.dialogs.MultiOptionDialogFragment.Option;
 | 
			
		||||
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 {
 | 
			
		||||
    private static final String TAG = "DirectMsgsSettingsFrag";
 | 
			
		||||
public class DirectMessageSettingsFragment extends Fragment {
 | 
			
		||||
    private static final String TAG = DirectMessageSettingsFragment.class.getSimpleName();
 | 
			
		||||
 | 
			
		||||
    private AppCompatActivity fragmentActivity;
 | 
			
		||||
    private RecyclerView userList;
 | 
			
		||||
    private RecyclerView leftUserList;
 | 
			
		||||
    private EditText titleText;
 | 
			
		||||
    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);
 | 
			
		||||
    //         }
 | 
			
		||||
    //     }
 | 
			
		||||
    // };
 | 
			
		||||
    private FragmentDirectMessagesSettingsBinding binding;
 | 
			
		||||
    private DirectSettingsViewModel viewModel;
 | 
			
		||||
    private DirectUsersAdapter usersAdapter;
 | 
			
		||||
    private List<Option<String>> options;
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void onCreate(@Nullable final Bundle savedInstanceState) {
 | 
			
		||||
        super.onCreate(savedInstanceState);
 | 
			
		||||
        fragmentActivity = (AppCompatActivity) requireActivity();
 | 
			
		||||
        basicClickListener = v -> {
 | 
			
		||||
            final Object tag = v.getTag();
 | 
			
		||||
            if (tag instanceof ProfileModel) {
 | 
			
		||||
                ProfileModel model = (ProfileModel) tag;
 | 
			
		||||
                final Bundle bundle = new Bundle();
 | 
			
		||||
                bundle.putString("username", "@" + model.getUsername());
 | 
			
		||||
                NavHostFragment.findNavController(this).navigate(R.id.action_global_profileFragment, bundle);
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        clickListener = v -> {
 | 
			
		||||
            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();
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
        final NavController navController = NavHostFragment.findNavController(this);
 | 
			
		||||
        final ViewModelStoreOwner viewModelStoreOwner = navController.getViewModelStoreOwner(R.id.direct_messages_nav_graph);
 | 
			
		||||
        final DirectInboxViewModel inboxViewModel = new ViewModelProvider(viewModelStoreOwner).get(DirectInboxViewModel.class);
 | 
			
		||||
        final List<DirectThread> threads = inboxViewModel.getThreads().getValue();
 | 
			
		||||
        final Bundle arguments = getArguments();
 | 
			
		||||
        if (arguments == null) {
 | 
			
		||||
            navController.navigateUp();
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        final DirectMessageSettingsFragmentArgs fragmentArgs = DirectMessageSettingsFragmentArgs.fromBundle(arguments);
 | 
			
		||||
        final String threadId = fragmentArgs.getThreadId();
 | 
			
		||||
        final Optional<DirectThread> first = threads != null ? threads.stream()
 | 
			
		||||
                                                                      .filter(thread -> thread.getThreadId().equals(threadId))
 | 
			
		||||
                                                                      .findFirst()
 | 
			
		||||
                                                             : Optional.empty();
 | 
			
		||||
        if (!first.isPresent()) {
 | 
			
		||||
            navController.navigateUp();
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        viewModel = new ViewModelProvider(this).get(DirectSettingsViewModel.class);
 | 
			
		||||
        viewModel.setViewer(inboxViewModel.getViewer());
 | 
			
		||||
        viewModel.setThread(first.get());
 | 
			
		||||
        // basicClickListener = v -> {
 | 
			
		||||
        //     final Object tag = v.getTag();
 | 
			
		||||
        //     if (tag instanceof ProfileModel) {
 | 
			
		||||
        //         ProfileModel model = (ProfileModel) tag;
 | 
			
		||||
        //         final Bundle bundle = new Bundle();
 | 
			
		||||
        //         bundle.putString("username", "@" + model.getUsername());
 | 
			
		||||
        //         NavHostFragment.findNavController(this).navigate(R.id.action_global_profileFragment, bundle);
 | 
			
		||||
        //     }
 | 
			
		||||
        // };
 | 
			
		||||
        //
 | 
			
		||||
        // clickListener = v -> {
 | 
			
		||||
        //     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
 | 
			
		||||
@ -128,155 +119,239 @@ public class DirectMessageSettingsFragment extends Fragment implements SwipeRefr
 | 
			
		||||
    public View onCreateView(@NonNull final LayoutInflater inflater,
 | 
			
		||||
                             final ViewGroup container,
 | 
			
		||||
                             final Bundle savedInstanceState) {
 | 
			
		||||
        final FragmentDirectMessagesSettingsBinding binding = FragmentDirectMessagesSettingsBinding.inflate(inflater, container, false);
 | 
			
		||||
        final LinearLayout root = binding.getRoot();
 | 
			
		||||
        final Context context = getContext();
 | 
			
		||||
        if (context == null) return root;
 | 
			
		||||
        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);
 | 
			
		||||
        binding = FragmentDirectMessagesSettingsBinding.inflate(inflater, container, false);
 | 
			
		||||
        // final String threadId = DirectMessageSettingsFragmentArgs.fromBundle(getArguments()).getThreadId();
 | 
			
		||||
        // threadTitle = DirectMessageSettingsFragmentArgs.fromBundle(getArguments()).getTitle();
 | 
			
		||||
        // binding.swipeRefreshLayout.setEnabled(false);
 | 
			
		||||
 | 
			
		||||
        final ActionBar actionBar = fragmentActivity.getSupportActionBar();
 | 
			
		||||
        if (actionBar != null) {
 | 
			
		||||
            actionBar.setTitle(threadTitle);
 | 
			
		||||
        }
 | 
			
		||||
        // final ActionBar actionBar = fragmentActivity.getSupportActionBar();
 | 
			
		||||
        // if (actionBar != null) {
 | 
			
		||||
        //     actionBar.setTitle(threadTitle);
 | 
			
		||||
        // }
 | 
			
		||||
 | 
			
		||||
        userList = binding.userList;
 | 
			
		||||
        userList.setHasFixedSize(true);
 | 
			
		||||
        userList.setLayoutManager(layoutManager);
 | 
			
		||||
        // titleSend.setOnClickListener(v -> new ChangeSettings(titleText.getText().toString()).execute("update_title"));
 | 
			
		||||
 | 
			
		||||
        leftUserList = binding.leftUserList;
 | 
			
		||||
        leftUserList.setHasFixedSize(true);
 | 
			
		||||
        leftUserList.setLayoutManager(layoutManagerDos);
 | 
			
		||||
        // binding.titleText.addTextChangedListener(new TextWatcherAdapter() {
 | 
			
		||||
        //     @Override
 | 
			
		||||
        //     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;
 | 
			
		||||
 | 
			
		||||
        titleText = binding.titleText;
 | 
			
		||||
        titleText.setText(threadTitle);
 | 
			
		||||
 | 
			
		||||
        titleSend = binding.titleSend;
 | 
			
		||||
        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());
 | 
			
		||||
        // 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();
 | 
			
		||||
        return root;
 | 
			
		||||
        return binding.getRoot();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void onRefresh() {
 | 
			
		||||
        stopCurrentExecutor();
 | 
			
		||||
        // currentlyRunning = new DirectMessageInboxThreadFetcher(threadId, null, null, fetchListener).execute();
 | 
			
		||||
    public void onViewCreated(@NonNull final View view, @Nullable final Bundle savedInstanceState) {
 | 
			
		||||
        init();
 | 
			
		||||
        setupObservers();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void stopCurrentExecutor() {
 | 
			
		||||
        // if (currentlyRunning != null) {
 | 
			
		||||
        //     try {
 | 
			
		||||
        //         currentlyRunning.cancel(true);
 | 
			
		||||
        //     } catch (final Exception e) {
 | 
			
		||||
        //         if (BuildConfig.DEBUG) {
 | 
			
		||||
        //             Log.e(TAG, "", e);
 | 
			
		||||
        //         }
 | 
			
		||||
        //     }
 | 
			
		||||
        // }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    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;
 | 
			
		||||
    private void setupObservers() {
 | 
			
		||||
        viewModel.getUsers().observe(getViewLifecycleOwner(), users -> {
 | 
			
		||||
            if (usersAdapter == null) return;
 | 
			
		||||
            usersAdapter.submitUsers(users.first, users.second);
 | 
			
		||||
        });
 | 
			
		||||
        viewModel.getTitle().observe(getViewLifecycleOwner(), title -> binding.titleEdit.setText(title));
 | 
			
		||||
        viewModel.getAdminUserIds().observe(getViewLifecycleOwner(), adminUserIds -> {
 | 
			
		||||
            if (usersAdapter == null) return;
 | 
			
		||||
            usersAdapter.setAdminUserIds(adminUserIds);
 | 
			
		||||
        });
 | 
			
		||||
        final NavController navController = NavHostFragment.findNavController(this);
 | 
			
		||||
        final NavBackStackEntry backStackEntry = navController.getCurrentBackStackEntry();
 | 
			
		||||
        if (backStackEntry != null) {
 | 
			
		||||
            final MutableLiveData<Object> resultLiveData = backStackEntry.getSavedStateHandle().getLiveData("result");
 | 
			
		||||
            resultLiveData.observe(getViewLifecycleOwner(), result -> {
 | 
			
		||||
                LiveData<Resource<Object>> detailsChangeResourceLiveData = null;
 | 
			
		||||
                if ((result instanceof DirectUser)) {
 | 
			
		||||
                    // Log.d(TAG, "result: " + result);
 | 
			
		||||
                    detailsChangeResourceLiveData = viewModel.addMembers(Collections.singleton((DirectUser) result));
 | 
			
		||||
                } else if ((result instanceof Set)) {
 | 
			
		||||
                    try {
 | 
			
		||||
                        // Log.d(TAG, "result: " + result);
 | 
			
		||||
                        //noinspection unchecked
 | 
			
		||||
                        detailsChangeResourceLiveData = viewModel.addMembers((Set<DirectUser>) result);
 | 
			
		||||
                    } catch (Exception e) {
 | 
			
		||||
                        Log.e(TAG, "search users result: ", e);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                urlConnection.disconnect();
 | 
			
		||||
            } catch (Throwable ex) {
 | 
			
		||||
                Log.e("austin_debug", "unsend: " + ex);
 | 
			
		||||
                if (detailsChangeResourceLiveData != null) {
 | 
			
		||||
                    observeDetailsChange(detailsChangeResourceLiveData);
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    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;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        @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();
 | 
			
		||||
        }
 | 
			
		||||
        });
 | 
			
		||||
        binding.titleEditInputLayout.getSuffixTextView().setOnClickListener(v -> {
 | 
			
		||||
            final Editable text = binding.titleEdit.getText();
 | 
			
		||||
            if (text == null) return;
 | 
			
		||||
            final String newTitle = text.toString().trim();
 | 
			
		||||
            if (newTitle.equals(viewModel.getTitle().getValue())) return;
 | 
			
		||||
            observeDetailsChange(viewModel.updateTitle(newTitle));
 | 
			
		||||
        });
 | 
			
		||||
        binding.addMembers.setOnClickListener(v -> {
 | 
			
		||||
            if (!isAdded()) return;
 | 
			
		||||
            final NavController navController = NavHostFragment.findNavController(this);
 | 
			
		||||
            final NavDestination currentDestination = navController.getCurrentDestination();
 | 
			
		||||
            if (currentDestination == null) return;
 | 
			
		||||
            if (currentDestination.getId() != R.id.directMessagesSettingsFragment) return;
 | 
			
		||||
            final Pair<List<DirectUser>, List<DirectUser>> users = viewModel.getUsers().getValue();
 | 
			
		||||
            final long[] currentUserIds;
 | 
			
		||||
            if (users != null && users.first != null) {
 | 
			
		||||
                final List<DirectUser> currentMembers = users.first;
 | 
			
		||||
                currentUserIds = currentMembers.stream()
 | 
			
		||||
                                               .mapToLong(DirectUser::getPk)
 | 
			
		||||
                                               .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.lifecycle.MutableLiveData;
 | 
			
		||||
import androidx.lifecycle.ViewModelProvider;
 | 
			
		||||
import androidx.lifecycle.ViewModelStoreOwner;
 | 
			
		||||
import androidx.navigation.NavBackStackEntry;
 | 
			
		||||
import androidx.navigation.NavController;
 | 
			
		||||
import androidx.navigation.NavDirections;
 | 
			
		||||
@ -249,8 +250,8 @@ public class DirectMessageThreadFragment extends Fragment {
 | 
			
		||||
    public boolean onOptionsItemSelected(@NonNull final MenuItem item) {
 | 
			
		||||
        final int itemId = item.getItemId();
 | 
			
		||||
        if (itemId == R.id.info) {
 | 
			
		||||
            // final NavDirections action = DirectMessageThreadFragmentDirections.actionDMThreadFragmentToDMSettingsFragment(threadId, threadTitle);
 | 
			
		||||
            // NavHostFragment.findNavController(this).navigate(action);
 | 
			
		||||
            final NavDirections action = DirectMessageThreadFragmentDirections.actionDMThreadFragmentToDMSettingsFragment(viewModel.getThreadId());
 | 
			
		||||
            NavHostFragment.findNavController(this).navigate(action);
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
        if (itemId == R.id.mark_as_seen) {
 | 
			
		||||
@ -372,7 +373,9 @@ public class DirectMessageThreadFragment extends Fragment {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    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 Optional<DirectThread> first = threads != null
 | 
			
		||||
                                             ? threads.stream()
 | 
			
		||||
 | 
			
		||||
@ -37,8 +37,8 @@ import awais.instagrabber.utils.Constants;
 | 
			
		||||
import awais.instagrabber.utils.CookieUtils;
 | 
			
		||||
import awais.instagrabber.utils.FlavorTown;
 | 
			
		||||
import awais.instagrabber.utils.TextUtils;
 | 
			
		||||
import awais.instagrabber.webservices.ProfileService;
 | 
			
		||||
import awais.instagrabber.webservices.ServiceCallback;
 | 
			
		||||
import awais.instagrabber.webservices.UserService;
 | 
			
		||||
 | 
			
		||||
import static awais.instagrabber.utils.Utils.settingsHelper;
 | 
			
		||||
 | 
			
		||||
@ -199,8 +199,8 @@ public class MorePreferencesFragment extends BasePreferencesFragment {
 | 
			
		||||
 | 
			
		||||
            // adds cookies to database for quick access
 | 
			
		||||
            final String uid = CookieUtils.getUserIdFromCookie(cookie);
 | 
			
		||||
            final ProfileService profileService = ProfileService.getInstance();
 | 
			
		||||
            profileService.getUserInfo(uid, new ServiceCallback<UserInfo>() {
 | 
			
		||||
            final UserService userService = UserService.getInstance();
 | 
			
		||||
            userService.getUserInfo(uid, new ServiceCallback<UserInfo>() {
 | 
			
		||||
                @Override
 | 
			
		||||
                public void onSuccess(final 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.DirectInboxResponse;
 | 
			
		||||
import awais.instagrabber.repositories.responses.directmessages.DirectThreadBroadcastResponse;
 | 
			
		||||
import awais.instagrabber.repositories.responses.directmessages.DirectThreadDetailsChangeResponse;
 | 
			
		||||
import awais.instagrabber.repositories.responses.directmessages.DirectThreadFeedResponse;
 | 
			
		||||
import retrofit2.Call;
 | 
			
		||||
import retrofit2.http.FieldMap;
 | 
			
		||||
@ -30,4 +31,29 @@ public interface DirectMessagesRepository {
 | 
			
		||||
    @POST("/api/v1/direct_v2/threads/broadcast/{item}/")
 | 
			
		||||
    Call<DirectThreadBroadcastResponse> broadcast(@Path("item") String item,
 | 
			
		||||
                                                  @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 {
 | 
			
		||||
 | 
			
		||||
    @GET("/api/v1/users/{uid}/info/")
 | 
			
		||||
    Call<String> getUserInfo(@Path("uid") final String uid);
 | 
			
		||||
 | 
			
		||||
    @GET("/api/v1/feed/user/{uid}/")
 | 
			
		||||
    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;
 | 
			
		||||
 | 
			
		||||
public class DirectInboxResponse {
 | 
			
		||||
    private final DirectUser viewer;
 | 
			
		||||
    private final DirectInbox inbox;
 | 
			
		||||
    private final long seqId;
 | 
			
		||||
    private final long snapshotAtMs;
 | 
			
		||||
@ -8,12 +9,14 @@ public class DirectInboxResponse {
 | 
			
		||||
    private final DirectUser mostRecentInviter;
 | 
			
		||||
    private final String status;
 | 
			
		||||
 | 
			
		||||
    public DirectInboxResponse(final DirectInbox inbox,
 | 
			
		||||
    public DirectInboxResponse(final DirectUser viewer,
 | 
			
		||||
                               final DirectInbox inbox,
 | 
			
		||||
                               final long seqId,
 | 
			
		||||
                               final long snapshotAtMs,
 | 
			
		||||
                               final int pendingRequestsTotal,
 | 
			
		||||
                               final DirectUser mostRecentInviter,
 | 
			
		||||
                               final String status) {
 | 
			
		||||
        this.viewer = viewer;
 | 
			
		||||
        this.inbox = inbox;
 | 
			
		||||
        this.seqId = seqId;
 | 
			
		||||
        this.snapshotAtMs = snapshotAtMs;
 | 
			
		||||
@ -22,6 +25,10 @@ public class DirectInboxResponse {
 | 
			
		||||
        this.status = status;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public DirectUser getViewer() {
 | 
			
		||||
        return viewer;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public DirectInbox getInbox() {
 | 
			
		||||
        return inbox;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -10,8 +10,8 @@ public class DirectThread {
 | 
			
		||||
    private final String threadId;
 | 
			
		||||
    private final String threadV2Id;
 | 
			
		||||
    private final List<DirectUser> users;
 | 
			
		||||
    private final List<String> leftUsers;
 | 
			
		||||
    private final List<String> adminUserIds;
 | 
			
		||||
    private final List<DirectUser> leftUsers;
 | 
			
		||||
    private final List<Long> adminUserIds;
 | 
			
		||||
    private final List<DirectItem> items;
 | 
			
		||||
    private final long lastActivityAt;
 | 
			
		||||
    private final boolean muted;
 | 
			
		||||
@ -42,8 +42,8 @@ public class DirectThread {
 | 
			
		||||
    public DirectThread(final String threadId,
 | 
			
		||||
                        final String threadV2Id,
 | 
			
		||||
                        final List<DirectUser> users,
 | 
			
		||||
                        final List<String> leftUsers,
 | 
			
		||||
                        final List<String> adminUserIds,
 | 
			
		||||
                        final List<DirectUser> leftUsers,
 | 
			
		||||
                        final List<Long> adminUserIds,
 | 
			
		||||
                        final List<DirectItem> items,
 | 
			
		||||
                        final long lastActivityAt,
 | 
			
		||||
                        final boolean muted,
 | 
			
		||||
@ -115,11 +115,11 @@ public class DirectThread {
 | 
			
		||||
        return users;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public List<String> getLeftUsers() {
 | 
			
		||||
    public List<DirectUser> getLeftUsers() {
 | 
			
		||||
        return leftUsers;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public List<String> getAdminUserIds() {
 | 
			
		||||
    public List<Long> getAdminUserIds() {
 | 
			
		||||
        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;
 | 
			
		||||
 | 
			
		||||
import java.io.Serializable;
 | 
			
		||||
import java.util.Objects;
 | 
			
		||||
 | 
			
		||||
import awais.instagrabber.models.ProfileModel;
 | 
			
		||||
 | 
			
		||||
public class DirectUser {
 | 
			
		||||
public class DirectUser implements Serializable {
 | 
			
		||||
    private final long pk;
 | 
			
		||||
    private final String username;
 | 
			
		||||
    private final String fullName;
 | 
			
		||||
@ -104,8 +107,8 @@ public class DirectUser {
 | 
			
		||||
                        profileModel.isPrivate(),
 | 
			
		||||
                        false,
 | 
			
		||||
                        profileModel.isRequested(),
 | 
			
		||||
                        false
 | 
			
		||||
                ),
 | 
			
		||||
                        false,
 | 
			
		||||
                        profileModel.isRestricted()),
 | 
			
		||||
                profileModel.isVerified(),
 | 
			
		||||
                false,
 | 
			
		||||
                false,
 | 
			
		||||
@ -113,4 +116,18 @@ public class DirectUser {
 | 
			
		||||
                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;
 | 
			
		||||
 | 
			
		||||
public class DirectUserFriendshipStatus {
 | 
			
		||||
import androidx.annotation.NonNull;
 | 
			
		||||
 | 
			
		||||
import java.io.Serializable;
 | 
			
		||||
 | 
			
		||||
public class DirectUserFriendshipStatus implements Serializable {
 | 
			
		||||
    private final boolean following;
 | 
			
		||||
    private final boolean blocking;
 | 
			
		||||
    private final boolean isPrivate;
 | 
			
		||||
    private final boolean incomingRequest;
 | 
			
		||||
    private final boolean outgoingRequest;
 | 
			
		||||
    private final boolean isBestie;
 | 
			
		||||
    private final boolean isRestricted;
 | 
			
		||||
 | 
			
		||||
    public DirectUserFriendshipStatus(final boolean following,
 | 
			
		||||
                                      final boolean blocking,
 | 
			
		||||
                                      final boolean isPrivate,
 | 
			
		||||
                                      final boolean incomingRequest,
 | 
			
		||||
                                      final boolean outgoingRequest,
 | 
			
		||||
                                      final boolean isBestie) {
 | 
			
		||||
                                      final boolean isBestie, final boolean isRestricted) {
 | 
			
		||||
        this.following = following;
 | 
			
		||||
        this.blocking = blocking;
 | 
			
		||||
        this.isPrivate = isPrivate;
 | 
			
		||||
        this.incomingRequest = incomingRequest;
 | 
			
		||||
        this.outgoingRequest = outgoingRequest;
 | 
			
		||||
        this.isBestie = isBestie;
 | 
			
		||||
        this.isRestricted = isRestricted;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public boolean isFollowing() {
 | 
			
		||||
@ -46,15 +52,21 @@ public class DirectUserFriendshipStatus {
 | 
			
		||||
        return isBestie;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public boolean isRestricted() {
 | 
			
		||||
        return isRestricted;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @NonNull
 | 
			
		||||
    @Override
 | 
			
		||||
    public String toString() {
 | 
			
		||||
        return "DirectInboxFeedResponseFriendshipStatus{" +
 | 
			
		||||
                "following=" + following +
 | 
			
		||||
                ", blocking=" + blocking +
 | 
			
		||||
                ", is_private=" + isPrivate +
 | 
			
		||||
                ", isPrivate=" + isPrivate +
 | 
			
		||||
                ", incomingRequest=" + incomingRequest +
 | 
			
		||||
                ", outgoingRequest=" + outgoingRequest +
 | 
			
		||||
                ", 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.Window;
 | 
			
		||||
import android.view.WindowManager;
 | 
			
		||||
import android.view.inputmethod.InputMethodManager;
 | 
			
		||||
import android.webkit.MimeTypeMap;
 | 
			
		||||
import android.widget.Toast;
 | 
			
		||||
 | 
			
		||||
@ -340,4 +341,17 @@ public final class Utils {
 | 
			
		||||
                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.ShapeDrawable;
 | 
			
		||||
import android.graphics.drawable.shapes.RoundRectShape;
 | 
			
		||||
import android.view.View;
 | 
			
		||||
import android.widget.FrameLayout;
 | 
			
		||||
import android.widget.TextView;
 | 
			
		||||
 | 
			
		||||
import androidx.annotation.ColorInt;
 | 
			
		||||
import androidx.annotation.NonNull;
 | 
			
		||||
import androidx.core.content.res.ResourcesCompat;
 | 
			
		||||
import androidx.core.util.Pair;
 | 
			
		||||
 | 
			
		||||
public final class ViewUtils {
 | 
			
		||||
 | 
			
		||||
@ -53,4 +57,16 @@ public final class ViewUtils {
 | 
			
		||||
    private static int getSize(float 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.DirectInboxResponse;
 | 
			
		||||
import awais.instagrabber.repositories.responses.directmessages.DirectThread;
 | 
			
		||||
import awais.instagrabber.repositories.responses.directmessages.DirectUser;
 | 
			
		||||
import awais.instagrabber.utils.Constants;
 | 
			
		||||
import awais.instagrabber.utils.CookieUtils;
 | 
			
		||||
import awais.instagrabber.utils.TextUtils;
 | 
			
		||||
@ -38,6 +39,7 @@ public class DirectInboxViewModel extends ViewModel {
 | 
			
		||||
    private long seqId;
 | 
			
		||||
    private String cursor;
 | 
			
		||||
    private boolean hasOlder = true;
 | 
			
		||||
    private DirectUser viewer;
 | 
			
		||||
 | 
			
		||||
    public DirectInboxViewModel() {
 | 
			
		||||
        final String cookie = settingsHelper.getString(Constants.COOKIE);
 | 
			
		||||
@ -76,6 +78,10 @@ public class DirectInboxViewModel extends ViewModel {
 | 
			
		||||
        return fetchingInbox;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public DirectUser getViewer() {
 | 
			
		||||
        return viewer;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void fetchInbox() {
 | 
			
		||||
        if ((fetchingInbox.getValue() != null && fetchingInbox.getValue()) || !hasOlder) return;
 | 
			
		||||
        stopCurrentInboxRequest();
 | 
			
		||||
@ -108,6 +114,9 @@ public class DirectInboxViewModel extends ViewModel {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        seqId = response.getSeqId();
 | 
			
		||||
        if (viewer == null) {
 | 
			
		||||
            viewer = response.getViewer();
 | 
			
		||||
        }
 | 
			
		||||
        final DirectInbox inbox = response.getInbox();
 | 
			
		||||
        final List<DirectThread> threads = inbox.getThreads();
 | 
			
		||||
        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 java.io.UnsupportedEncodingException;
 | 
			
		||||
import java.util.Collection;
 | 
			
		||||
import java.util.HashMap;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
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.DirectInboxResponse;
 | 
			
		||||
import awais.instagrabber.repositories.responses.directmessages.DirectThreadBroadcastResponse;
 | 
			
		||||
import awais.instagrabber.repositories.responses.directmessages.DirectThreadDetailsChangeResponse;
 | 
			
		||||
import awais.instagrabber.repositories.responses.directmessages.DirectThreadFeedResponse;
 | 
			
		||||
import awais.instagrabber.utils.TextUtils;
 | 
			
		||||
import awais.instagrabber.utils.Utils;
 | 
			
		||||
@ -175,4 +177,54 @@ public class DirectMessagesService extends BaseService {
 | 
			
		||||
        final Map<String, String> signedForm = Utils.sign(form);
 | 
			
		||||
        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.repositories.ProfileRepository;
 | 
			
		||||
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.TextUtils;
 | 
			
		||||
import retrofit2.Call;
 | 
			
		||||
@ -47,39 +45,6 @@ public class ProfileService extends BaseService {
 | 
			
		||||
        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,
 | 
			
		||||
                           final String maxId,
 | 
			
		||||
                           final ServiceCallback<PostsFetchResponse> callback) {
 | 
			
		||||
 | 
			
		||||
@ -160,8 +160,8 @@ public class StoriesService extends BaseService {
 | 
			
		||||
                                highlightNode.getString("title"),
 | 
			
		||||
                                highlightNode.getString(Constants.EXTRAS_ID),
 | 
			
		||||
                                highlightNode.getJSONObject("cover_media")
 | 
			
		||||
                                        .getJSONObject("cropped_image_version")
 | 
			
		||||
                                        .getString("url"),
 | 
			
		||||
                                             .getJSONObject("cropped_image_version")
 | 
			
		||||
                                             .getString("url"),
 | 
			
		||||
                                highlightNode.getLong("latest_reel_media"),
 | 
			
		||||
                                highlightNode.getInt("media_count")
 | 
			
		||||
                        ));
 | 
			
		||||
@ -188,7 +188,7 @@ public class StoriesService extends BaseService {
 | 
			
		||||
        form.put("include_suggested_highlights", "false");
 | 
			
		||||
        form.put("is_in_archive_home", "true");
 | 
			
		||||
        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)) {
 | 
			
		||||
            form.put("max_id", maxId); // NOT TESTED
 | 
			
		||||
        }
 | 
			
		||||
@ -338,40 +338,40 @@ public class StoriesService extends BaseService {
 | 
			
		||||
 | 
			
		||||
    // RespondAction.java
 | 
			
		||||
    public void respondToQuestion(final String storyId,
 | 
			
		||||
                                   final String stickerId,
 | 
			
		||||
                                   final String answer,
 | 
			
		||||
                                   final String userId,
 | 
			
		||||
                                   final String csrfToken,
 | 
			
		||||
                                   final ServiceCallback<StoryStickerResponse> callback) {
 | 
			
		||||
                                  final String stickerId,
 | 
			
		||||
                                  final String answer,
 | 
			
		||||
                                  final String userId,
 | 
			
		||||
                                  final String csrfToken,
 | 
			
		||||
                                  final ServiceCallback<StoryStickerResponse> callback) {
 | 
			
		||||
        respondToSticker(storyId, stickerId, "story_question_response", "response", answer, userId, csrfToken, callback);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // QuizAction.java
 | 
			
		||||
    public void respondToQuiz(final String storyId,
 | 
			
		||||
                               final String stickerId,
 | 
			
		||||
                               final int answer,
 | 
			
		||||
                               final String userId,
 | 
			
		||||
                               final String csrfToken,
 | 
			
		||||
                               final ServiceCallback<StoryStickerResponse> callback) {
 | 
			
		||||
                              final String stickerId,
 | 
			
		||||
                              final int answer,
 | 
			
		||||
                              final String userId,
 | 
			
		||||
                              final String csrfToken,
 | 
			
		||||
                              final ServiceCallback<StoryStickerResponse> callback) {
 | 
			
		||||
        respondToSticker(storyId, stickerId, "story_quiz_answer", "answer", String.valueOf(answer), userId, csrfToken, callback);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // VoteAction.java
 | 
			
		||||
    public void respondToPoll(final String storyId,
 | 
			
		||||
                               final String stickerId,
 | 
			
		||||
                               final int answer,
 | 
			
		||||
                               final String userId,
 | 
			
		||||
                               final String csrfToken,
 | 
			
		||||
                               final ServiceCallback<StoryStickerResponse> callback) {
 | 
			
		||||
                              final String stickerId,
 | 
			
		||||
                              final int answer,
 | 
			
		||||
                              final String userId,
 | 
			
		||||
                              final String csrfToken,
 | 
			
		||||
                              final ServiceCallback<StoryStickerResponse> callback) {
 | 
			
		||||
        respondToSticker(storyId, stickerId, "story_poll_vote", "vote", String.valueOf(answer), userId, csrfToken, callback);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void respondToSlider(final String storyId,
 | 
			
		||||
                              final String stickerId,
 | 
			
		||||
                              final double answer,
 | 
			
		||||
                              final String userId,
 | 
			
		||||
                              final String csrfToken,
 | 
			
		||||
                              final ServiceCallback<StoryStickerResponse> callback) {
 | 
			
		||||
                                final String stickerId,
 | 
			
		||||
                                final double answer,
 | 
			
		||||
                                final String userId,
 | 
			
		||||
                                final String csrfToken,
 | 
			
		||||
                                final ServiceCallback<StoryStickerResponse> 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"?>
 | 
			
		||||
<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:tools="http://schemas.android.com/tools"
 | 
			
		||||
    android:layout_width="match_parent"
 | 
			
		||||
    android:layout_height="match_parent"
 | 
			
		||||
    android:orientation="vertical">
 | 
			
		||||
    android:focusableInTouchMode="true">
 | 
			
		||||
 | 
			
		||||
    <LinearLayout
 | 
			
		||||
        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"
 | 
			
		||||
    <com.google.android.material.appbar.AppBarLayout
 | 
			
		||||
        android:layout_width="match_parent"
 | 
			
		||||
        android:layout_height="wrap_content"
 | 
			
		||||
        android:layout_marginLeft="8dp"
 | 
			
		||||
        android:layout_marginStart="6dp"
 | 
			
		||||
        android:layout_marginRight="8dp"
 | 
			
		||||
        android:text="@string/dms_action_leave"
 | 
			
		||||
        android:textColor="@color/btn_red_text_color"
 | 
			
		||||
        android:textSize="18sp"
 | 
			
		||||
        app:backgroundTint="@color/btn_red_background" />
 | 
			
		||||
        android:background="@null"
 | 
			
		||||
        android:elevation="0dp"
 | 
			
		||||
        app:elevation="0dp">
 | 
			
		||||
 | 
			
		||||
    <androidx.swiperefreshlayout.widget.SwipeRefreshLayout
 | 
			
		||||
        android:id="@+id/swipeRefreshLayout"
 | 
			
		||||
        android:layout_width="match_parent"
 | 
			
		||||
        android:layout_height="0dp"
 | 
			
		||||
        android:layout_weight="1">
 | 
			
		||||
 | 
			
		||||
        <androidx.core.widget.NestedScrollView
 | 
			
		||||
        <com.google.android.material.appbar.CollapsingToolbarLayout
 | 
			
		||||
            android:layout_width="match_parent"
 | 
			
		||||
            android:layout_height="match_parent">
 | 
			
		||||
            <LinearLayout
 | 
			
		||||
            android:layout_height="wrap_content"
 | 
			
		||||
            app:layout_scrollFlags="scroll">
 | 
			
		||||
 | 
			
		||||
            <androidx.constraintlayout.widget.ConstraintLayout
 | 
			
		||||
                android:id="@+id/settings_parent"
 | 
			
		||||
                android:layout_width="match_parent"
 | 
			
		||||
                android:layout_height="match_parent"
 | 
			
		||||
                android:orientation="vertical">
 | 
			
		||||
                <androidx.recyclerview.widget.RecyclerView
 | 
			
		||||
                    android:id="@+id/userList"
 | 
			
		||||
                    android:layout_width="match_parent"
 | 
			
		||||
                    android:layout_height="match_parent"
 | 
			
		||||
                    android:nestedScrollingEnabled="false"/>
 | 
			
		||||
                <androidx.appcompat.widget.AppCompatTextView
 | 
			
		||||
                    android:id="@+id/leftTitle"
 | 
			
		||||
                android:layout_height="wrap_content">
 | 
			
		||||
 | 
			
		||||
                <com.google.android.material.textfield.TextInputLayout
 | 
			
		||||
                    android:id="@+id/title_edit_input_layout"
 | 
			
		||||
                    android:layout_width="match_parent"
 | 
			
		||||
                    android:layout_height="wrap_content"
 | 
			
		||||
                    android:animateLayoutChanges="true"
 | 
			
		||||
                    android:hint="@string/title"
 | 
			
		||||
                    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:padding="5dp"
 | 
			
		||||
                    android:text="@string/dms_left_users"
 | 
			
		||||
                    android:paddingStart="16dp"
 | 
			
		||||
                    android:paddingEnd="16dp"
 | 
			
		||||
                    android:text="@string/mute_messages"
 | 
			
		||||
                    android:textAppearance="@style/TextAppearance.MaterialComponents.Body1"
 | 
			
		||||
                    android:textColor="?android:textColorPrimary"
 | 
			
		||||
                    android:textSize="24sp"/>
 | 
			
		||||
                <androidx.recyclerview.widget.RecyclerView
 | 
			
		||||
                    android:id="@+id/leftUserList"
 | 
			
		||||
                    app:layout_constraintBottom_toBottomOf="@id/mute_messages"
 | 
			
		||||
                    app:layout_constraintEnd_toEndOf="parent"
 | 
			
		||||
                    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_height="match_parent"
 | 
			
		||||
                    android:nestedScrollingEnabled="false"/>
 | 
			
		||||
            </LinearLayout>
 | 
			
		||||
        </androidx.core.widget.NestedScrollView>
 | 
			
		||||
    </androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
 | 
			
		||||
</LinearLayout>
 | 
			
		||||
                    android:layout_height="wrap_content"
 | 
			
		||||
                    android:gravity="center_vertical"
 | 
			
		||||
                    android:paddingStart="16dp"
 | 
			
		||||
                    android:paddingTop="8dp"
 | 
			
		||||
                    android:paddingEnd="16dp"
 | 
			
		||||
                    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_height="60dp"
 | 
			
		||||
        android:layout_marginStart="66dp"
 | 
			
		||||
        android:layout_marginLeft="66dp"
 | 
			
		||||
        android:layout_marginEnd="26dp"
 | 
			
		||||
        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
 | 
			
		||||
        android:id="@+id/action_global_likesViewerFragment"
 | 
			
		||||
        app:destination="@id/likes_nav_graph">
 | 
			
		||||
    <argument
 | 
			
		||||
        android:name="postId"
 | 
			
		||||
        app:argType="string"
 | 
			
		||||
        app:nullable="false" />
 | 
			
		||||
    <argument
 | 
			
		||||
        android:name="isComment"
 | 
			
		||||
        app:argType="boolean"
 | 
			
		||||
        app:nullable="false" />
 | 
			
		||||
        <argument
 | 
			
		||||
            android:name="postId"
 | 
			
		||||
            app:argType="string"
 | 
			
		||||
            app:nullable="false" />
 | 
			
		||||
        <argument
 | 
			
		||||
            android:name="isComment"
 | 
			
		||||
            app:argType="boolean"
 | 
			
		||||
            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>
 | 
			
		||||
 | 
			
		||||
    <fragment
 | 
			
		||||
@ -112,20 +136,23 @@
 | 
			
		||||
    <fragment
 | 
			
		||||
        android:id="@+id/directMessagesSettingsFragment"
 | 
			
		||||
        android:name="awais.instagrabber.fragments.directmessages.DirectMessageSettingsFragment"
 | 
			
		||||
        android:label="@string/action_settings"
 | 
			
		||||
        android:label="@string/details"
 | 
			
		||||
        tools:layout="@layout/fragment_direct_messages_settings">
 | 
			
		||||
        <argument
 | 
			
		||||
            android:name="threadId"
 | 
			
		||||
            app:argType="string" />
 | 
			
		||||
        <argument
 | 
			
		||||
            android:name="title"
 | 
			
		||||
            app:argType="string" />
 | 
			
		||||
        <action
 | 
			
		||||
            android:id="@+id/action_settings_to_inbox"
 | 
			
		||||
            app:destination="@id/directMessagesInboxFragment"
 | 
			
		||||
            app:popUpTo="@+id/directMessagesInboxFragment"
 | 
			
		||||
            app:popUpToInclusive="true" />
 | 
			
		||||
 | 
			
		||||
    </fragment>
 | 
			
		||||
    <fragment
 | 
			
		||||
        android:id="@+id/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
 | 
			
		||||
            android:name="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_cant_deliver">Cannot deliver</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_kick">Kick</string>
 | 
			
		||||
    <string name="dms_left_users">Left users</string>
 | 
			
		||||
@ -378,4 +378,16 @@
 | 
			
		||||
        <item quantity="other">%s stories</item>
 | 
			
		||||
    </plurals>
 | 
			
		||||
    <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>
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user