Allow ending a group chat

This commit is contained in:
Ammar Githam 2021-01-20 02:24:49 +09:00
parent ba526eb0bb
commit 6aee7ea863
14 changed files with 176 additions and 158 deletions

View File

@ -50,6 +50,7 @@ import awais.instagrabber.utils.ResponseBodyUtils;
public abstract class DirectItemViewHolder extends RecyclerView.ViewHolder implements SwipeableViewHolder { public abstract class DirectItemViewHolder extends RecyclerView.ViewHolder implements SwipeableViewHolder {
private static final String TAG = DirectItemViewHolder.class.getSimpleName(); private static final String TAG = DirectItemViewHolder.class.getSimpleName();
// private static final List<Integer> THREAD_CHANGING_OPTIONS = ImmutableList.of(R.id.unsend);
private final LayoutDmBaseBinding binding; private final LayoutDmBaseBinding binding;
private final User currentUser; private final User currentUser;
@ -532,10 +533,13 @@ public abstract class DirectItemViewHolder extends RecyclerView.ViewHolder imple
if (canForward()) { if (canForward()) {
builder.add(new DirectItemContextMenu.MenuItem(R.id.forward, R.string.forward)); builder.add(new DirectItemContextMenu.MenuItem(R.id.forward, R.string.forward));
} }
if (messageDirection == MessageDirection.OUTGOING) { if (thread.getInputMode() != 1 && messageDirection == MessageDirection.OUTGOING) {
builder.add(new DirectItemContextMenu.MenuItem(R.id.unsend, R.string.dms_inbox_unsend)); builder.add(new DirectItemContextMenu.MenuItem(R.id.unsend, R.string.dms_inbox_unsend));
} }
final DirectItemContextMenu menu = new DirectItemContextMenu(itemView.getContext(), allowReaction(), builder.build()); final boolean showReactions = thread.getInputMode() != 1 && allowReaction();
final ImmutableList<DirectItemContextMenu.MenuItem> menuItems = builder.build();
if (!showReactions && menuItems.isEmpty()) return;
final DirectItemContextMenu menu = new DirectItemContextMenu(itemView.getContext(), showReactions, menuItems);
menu.setOnDismissListener(() -> setSelected(false)); menu.setOnDismissListener(() -> setSelected(false));
menu.setOnReactionClickListener(emoji -> callback.onReaction(item, emoji)); menu.setOnReactionClickListener(emoji -> callback.onReaction(item, emoji));
menu.setOnOptionSelectListener(itemId -> callback.onOptionSelect(item, itemId)); menu.setOnOptionSelectListener(itemId -> callback.onOptionSelect(item, itemId));

View File

@ -6,6 +6,7 @@ import android.animation.AnimatorSet;
import android.animation.TimeInterpolator; import android.animation.TimeInterpolator;
import android.animation.ValueAnimator; import android.animation.ValueAnimator;
import android.content.Context; import android.content.Context;
import android.content.res.Resources;
import android.graphics.Point; import android.graphics.Point;
import android.graphics.Rect; import android.graphics.Rect;
import android.util.TypedValue; import android.util.TypedValue;
@ -60,11 +61,8 @@ public class DirectItemContextMenu extends PopupWindow {
private final int addAdjust; private final int addAdjust;
private final boolean hasOptions; private final boolean hasOptions;
private final List<MenuItem> options; private final List<MenuItem> options;
private final int widthWithoutReactions;
/* = ImmutableList.of(
new MenuItem(R.id.reply, R.string.reply),
new MenuItem(R.id.unsend, R.string.dms_inbox_unsend)
);*/
private AnimatorSet openCloseAnimator; private AnimatorSet openCloseAnimator;
private Point location; private Point location;
private Point point; private Point point;
@ -81,13 +79,15 @@ public class DirectItemContextMenu extends PopupWindow {
throw new IllegalArgumentException("showReactions is set false and options are empty"); throw new IllegalArgumentException("showReactions is set false and options are empty");
} }
reactionsManager = ReactionsManager.getInstance(); reactionsManager = ReactionsManager.getInstance();
emojiSize = context.getResources().getDimensionPixelSize(R.dimen.reaction_picker_emoji_size); final Resources resources = context.getResources();
emojiMargin = context.getResources().getDimensionPixelSize(R.dimen.reaction_picker_emoji_margin); emojiSize = resources.getDimensionPixelSize(R.dimen.reaction_picker_emoji_size);
emojiMargin = resources.getDimensionPixelSize(R.dimen.reaction_picker_emoji_margin);
emojiMarginHalf = emojiMargin / 2; emojiMarginHalf = emojiMargin / 2;
addAdjust = context.getResources().getDimensionPixelSize(R.dimen.reaction_picker_add_padding_adjustment); addAdjust = resources.getDimensionPixelSize(R.dimen.reaction_picker_add_padding_adjustment);
dividerHeight = context.getResources().getDimensionPixelSize(R.dimen.horizontal_divider_height); dividerHeight = resources.getDimensionPixelSize(R.dimen.horizontal_divider_height);
optionHeight = context.getResources().getDimensionPixelSize(R.dimen.reaction_picker_option_height); optionHeight = resources.getDimensionPixelSize(R.dimen.reaction_picker_option_height);
optionPadding = context.getResources().getDimensionPixelSize(R.dimen.dm_message_card_radius); optionPadding = resources.getDimensionPixelSize(R.dimen.dm_message_card_radius);
widthWithoutReactions = resources.getDimensionPixelSize(R.dimen.dm_item_context_min_width);
exitAnimationListener = new AnimatorListenerAdapter() { exitAnimationListener = new AnimatorListenerAdapter() {
@Override @Override
public void onAnimationEnd(final Animator animation) { public void onAnimationEnd(final Animator animation) {
@ -315,6 +315,9 @@ public class DirectItemContextMenu extends PopupWindow {
final ConstraintLayout container, final ConstraintLayout container,
@Nullable final View divider) { @Nullable final View divider) {
View prevOptionView = null; View prevOptionView = null;
if (!showReactions) {
container.getLayoutParams().width = widthWithoutReactions;
}
for (int i = 0; i < options.size(); i++) { for (int i = 0; i < options.size(); i++) {
final MenuItem menuItem = options.get(i); final MenuItem menuItem = options.get(i);
final AppCompatTextView textView = getTextView(); final AppCompatTextView textView = getTextView();
@ -324,11 +327,12 @@ public class DirectItemContextMenu extends PopupWindow {
if (i == 0) { if (i == 0) {
if (divider != null) { if (divider != null) {
layoutParams.topToBottom = divider.getId(); layoutParams.topToBottom = divider.getId();
((ConstraintLayout.LayoutParams) divider.getLayoutParams()).bottomToTop = textView.getId();
} else { } else {
// if divider is null mean reactions were not added, so connect top to top of parent // if divider is null mean reactions were not added, so connect top to top of parent
layoutParams.topToTop = ConstraintLayout.LayoutParams.PARENT_ID; layoutParams.topToTop = ConstraintLayout.LayoutParams.PARENT_ID;
layoutParams.topMargin = emojiMargin; // material design spec (https://material.io/components/menus#specs)
} }
((ConstraintLayout.LayoutParams) divider.getLayoutParams()).bottomToTop = textView.getId();
} else { } else {
layoutParams.topToBottom = prevOptionView.getId(); layoutParams.topToBottom = prevOptionView.getId();
final ConstraintLayout.LayoutParams prevLayoutParams = (ConstraintLayout.LayoutParams) prevOptionView.getLayoutParams(); final ConstraintLayout.LayoutParams prevLayoutParams = (ConstraintLayout.LayoutParams) prevOptionView.getLayoutParams();

View File

@ -62,6 +62,7 @@ public class DirectMessageSettingsFragment extends Fragment implements ConfirmDi
private static final String TAG = DirectMessageSettingsFragment.class.getSimpleName(); private static final String TAG = DirectMessageSettingsFragment.class.getSimpleName();
private static final int APPROVAL_REQUIRED_REQUEST_CODE = 200; private static final int APPROVAL_REQUIRED_REQUEST_CODE = 200;
private static final int LEAVE_THREAD_REQUEST_CODE = 201; private static final int LEAVE_THREAD_REQUEST_CODE = 201;
private static final int END_THREAD_REQUEST_CODE = 202;
private FragmentDirectMessagesSettingsBinding binding; private FragmentDirectMessagesSettingsBinding binding;
private DirectSettingsViewModel viewModel; private DirectSettingsViewModel viewModel;
@ -147,6 +148,17 @@ public class DirectMessageSettingsFragment extends Fragment implements ConfirmDi
} }
private void setupObservers() { private void setupObservers() {
viewModel.getInputMode().observe(getViewLifecycleOwner(), inputMode -> {
if (inputMode == null || inputMode == 0) return;
if (inputMode == 1) {
binding.groupSettings.setVisibility(View.GONE);
binding.pendingMembersGroup.setVisibility(View.GONE);
binding.approvalRequired.setVisibility(View.GONE);
binding.approvalRequiredLabel.setVisibility(View.GONE);
binding.muteMessagesLabel.setVisibility(View.GONE);
binding.muteMessages.setVisibility(View.GONE);
}
});
viewModel.getUsers().observe(getViewLifecycleOwner(), users -> { viewModel.getUsers().observe(getViewLifecycleOwner(), users -> {
if (usersAdapter == null) return; if (usersAdapter == null) return;
usersAdapter.submitUsers(users.first, users.second); usersAdapter.submitUsers(users.first, users.second);
@ -230,6 +242,11 @@ public class DirectMessageSettingsFragment extends Fragment implements ConfirmDi
private void setupSettings() { private void setupSettings() {
binding.groupSettings.setVisibility(viewModel.isGroup() ? View.VISIBLE : View.GONE); binding.groupSettings.setVisibility(viewModel.isGroup() ? View.VISIBLE : View.GONE);
binding.muteMessagesLabel.setOnClickListener(v -> binding.muteMessages.toggle());
binding.muteMessages.setOnCheckedChangeListener((buttonView, isChecked) -> {
final LiveData<Resource<Object>> resourceLiveData = isChecked ? viewModel.mute() : viewModel.unmute();
handleSwitchChangeResource(resourceLiveData, buttonView);
});
if (!viewModel.isGroup()) return; if (!viewModel.isGroup()) return;
binding.titleEdit.addTextChangedListener(new TextWatcherAdapter() { binding.titleEdit.addTextChangedListener(new TextWatcherAdapter() {
@Override @Override
@ -274,11 +291,6 @@ public class DirectMessageSettingsFragment extends Fragment implements ConfirmDi
.setMultiple(true); .setMultiple(true);
navController.navigate(actionGlobalUserSearch); navController.navigate(actionGlobalUserSearch);
}); });
binding.muteMessagesLabel.setOnClickListener(v -> binding.muteMessages.toggle());
binding.muteMessages.setOnCheckedChangeListener((buttonView, isChecked) -> {
final LiveData<Resource<Object>> resourceLiveData = isChecked ? viewModel.mute() : viewModel.unmute();
handleSwitchChangeResource(resourceLiveData, buttonView);
});
binding.muteMentionsLabel.setOnClickListener(v -> binding.muteMentions.toggle()); binding.muteMentionsLabel.setOnClickListener(v -> binding.muteMentions.toggle());
binding.muteMentions.setOnCheckedChangeListener((buttonView, isChecked) -> { binding.muteMentions.setOnCheckedChangeListener((buttonView, isChecked) -> {
final LiveData<Resource<Object>> resourceLiveData = isChecked ? viewModel.muteMentions() : viewModel.unmuteMentions(); final LiveData<Resource<Object>> resourceLiveData = isChecked ? viewModel.muteMentions() : viewModel.unmuteMentions();
@ -296,6 +308,22 @@ public class DirectMessageSettingsFragment extends Fragment implements ConfirmDi
); );
confirmDialogFragment.show(getChildFragmentManager(), "leave_thread_confirmation_dialog"); confirmDialogFragment.show(getChildFragmentManager(), "leave_thread_confirmation_dialog");
}); });
if (viewModel.isViewerAdmin()) {
binding.end.setVisibility(View.VISIBLE);
binding.end.setOnClickListener(v -> {
final ConfirmDialogFragment confirmDialogFragment = ConfirmDialogFragment.newInstance(
END_THREAD_REQUEST_CODE,
R.string.dms_action_end_question,
R.string.dms_action_end_description,
R.string.yes,
R.string.no,
-1
);
confirmDialogFragment.show(getChildFragmentManager(), "end_thread_confirmation_dialog");
});
} else {
binding.end.setVisibility(View.GONE);
}
} }
private void setApprovalRelatedUI() { private void setApprovalRelatedUI() {
@ -476,6 +504,26 @@ public class DirectMessageSettingsFragment extends Fragment implements ConfirmDi
break; break;
} }
}); });
return;
}
if (requestCode == END_THREAD_REQUEST_CODE) {
final LiveData<Resource<Object>> resourceLiveData = viewModel.end();
resourceLiveData.observe(getViewLifecycleOwner(), resource -> {
if (resource == null) return;
switch (resource.status) {
case SUCCESS:
break;
case ERROR:
binding.end.setEnabled(true);
if (resource.message != null) {
Snackbar.make(binding.getRoot(), resource.message, Snackbar.LENGTH_LONG).show();
}
break;
case LOADING:
binding.end.setEnabled(false);
break;
}
});
} }
} }

View File

@ -293,6 +293,7 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact
backStackSavedStateResultLiveData.postValue(null); backStackSavedStateResultLiveData.postValue(null);
}; };
private final MutableLiveData<Integer> inputLength = new MutableLiveData<>(0); private final MutableLiveData<Integer> inputLength = new MutableLiveData<>(0);
private ItemTouchHelper itemTouchHelper;
@Override @Override
public void onCreate(@Nullable final Bundle savedInstanceState) { public void onCreate(@Nullable final Bundle savedInstanceState) {
@ -520,135 +521,29 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact
viewModel.setReplyToItem(directItemOrHeader.item); viewModel.setReplyToItem(directItemOrHeader.item);
} }
); );
final ItemTouchHelper itemTouchHelper = new ItemTouchHelper(touchHelperCallback); final Integer inputMode = viewModel.getInputMode().getValue();
if (inputMode != null && inputMode != 1) {
itemTouchHelper = new ItemTouchHelper(touchHelperCallback);
itemTouchHelper.attachToRecyclerView(binding.chats); itemTouchHelper.attachToRecyclerView(binding.chats);
// final MentionClickListener mentionClickListener = (view, text, isHashtag, isLocation) -> searchUsername(text); }
// final DialogInterface.OnClickListener onDialogListener = (dialogInterface, which) -> {
// if (which == 0) {
// final DirectItemType itemType = directItemModel.getItemType();
// switch (itemType) {
// case MEDIA_SHARE:
// case CLIP:
// case FELIX_SHARE:
// final String shortCode = ((DirectItemMediaModel) directItemModel.getMediaModel()).getCode();
// new PostFetcher(shortCode, feedModel -> {
// final PostViewV2Fragment fragment = PostViewV2Fragment
// .builder(feedModel)
// .build();
// fragment.show(getChildFragmentManager(), "post_view");
// }).execute();
// break;
// case LINK:
// Intent linkIntent = new Intent(Intent.ACTION_VIEW);
// linkIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP);
// linkIntent.setData(Uri.parse(((DirectItemLinkModel) directItemModel.getMediaModel()).getLinkContext().getLinkUrl()));
// startActivity(linkIntent);
// break;
// case TEXT:
// case REEL_SHARE:
// Utils.copyText(context, directItemModel.getText());
// Toast.makeText(context, R.string.clipboard_copied, Toast.LENGTH_SHORT).show();
// break;
// case RAVEN_MEDIA:
// case MEDIA:
// downloadItem(context);
// break;
// case STORY_SHARE:
// if (directItemModel.getMediaModel() != null) {
// // StoryModel sm = new StoryModel(
// // directItemModel.getReelShare().getReelId(),
// // directItemModel.getReelShare().getMedia().getVideoUrl(),
// // directItemModel.getReelShare().getMedia().getMediaType(),
// // directItemModel.getTimestamp(),
// // directItemModel.getReelShare().getReelOwnerName(),
// // String.valueOf(directItemModel.getReelShare().getReelOwnerId()),
// // false
// // );
// // sm.setVideoUrl(directItemModel.getReelShare().getMedia().getVideoUrl());
// // StoryModel[] sms = {sm};
// // startActivity(new Intent(getContext(), StoryViewer.class)
// // .putExtra(Constants.EXTRAS_USERNAME, directItemModel.getReelShare().getReelOwnerName())
// // .putExtra(Constants.EXTRAS_STORIES, sms)
// // );
// } else if (directItemModel.getText() != null && directItemModel.getText().toString().contains("@")) {
// searchUsername(directItemModel.getText().toString().split("@")[1].split(" ")[0]);
// }
// break;
// case PLACEHOLDER:
// if (directItemModel.getText().toString().contains("@"))
// searchUsername(directItemModel.getText().toString().split("@")[1].split(" ")[0]);
// break;
// default:
// Log.d(TAG, "unsupported type " + itemType);
// }
// } else if (which == 1) {
// sendText(null, directItemModel.getItemId(), directItemModel.isLiked());
// } else if (which == 2) {
// if (directItemModel == null) {
// Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show();
// } else if (String.valueOf(directItemModel.getUserId()).equals(myId)) {
// new ThreadAction().execute("delete", directItemModel.getItemId());
// } else {
// searchUsername(getUser(directItemModel.getUserId()).getUsername());
// }
// }
// };
// final View.OnClickListener onClickListener = v -> {
// Object tag = v.getTag();
// if (tag instanceof ProfileModel) {
// searchUsername(((ProfileModel) tag).getUsername());
// } else if (tag instanceof DirectItemModel) {
// directItemModel = (DirectItemModel) tag;
// final DirectItemType itemType = directItemModel.getItemType();
// int firstOption = R.string.dms_inbox_raven_message_unknown;
// String[] dialogList;
//
// switch (itemType) {
// case MEDIA_SHARE:
// case CLIP:
// case FELIX_SHARE:
// firstOption = R.string.view_post;
// break;
// case LINK:
// firstOption = R.string.dms_inbox_open_link;
// break;
// case TEXT:
// case REEL_SHARE:
// firstOption = R.string.dms_inbox_copy_text;
// break;
// case RAVEN_MEDIA:
// case MEDIA:
// firstOption = R.string.dms_inbox_download;
// break;
// case STORY_SHARE:
// if (directItemModel.getMediaModel() != null) {
// firstOption = R.string.show_stories;
// } else if (directItemModel.getText() != null && directItemModel.getText().toString().contains("@")) {
// firstOption = R.string.open_profile;
// }
// break;
// case PLACEHOLDER:
// if (directItemModel.getText().toString().contains("@"))
// firstOption = R.string.open_profile;
// break;
// }
//
// dialogList = new String[]{
// getString(firstOption),
// getString(directItemModel.isLiked() ? R.string.dms_inbox_unlike : R.string.dms_inbox_like),
// getString(String.valueOf(directItemModel.getUserId()).equals(myId) ? R.string.dms_inbox_unsend : R.string.dms_inbox_author)
// };
//
// dialogAdapter = new ArrayAdapter<>(context, android.R.layout.simple_list_item_1, dialogList);
//
// new AlertDialog.Builder(context)
// .setAdapter(dialogAdapter, onDialogListener)
// .show();
// }
// };
} }
private void setObservers() { private void setObservers() {
viewModel.getInputMode().observe(getViewLifecycleOwner(), inputMode -> {
if (inputMode == null || inputMode == 0) return;
if (inputMode == 1) {
binding.emojiToggle.setVisibility(View.GONE);
binding.camera.setVisibility(View.GONE);
binding.gallery.setVisibility(View.GONE);
binding.input.setVisibility(View.GONE);
binding.inputBg.setVisibility(View.GONE);
binding.recordView.setVisibility(View.GONE);
binding.send.setVisibility(View.GONE);
if (itemTouchHelper != null) {
itemTouchHelper.attachToRecyclerView(null);
}
}
});
viewModel.getThreadTitle().observe(getViewLifecycleOwner(), this::setTitle); viewModel.getThreadTitle().observe(getViewLifecycleOwner(), this::setTitle);
viewModel.getFetching().observe(getViewLifecycleOwner(), fetching -> { viewModel.getFetching().observe(getViewLifecycleOwner(), fetching -> {
if (fetching) { if (fetching) {
@ -882,6 +777,8 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact
} }
private void setupInput() { private void setupInput() {
final Integer inputMode = viewModel.getInputMode().getValue();
if (inputMode != null && inputMode == 1) return;
final Context context = getContext(); final Context context = getContext();
if (context == null) return; if (context == null) return;
tooltip.setText(HOLD_TO_RECORD_AUDIO_LABEL); tooltip.setText(HOLD_TO_RECORD_AUDIO_LABEL);

View File

@ -127,4 +127,9 @@ public interface DirectMessagesRepository {
@POST("/api/v1/direct_v2/threads/{threadId}/leave/") @POST("/api/v1/direct_v2/threads/{threadId}/leave/")
Call<DirectThreadDetailsChangeResponse> leave(@Path("threadId") String threadId, Call<DirectThreadDetailsChangeResponse> leave(@Path("threadId") String threadId,
@FieldMap final Map<String, String> form); @FieldMap final Map<String, String> form);
@FormUrlEncoded
@POST("/api/v1/direct_v2/threads/{threadId}/remove_all_users/")
Call<DirectThreadDetailsChangeResponse> end(@Path("threadId") String threadId,
@FieldMap final Map<String, String> form);
} }

View File

@ -42,6 +42,7 @@ public class DirectThread implements Serializable {
private final DirectItem lastPermanentItem; private final DirectItem lastPermanentItem;
private final DirectThreadDirectStory directStory; private final DirectThreadDirectStory directStory;
private boolean approvalRequiredForNewMembers; private boolean approvalRequiredForNewMembers;
private int inputMode;
public DirectThread(final String threadId, public DirectThread(final String threadId,
final String threadV2Id, final String threadV2Id,
@ -74,7 +75,8 @@ public class DirectThread implements Serializable {
final boolean isSpam, final boolean isSpam,
final DirectItem lastPermanentItem, final DirectItem lastPermanentItem,
final DirectThreadDirectStory directStory, final DirectThreadDirectStory directStory,
final boolean approvalRequiredForNewMembers) { final boolean approvalRequiredForNewMembers,
final int inputMode) {
this.threadId = threadId; this.threadId = threadId;
this.threadV2Id = threadV2Id; this.threadV2Id = threadV2Id;
this.users = users; this.users = users;
@ -107,6 +109,7 @@ public class DirectThread implements Serializable {
this.lastPermanentItem = lastPermanentItem; this.lastPermanentItem = lastPermanentItem;
this.directStory = directStory; this.directStory = directStory;
this.approvalRequiredForNewMembers = approvalRequiredForNewMembers; this.approvalRequiredForNewMembers = approvalRequiredForNewMembers;
this.inputMode = inputMode;
} }
public String getThreadId() { public String getThreadId() {
@ -249,6 +252,14 @@ public class DirectThread implements Serializable {
this.approvalRequiredForNewMembers = approvalRequiredForNewMembers; this.approvalRequiredForNewMembers = approvalRequiredForNewMembers;
} }
public int getInputMode() {
return inputMode;
}
public void setInputMode(final int inputMode) {
this.inputMode = inputMode;
}
@Nullable @Nullable
public DirectItem getFirstDirectItem() { public DirectItem getFirstDirectItem() {
DirectItem firstItem = null; DirectItem firstItem = null;

View File

@ -2,7 +2,6 @@ package awais.instagrabber.viewmodels;
import android.app.Application; import android.app.Application;
import android.os.AsyncTask; import android.os.AsyncTask;
import android.util.Log;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.lifecycle.AndroidViewModel; import androidx.lifecycle.AndroidViewModel;
@ -35,7 +34,7 @@ public class AppStateViewModel extends AndroidViewModel {
public AppStateViewModel(@NonNull final Application application) { public AppStateViewModel(@NonNull final Application application) {
super(application); super(application);
Log.d(TAG, "AppStateViewModel: constructor"); // Log.d(TAG, "AppStateViewModel: constructor");
cookie = settingsHelper.getString(Constants.COOKIE); cookie = settingsHelper.getString(Constants.COOKIE);
isLoggedIn = !TextUtils.isEmpty(cookie) && CookieUtils.getUserIdFromCookie(cookie) > 0; isLoggedIn = !TextUtils.isEmpty(cookie) && CookieUtils.getUserIdFromCookie(cookie) > 0;
if (!isLoggedIn) return; if (!isLoggedIn) return;

View File

@ -66,6 +66,7 @@ public class DirectSettingsViewModel extends AndroidViewModel {
private final MutableLiveData<Boolean> mentionsMuted = new MutableLiveData<>(false); private final MutableLiveData<Boolean> mentionsMuted = new MutableLiveData<>(false);
private final MutableLiveData<Boolean> approvalRequiredToJoin = new MutableLiveData<>(false); private final MutableLiveData<Boolean> approvalRequiredToJoin = new MutableLiveData<>(false);
private final MutableLiveData<DirectThreadParticipantRequestsResponse> pendingRequests = new MutableLiveData<>(null); private final MutableLiveData<DirectThreadParticipantRequestsResponse> pendingRequests = new MutableLiveData<>(null);
private final MutableLiveData<Integer> inputMode = new MutableLiveData<>(null);
private final DirectMessagesService directMessagesService; private final DirectMessagesService directMessagesService;
private final long userId; private final long userId;
private final Resources resources; private final Resources resources;
@ -97,6 +98,7 @@ public class DirectSettingsViewModel extends AndroidViewModel {
public void setThread(@NonNull final DirectThread thread) { public void setThread(@NonNull final DirectThread thread) {
this.thread = thread; this.thread = thread;
inputMode.postValue(thread.getInputMode());
List<User> users = thread.getUsers(); List<User> users = thread.getUsers();
if (viewer != null) { if (viewer != null) {
final ImmutableList.Builder<User> builder = ImmutableList.<User>builder().add(viewer); final ImmutableList.Builder<User> builder = ImmutableList.<User>builder().add(viewer);
@ -113,11 +115,15 @@ public class DirectSettingsViewModel extends AndroidViewModel {
muted.postValue(thread.isMuted()); muted.postValue(thread.isMuted());
mentionsMuted.postValue(thread.isMentionsMuted()); mentionsMuted.postValue(thread.isMentionsMuted());
approvalRequiredToJoin.postValue(thread.isApprovalRequiredForNewMembers()); approvalRequiredToJoin.postValue(thread.isApprovalRequiredForNewMembers());
if (thread.isGroup() && viewerIsAdmin) { if (thread.getInputMode() != 1 && thread.isGroup() && viewerIsAdmin) {
fetchPendingRequests(); fetchPendingRequests();
} }
} }
public LiveData<Integer> getInputMode() {
return inputMode;
}
public boolean isGroup() { public boolean isGroup() {
if (thread != null) { if (thread != null) {
return thread.isGroup(); return thread.isGroup();
@ -542,8 +548,17 @@ public class DirectSettingsViewModel extends AndroidViewModel {
final MutableLiveData<Resource<Object>> data = new MutableLiveData<>(); final MutableLiveData<Resource<Object>> data = new MutableLiveData<>();
data.postValue(Resource.loading(null)); data.postValue(Resource.loading(null));
final Call<DirectThreadDetailsChangeResponse> request = directMessagesService.leave(thread.getThreadId()); final Call<DirectThreadDetailsChangeResponse> request = directMessagesService.leave(thread.getThreadId());
handleDetailsChangeRequest(data, request, () -> { handleDetailsChangeRequest(data, request);
return data;
}
public LiveData<Resource<Object>> end() {
final MutableLiveData<Resource<Object>> data = new MutableLiveData<>();
data.postValue(Resource.loading(null));
final Call<DirectThreadDetailsChangeResponse> request = directMessagesService.end(thread.getThreadId());
handleDetailsChangeRequest(data, request, () -> {
thread.setInputMode(1);
inputMode.postValue(1);
}); });
return data; return data;
} }

View File

@ -84,6 +84,7 @@ public class DirectThreadViewModel extends AndroidViewModel {
private final MutableLiveData<List<User>> leftUsers = new MutableLiveData<>(new ArrayList<>()); private final MutableLiveData<List<User>> leftUsers = new MutableLiveData<>(new ArrayList<>());
private final MutableLiveData<DirectItem> replyToItem = new MutableLiveData<>(); private final MutableLiveData<DirectItem> replyToItem = new MutableLiveData<>();
private final MutableLiveData<Integer> pendingRequestsCount = new MutableLiveData<>(null); private final MutableLiveData<Integer> pendingRequestsCount = new MutableLiveData<>(null);
private final MutableLiveData<Integer> inputMode = new MutableLiveData<>(0);
private final DirectMessagesService service; private final DirectMessagesService service;
private final ContentResolver contentResolver; private final ContentResolver contentResolver;
@ -335,6 +336,10 @@ public class DirectThreadViewModel extends AndroidViewModel {
return pendingRequestsCount; return pendingRequestsCount;
} }
public LiveData<Integer> getInputMode() {
return inputMode;
}
public void fetchChats() { public void fetchChats() {
final Boolean isFetching = fetching.getValue(); final Boolean isFetching = fetching.getValue();
if ((isFetching != null && isFetching) || !hasOlder) return; if ((isFetching != null && isFetching) || !hasOlder) return;
@ -382,6 +387,7 @@ public class DirectThreadViewModel extends AndroidViewModel {
private void setupThreadInfo(final DirectThread thread) { private void setupThreadInfo(final DirectThread thread) {
if (thread == null) return; if (thread == null) return;
inputMode.postValue(thread.getInputMode());
final List<DirectItem> items = thread.getItems() final List<DirectItem> items = thread.getItems()
.stream() .stream()
.filter(directItem -> directItem.getHideInThread() == 0) .filter(directItem -> directItem.getHideInThread() == 0)
@ -400,7 +406,7 @@ public class DirectThreadViewModel extends AndroidViewModel {
fetching.postValue(false); fetching.postValue(false);
final List<Long> adminUserIds = thread.getAdminUserIds(); final List<Long> adminUserIds = thread.getAdminUserIds();
viewerIsAdmin = adminUserIds.contains(viewerId); viewerIsAdmin = adminUserIds.contains(viewerId);
if (thread.isGroup() && viewerIsAdmin) { if (thread.getInputMode() != 1 && thread.isGroup() && viewerIsAdmin) {
fetchPendingRequests(); fetchPendingRequests();
} }
} }

View File

@ -401,4 +401,12 @@ public class DirectMessagesService extends BaseService {
); );
return repository.leave(threadId, form); return repository.leave(threadId, form);
} }
public Call<DirectThreadDetailsChangeResponse> end(@NonNull final String threadId) {
final ImmutableMap<String, String> form = ImmutableMap.of(
"_csrftoken", csrfToken,
"_uuid", deviceUuid
);
return repository.end(threadId, form);
}
} }

View File

@ -146,9 +146,26 @@
android:text="@string/dms_action_leave" android:text="@string/dms_action_leave"
android:textAppearance="@style/TextAppearance.MaterialComponents.Body1" android:textAppearance="@style/TextAppearance.MaterialComponents.Body1"
android:textColor="@color/red_600" android:textColor="@color/red_600"
app:layout_constraintBottom_toTopOf="@id/pending_members_header" app:layout_constraintBottom_toTopOf="@id/end"
app:layout_constraintTop_toBottomOf="@id/approval_required" /> app:layout_constraintTop_toBottomOf="@id/approval_required" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/end"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:background="?selectableItemBackground"
android:gravity="center_vertical"
android:paddingStart="16dp"
android:paddingTop="12dp"
android:paddingEnd="16dp"
android:paddingBottom="12dp"
android:text="@string/dms_action_end"
android:textAppearance="@style/TextAppearance.MaterialComponents.Body1"
android:textColor="@color/red_600"
app:layout_constraintBottom_toTopOf="@id/pending_members_header"
app:layout_constraintTop_toBottomOf="@id/leave" />
<androidx.appcompat.widget.AppCompatTextView <androidx.appcompat.widget.AppCompatTextView
android:id="@+id/pending_members_header" android:id="@+id/pending_members_header"
android:layout_width="wrap_content" android:layout_width="wrap_content"
@ -163,7 +180,7 @@
android:textStyle="bold" android:textStyle="bold"
app:layout_constraintBottom_toTopOf="@id/pending_members" app:layout_constraintBottom_toTopOf="@id/pending_members"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/leave" /> app:layout_constraintTop_toBottomOf="@id/end" />
<androidx.appcompat.widget.AppCompatTextView <androidx.appcompat.widget.AppCompatTextView
android:id="@+id/pending_members_admin_only" android:id="@+id/pending_members_admin_only"
@ -216,7 +233,7 @@
android:id="@+id/group_settings" android:id="@+id/group_settings"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="0dp" android:layout_height="0dp"
app:constraint_referenced_ids="title_edit_input_layout, mute_mentions_label, mute_mentions, leave, add_members, approval_required, approval_required_label" /> app:constraint_referenced_ids="title_edit_input_layout, mute_mentions_label, mute_mentions, leave, end, add_members, approval_required, approval_required_label" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.appbar.CollapsingToolbarLayout> </com.google.android.material.appbar.CollapsingToolbarLayout>

View File

@ -27,7 +27,7 @@
app:layout_constraintEnd_toEndOf="@id/input_bg" app:layout_constraintEnd_toEndOf="@id/input_bg"
app:layout_constraintStart_toStartOf="@id/input_bg" app:layout_constraintStart_toStartOf="@id/input_bg"
app:layout_constraintTop_toTopOf="@id/reply_info" app:layout_constraintTop_toTopOf="@id/reply_info"
tools:visibility="visible" /> tools:visibility="gone" />
<androidx.appcompat.widget.AppCompatTextView <androidx.appcompat.widget.AppCompatTextView
android:id="@+id/reply_info" android:id="@+id/reply_info"
@ -46,7 +46,7 @@
app:layout_constraintStart_toStartOf="@id/input_bg" app:layout_constraintStart_toStartOf="@id/input_bg"
app:layout_constraintTop_toBottomOf="@id/chats" app:layout_constraintTop_toBottomOf="@id/chats"
tools:text="Replying to yourself" tools:text="Replying to yourself"
tools:visibility="visible" /> tools:visibility="gone" />
<androidx.emoji.widget.EmojiAppCompatTextView <androidx.emoji.widget.EmojiAppCompatTextView
android:id="@+id/reply_preview_text" android:id="@+id/reply_preview_text"
@ -66,7 +66,7 @@
app:layout_constraintTop_toBottomOf="@id/reply_info" app:layout_constraintTop_toBottomOf="@id/reply_info"
app:layout_goneMarginTop="8dp" app:layout_goneMarginTop="8dp"
tools:text="Post" tools:text="Post"
tools:visibility="visible" /> tools:visibility="gone" />
<com.facebook.drawee.view.SimpleDraweeView <com.facebook.drawee.view.SimpleDraweeView
android:id="@+id/reply_preview_image" android:id="@+id/reply_preview_image"
@ -80,7 +80,7 @@
app:layout_constraintStart_toEndOf="@id/reply_preview_text" app:layout_constraintStart_toEndOf="@id/reply_preview_text"
app:layout_constraintTop_toTopOf="@id/reply_info" app:layout_constraintTop_toTopOf="@id/reply_info"
tools:background="@mipmap/ic_launcher" tools:background="@mipmap/ic_launcher"
tools:visibility="visible" /> tools:visibility="gone" />
<androidx.appcompat.widget.AppCompatImageView <androidx.appcompat.widget.AppCompatImageView
android:id="@+id/reply_cancel" android:id="@+id/reply_cancel"
@ -93,7 +93,7 @@
app:layout_constraintStart_toEndOf="@id/reply_preview_image" app:layout_constraintStart_toEndOf="@id/reply_preview_image"
app:layout_constraintTop_toTopOf="@id/reply_info" app:layout_constraintTop_toTopOf="@id/reply_info"
app:srcCompat="@drawable/ic_close_24" app:srcCompat="@drawable/ic_close_24"
tools:visibility="visible" /> tools:visibility="gone" />
<!--<androidx.constraintlayout.widget.Group--> <!--<androidx.constraintlayout.widget.Group-->
<!-- android:id="@+id/reply_group"--> <!-- android:id="@+id/reply_group"-->

View File

@ -43,6 +43,7 @@
<dimen name="reaction_picker_emoji_margin">8dp</dimen> <dimen name="reaction_picker_emoji_margin">8dp</dimen>
<dimen name="reaction_picker_option_height">48dp</dimen> <dimen name="reaction_picker_option_height">48dp</dimen>
<dimen name="reaction_picker_add_padding_adjustment">4dp</dimen> <dimen name="reaction_picker_add_padding_adjustment">4dp</dimen>
<dimen name="dm_item_context_min_width">200dp</dimen>
<dimen name="horizontal_divider_height">1dp</dimen> <dimen name="horizontal_divider_height">1dp</dimen>
</resources> </resources>

View File

@ -415,4 +415,7 @@
<string name="added_by">Added by %s</string> <string name="added_by">Added by %s</string>
<string name="admin_approval_required">Admin approval required</string> <string name="admin_approval_required">Admin approval required</string>
<string name="admin_approval_required_description">An admin approval will be required to add new members to the group</string> <string name="admin_approval_required_description">An admin approval will be required to add new members to the group</string>
<string name="dms_action_end">End chat</string>
<string name="dms_action_end_question">End chat?</string>
<string name="dms_action_end_description">All members will be removed from the group. They will still be able to view the chat history.</string>
</resources> </resources>