package org.thoughtcrime.securesms; import android.app.Activity; import android.content.Intent; import android.graphics.Bitmap; import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.Bundle; import android.util.Log; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.widget.EditText; import android.widget.ImageView; import android.widget.ListView; import android.widget.TextView; import android.widget.Toast; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.loader.app.LoaderManager; import chat.delta.rpc.Rpc; import chat.delta.rpc.RpcException; import com.b44t.messenger.DcChat; import com.b44t.messenger.DcContact; import com.b44t.messenger.DcContext; import com.bumptech.glide.load.engine.DiskCacheStrategy; import com.bumptech.glide.request.target.CustomTarget; import com.bumptech.glide.request.transition.Transition; import java.io.File; import java.util.ArrayList; import java.util.Objects; import org.thoughtcrime.securesms.components.AvatarSelector; import org.thoughtcrime.securesms.connect.DcHelper; import org.thoughtcrime.securesms.contacts.avatars.ResourceContactPhoto; import org.thoughtcrime.securesms.mms.AttachmentManager; import org.thoughtcrime.securesms.mms.GlideApp; import org.thoughtcrime.securesms.permissions.Permissions; import org.thoughtcrime.securesms.profiles.AvatarHelper; import org.thoughtcrime.securesms.scribbles.ScribbleActivity; import org.thoughtcrime.securesms.util.SelectedContactsAdapter; import org.thoughtcrime.securesms.util.SelectedContactsAdapter.ItemClickListener; import org.thoughtcrime.securesms.util.ThemeUtil; import org.thoughtcrime.securesms.util.ViewUtil; public class GroupCreateActivity extends PassphraseRequiredActionBarActivity implements ItemClickListener { private static final String TAG = "GroupCreateActivity"; public static final String EDIT_GROUP_CHAT_ID = "edit_group_chat_id"; public static final String CREATE_BROADCAST = "create_broadcast"; public static final String UNENCRYPTED = "unencrypted"; public static final String CLONE_CHAT_EXTRA = "clone_chat"; private static final int PICK_CONTACT = 1; private static final int REQUEST_CODE_AVATAR = 2759; private DcContext dcContext; private boolean unencrypted; private boolean broadcast; private EditText groupName; private EditText chatDescription; private ListView lv; private ImageView avatar; private Bitmap avatarBmp; private int groupChatId; private boolean isEdit; private boolean avatarChanged; private boolean imageLoaded; private AttachmentManager attachmentManager; @Override protected void onCreate(Bundle state, boolean ready) { dcContext = DcHelper.getContext(this); setContentView(R.layout.group_create_activity); broadcast = getIntent().getBooleanExtra(CREATE_BROADCAST, false); unencrypted = getIntent().getBooleanExtra(UNENCRYPTED, false); Objects.requireNonNull(getSupportActionBar()).setDisplayHomeAsUpEnabled(true); getSupportActionBar().setHomeAsUpIndicator(R.drawable.ic_close_white_24dp); groupChatId = getIntent().getIntExtra(EDIT_GROUP_CHAT_ID, 0); attachmentManager = new AttachmentManager(this, () -> {}); avatarChanged = false; // groupChatId may be set during creation, // so always check isEdit() if (groupChatId != 0) { isEdit = true; DcChat dcChat = dcContext.getChat(groupChatId); broadcast = dcChat.isOutBroadcast(); unencrypted = !dcChat.isEncrypted(); } int chatId = getIntent().getIntExtra(CLONE_CHAT_EXTRA, 0); if (chatId != 0) { DcChat dcChat = dcContext.getChat(chatId); broadcast = dcChat.isOutBroadcast(); unencrypted = !dcChat.isEncrypted(); } initializeResources(); } @Override public void onResume() { super.onResume(); updateViewState(); } @Override protected void onDestroy() { super.onDestroy(); } @SuppressWarnings("ConstantConditions") private void updateViewState() { avatar.setEnabled(true); groupName.setEnabled(true); String title; if (isEdit()) { title = getString(R.string.global_menu_edit_desktop); } else if (broadcast) { title = getString(R.string.new_channel); } else if (unencrypted) { title = getString(R.string.new_email); } else { title = getString(R.string.menu_new_group); } getSupportActionBar().setTitle(title); } private void initializeResources() { lv = ViewUtil.findById(this, R.id.selected_contacts_list); avatar = ViewUtil.findById(this, R.id.avatar); groupName = ViewUtil.findById(this, R.id.group_name); chatDescription = ViewUtil.findById(this, R.id.chat_description); TextView chatHints = ViewUtil.findById(this, R.id.chat_hints); // add padding to avoid content hidden behind system bars ViewUtil.applyWindowInsets(lv, false, false, false, true); // apply padding to root to avoid collision with system bars ViewUtil.applyWindowInsets(findViewById(R.id.root_layout), true, false, true, false); initializeAvatarView(); SelectedContactsAdapter adapter = new SelectedContactsAdapter(this, GlideApp.with(this), broadcast); adapter.setItemClickListener(this); lv.setAdapter(adapter); int chatId = getIntent().getIntExtra(CLONE_CHAT_EXTRA, 0); if (chatId != 0) { DcChat dcChat = dcContext.getChat(chatId); groupName.setText(dcChat.getName()); File file = new File(dcChat.getProfileImage()); if (file.exists()) { setAvatarView(Uri.fromFile(file)); } int[] contactIds = dcContext.getChatContacts(chatId); ArrayList preselectedContactIds = new ArrayList<>(contactIds.length); for (int id : contactIds) { preselectedContactIds.add(id); } adapter.changeData(preselectedContactIds); } else { adapter.changeData(null); } if (broadcast) { groupName.setHint(R.string.channel_name); chatHints.setVisibility(View.VISIBLE); } else if (unencrypted) { avatar.setVisibility(View.GONE); groupName.setHint(R.string.subject); findViewById(R.id.chat_description_container).setVisibility(View.GONE); chatHints.setVisibility(View.GONE); } else { chatHints.setVisibility(View.GONE); } if (isEdit()) { groupName.setText(dcContext.getChat(groupChatId).getName()); lv.setVisibility(View.GONE); Rpc rpc = DcHelper.getRpc(this); try { String description = rpc.getChatDescription(rpc.getSelectedAccountId(), groupChatId); chatDescription.setText(description); } catch (RpcException e) { Log.e(TAG, "RPC error", e); } } } private void initializeAvatarView() { imageLoaded = false; if (groupChatId != 0) { String avatarPath = dcContext.getChat(groupChatId).getProfileImage(); File avatarFile = new File(avatarPath); if (avatarFile.exists()) { imageLoaded = true; GlideApp.with(this).load(avatarFile).circleCrop().into(avatar); } } if (!imageLoaded) { avatar.setImageDrawable( new ResourceContactPhoto(R.drawable.ic_group_white_24dp) .asDrawable(this, ThemeUtil.getDummyContactColor(this))); } avatar.setOnClickListener( view -> new AvatarSelector( this, LoaderManager.getInstance(this), new AvatarSelectedListener(), imageLoaded) .show(this, avatar)); } @Override public boolean onPrepareOptionsMenu(Menu menu) { MenuInflater inflater = this.getMenuInflater(); menu.clear(); inflater.inflate(R.menu.group_create, menu); super.onPrepareOptionsMenu(menu); return true; } @Override public boolean onOptionsItemSelected(@NonNull MenuItem item) { super.onOptionsItemSelected(item); int itemId = item.getItemId(); if (itemId == android.R.id.home) { finish(); return true; } else if (itemId == R.id.menu_create_group) { String groupName = getGroupName(); if (showGroupNameEmptyToast(groupName)) return true; if (groupChatId != 0) { updateGroup(groupName); } else { createGroup(groupName); } return true; } return false; } @Override public void onItemClick(int contactId) { if (contactId == DcContact.DC_CONTACT_ID_ADD_MEMBER) { Intent intent = new Intent(this, ContactMultiSelectionActivity.class); intent.putExtra(ContactSelectionListFragment.SELECT_UNENCRYPTED_EXTRA, unencrypted); ArrayList preselectedContacts = new ArrayList<>(getAdapter().getContacts()); intent.putExtra(ContactSelectionListFragment.PRESELECTED_CONTACTS, preselectedContacts); startActivityForResult(intent, PICK_CONTACT); } } @Override public void onItemDeleteClick(int contactId) { getAdapter().remove(contactId); } private void createGroup(String groupName) { Rpc rpc = DcHelper.getRpc(this); int accId; try { accId = rpc.getSelectedAccountId(); if (broadcast) { groupChatId = rpc.createBroadcast(accId, groupName); } else if (unencrypted) { groupChatId = rpc.createGroupChatUnencrypted(accId, groupName); } else { groupChatId = rpc.createGroupChat(accId, groupName, false); } String description = getChatDescription(); if (!description.isEmpty()) { rpc.setChatDescription(accId, groupChatId, description); } } catch (RpcException e) { Log.e(TAG, "RPC error", e); return; } for (int contactId : getAdapter().getContacts()) { dcContext.addContactToChat(groupChatId, contactId); } if (avatarBmp != null) { AvatarHelper.setGroupAvatar(this, groupChatId, avatarBmp); } attachmentManager.cleanup(); Intent intent = new Intent(this, ConversationActivity.class); intent.putExtra(ConversationActivity.CHAT_ID_EXTRA, groupChatId); startActivity(intent); finish(); } private boolean showGroupNameEmptyToast(String groupName) { if (groupName == null) { Toast.makeText(this, getString(R.string.please_enter_chat_name), Toast.LENGTH_LONG).show(); return true; } return false; } private void updateGroup(String groupName) { if (groupChatId == 0) { return; } dcContext.setChatName(groupChatId, groupName); Rpc rpc = DcHelper.getRpc(this); String description = getChatDescription(); try { rpc.setChatDescription(rpc.getSelectedAccountId(), groupChatId, description); } catch (RpcException e) { Log.e(TAG, "RPC error", e); } if (avatarChanged) AvatarHelper.setGroupAvatar(this, groupChatId, avatarBmp); attachmentManager.cleanup(); Intent intent = new Intent(); intent.putExtra(GroupCreateActivity.EDIT_GROUP_CHAT_ID, groupChatId); setResult(RESULT_OK, intent); finish(); } private SelectedContactsAdapter getAdapter() { return (SelectedContactsAdapter) lv.getAdapter(); } private @Nullable String getGroupName() { String ret = groupName.getText() != null ? groupName.getText().toString() : null; if (ret != null) { ret = ret.trim(); if (ret.isEmpty()) { ret = null; } } return ret; } private String getChatDescription() { return chatDescription.getText() != null ? chatDescription.getText().toString().trim() : ""; } @Override public void onRequestPermissionsResult( int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); Permissions.onRequestPermissionsResult(this, requestCode, permissions, grantResults); } @Override public void onActivityResult(int reqCode, int resultCode, final Intent data) { super.onActivityResult(reqCode, resultCode, data); if (resultCode != Activity.RESULT_OK) return; switch (reqCode) { case REQUEST_CODE_AVATAR: Uri inputFile = (data != null ? data.getData() : null); onFileSelected(inputFile); break; case PICK_CONTACT: ArrayList contactIds = new ArrayList<>(); for (Integer contactId : Objects.requireNonNull( data.getIntegerArrayListExtra(ContactMultiSelectionActivity.CONTACTS_EXTRA))) { if (contactId != null) { contactIds.add(contactId); } } getAdapter().changeData(contactIds); break; case ScribbleActivity.SCRIBBLE_REQUEST_CODE: setAvatarView(data.getData()); break; } } private void setAvatarView(Uri output) { GlideApp.with(this) .asBitmap() .load(output) .skipMemoryCache(true) .diskCacheStrategy(DiskCacheStrategy.NONE) .centerCrop() .override(AvatarHelper.AVATAR_SIZE, AvatarHelper.AVATAR_SIZE) .into( new CustomTarget() { @Override public void onResourceReady( @NonNull Bitmap resource, Transition transition) { setAvatar(output, resource); } @Override public void onLoadCleared(@Nullable Drawable placeholder) {} }); } private void setAvatar(T model, Bitmap bitmap) { avatarBmp = bitmap; avatarChanged = true; imageLoaded = true; GlideApp.with(this) .load(model) .circleCrop() .skipMemoryCache(true) .diskCacheStrategy(DiskCacheStrategy.NONE) .into(avatar); } private boolean isEdit() { return isEdit; } private class AvatarSelectedListener implements AvatarSelector.AttachmentClickedListener { @Override public void onClick(int type) { switch (type) { case AvatarSelector.ADD_GALLERY: AttachmentManager.selectImage(GroupCreateActivity.this, REQUEST_CODE_AVATAR); break; case AvatarSelector.REMOVE_PHOTO: avatarBmp = null; imageLoaded = false; avatarChanged = true; avatar.setImageDrawable( new ResourceContactPhoto(R.drawable.ic_group_white_24dp) .asDrawable( GroupCreateActivity.this, ThemeUtil.getDummyContactColor(GroupCreateActivity.this))); break; case AvatarSelector.TAKE_PHOTO: attachmentManager.capturePhoto(GroupCreateActivity.this, REQUEST_CODE_AVATAR); break; } } @Override public void onQuickAttachment(Uri inputFile) { onFileSelected(inputFile); } } private void onFileSelected(Uri inputFile) { if (inputFile == null) { inputFile = attachmentManager.getImageCaptureUri(); } AvatarHelper.cropAvatar(this, inputFile); } }