diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index a95a9baeb..bc9e379d1 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -206,18 +206,6 @@ android:resource="@mipmap/ic_launcher" /> - - - - - . - */ -package org.thoughtcrime.securesms; - -import android.annotation.SuppressLint; -import android.content.ClipData; -import android.content.ClipboardManager; -import android.content.Context; -import android.database.Cursor; -import android.graphics.drawable.ColorDrawable; -import android.os.AsyncTask; -import android.os.Bundle; -import android.view.LayoutInflater; -import android.view.MenuItem; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ListView; -import android.widget.TextView; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.loader.app.LoaderManager.LoaderCallbacks; -import androidx.loader.content.Loader; -import org.session.libsession.messaging.messages.visible.LinkPreview; -import org.session.libsession.messaging.messages.visible.OpenGroupInvitation; -import org.session.libsession.messaging.messages.visible.Quote; -import org.session.libsession.messaging.messages.visible.VisibleMessage; -import org.session.libsession.messaging.open_groups.OpenGroupV2; -import org.session.libsession.messaging.sending_receiving.MessageSender; -import org.session.libsession.messaging.utilities.UpdateMessageData; -import org.thoughtcrime.securesms.MessageDetailsRecipientAdapter.RecipientDeliveryStatus; -import org.session.libsession.utilities.MaterialColor; -import org.thoughtcrime.securesms.conversation.ConversationItem; -import org.thoughtcrime.securesms.database.DatabaseFactory; -import org.thoughtcrime.securesms.database.GroupReceiptDatabase; -import org.thoughtcrime.securesms.database.GroupReceiptDatabase.GroupReceiptInfo; -import org.thoughtcrime.securesms.database.MmsDatabase; -import org.thoughtcrime.securesms.database.MmsSmsDatabase; -import org.thoughtcrime.securesms.database.SmsDatabase; -import org.thoughtcrime.securesms.database.loaders.MessageDetailsLoader; -import org.thoughtcrime.securesms.database.model.MessageRecord; -import org.session.libsignal.utilities.Log; -import org.thoughtcrime.securesms.database.model.MmsMessageRecord; -import org.thoughtcrime.securesms.loki.database.LokiMessageDatabase; -import org.thoughtcrime.securesms.mms.GlideApp; -import org.thoughtcrime.securesms.mms.GlideRequests; -import org.session.libsession.utilities.recipients.Recipient; -import org.session.libsession.utilities.recipients.RecipientModifiedListener; -import org.thoughtcrime.securesms.util.DateUtils; -import org.session.libsession.utilities.ExpirationUtil; -import org.session.libsession.utilities.Util; -import org.session.libsignal.utilities.guava.Optional; - -import java.lang.ref.WeakReference; -import java.sql.Date; -import java.text.SimpleDateFormat; -import java.util.HashSet; -import java.util.LinkedList; -import java.util.List; -import java.util.Locale; - -import network.loki.messenger.R; - -/** - * @author Jake McGinty - */ -public class MessageDetailsActivity extends PassphraseRequiredActionBarActivity implements LoaderCallbacks, RecipientModifiedListener { - private final static String TAG = MessageDetailsActivity.class.getSimpleName(); - - public final static String MESSAGE_ID_EXTRA = "message_id"; - public final static String THREAD_ID_EXTRA = "thread_id"; - public final static String IS_PUSH_GROUP_EXTRA = "is_push_group"; - public final static String TYPE_EXTRA = "type"; - public final static String ADDRESS_EXTRA = "address"; - - private GlideRequests glideRequests; - private long threadId; - private boolean isPushGroup; - private ConversationItem conversationItem; - private ViewGroup itemParent; - private View metadataContainer; - private View expiresContainer; - private TextView errorText; - private View resendButton; - private TextView sentDate; - private TextView receivedDate; - private TextView expiresInText; - private View receivedContainer; - private TextView transport; - private TextView toFrom; - private View separator; - private ListView recipientsList; - private LayoutInflater inflater; - - private boolean running; - - @Override - public void onCreate(Bundle bundle, boolean ready) { - super.onCreate(bundle, ready); - setContentView(R.layout.message_details_activity); - running = true; - - initializeResources(); - initializeActionBar(); - getSupportLoaderManager().initLoader(0, null, this); - } - - @Override - protected void onResume() { - super.onResume(); - - assert getSupportActionBar() != null; - getSupportActionBar().setTitle("Message Details"); - - ApplicationContext.getInstance(this).messageNotifier.setVisibleThread(threadId); - } - - @Override - protected void onPause() { - super.onPause(); - ApplicationContext.getInstance(this).messageNotifier.setVisibleThread(-1L); - } - - @Override - protected void onDestroy() { - super.onDestroy(); - running = false; - } - - private void initializeActionBar() { - assert getSupportActionBar() != null; - - Recipient recipient = Recipient.from(this, getIntent().getParcelableExtra(ADDRESS_EXTRA), true); - recipient.addListener(this); - } - - private void setActionBarColor(MaterialColor color) { - assert getSupportActionBar() != null; - getSupportActionBar().setBackgroundDrawable(new ColorDrawable(color.toActionBarColor(this))); - } - - @Override - public void onModified(final Recipient recipient) { - Util.runOnMain(() -> setActionBarColor(recipient.getColor())); - } - - private void initializeResources() { - inflater = LayoutInflater.from(this); - View header = inflater.inflate(R.layout.message_details_header, recipientsList, false); - - threadId = getIntent().getLongExtra(THREAD_ID_EXTRA, -1); - isPushGroup = getIntent().getBooleanExtra(IS_PUSH_GROUP_EXTRA, false); - glideRequests = GlideApp.with(this); - itemParent = header.findViewById(R.id.item_container); - recipientsList = findViewById(R.id.recipients_list); - metadataContainer = header.findViewById(R.id.metadata_container); - errorText = header.findViewById(R.id.error_text); - resendButton = header.findViewById(R.id.resend_button); - sentDate = header.findViewById(R.id.sent_time); - receivedContainer = header.findViewById(R.id.received_container); - receivedDate = header.findViewById(R.id.received_time); - transport = header.findViewById(R.id.transport); - toFrom = header.findViewById(R.id.tofrom); - separator = header.findViewById(R.id.separator); - expiresContainer = header.findViewById(R.id.expires_container); - expiresInText = header.findViewById(R.id.expires_in); - recipientsList.setHeaderDividersEnabled(false); - recipientsList.addHeaderView(header, null, false); - } - - private void updateTransport(MessageRecord messageRecord) { - final String transportText; - if (messageRecord.isOutgoing() && messageRecord.isFailed()) { - transportText = "-"; - } else if (messageRecord.isPending()) { - transportText = getString(R.string.ConversationFragment_pending); - } else if (messageRecord.isMms()) { - transportText = getString(R.string.ConversationFragment_mms); - } else { - transportText = getString(R.string.ConversationFragment_sms); - } - - transport.setText(transportText); - } - - private void updateTime(MessageRecord messageRecord) { - sentDate.setOnLongClickListener(null); - receivedDate.setOnLongClickListener(null); - - if (messageRecord.isPending() || messageRecord.isFailed()) { - sentDate.setText("-"); - receivedContainer.setVisibility(View.GONE); - } else { - Locale dateLocale = Locale.getDefault(); - SimpleDateFormat dateFormatter = DateUtils.getDetailedDateFormatter(this, dateLocale); - sentDate.setText(dateFormatter.format(new Date(messageRecord.getDateSent()))); - sentDate.setOnLongClickListener(v -> { - copyToClipboard(String.valueOf(messageRecord.getDateSent())); - return true; - }); - - if (messageRecord.getDateReceived() != messageRecord.getDateSent() && !messageRecord.isOutgoing()) { - receivedDate.setText(dateFormatter.format(new Date(messageRecord.getDateReceived()))); - receivedDate.setOnLongClickListener(v -> { - copyToClipboard(String.valueOf(messageRecord.getDateReceived())); - return true; - }); - receivedContainer.setVisibility(View.VISIBLE); - } else { - receivedContainer.setVisibility(View.GONE); - } - } - } - - private void updateExpirationTime(final MessageRecord messageRecord) { - if (messageRecord.getExpiresIn() <= 0 || messageRecord.getExpireStarted() <= 0) { - expiresContainer.setVisibility(View.GONE); - return; - } - - expiresContainer.setVisibility(View.VISIBLE); - Util.runOnMain(new Runnable() { - @Override - public void run() { - long elapsed = System.currentTimeMillis() - messageRecord.getExpireStarted(); - long remaining = messageRecord.getExpiresIn() - elapsed; - - String duration = ExpirationUtil.getExpirationDisplayValue(MessageDetailsActivity.this, Math.max((int)(remaining / 1000), 1)); - expiresInText.setText(duration); - - if (running) { - Util.runOnMainDelayed(this, 500); - } - } - }); - } - - private void updateRecipients(MessageRecord messageRecord, Recipient recipient, List recipients) { - final int toFromRes; - if (messageRecord.isOutgoing()) { - toFromRes = R.string.message_details_header__to; - } else { - toFromRes = R.string.message_details_header__from; - } - toFrom.setText(toFromRes); - long threadID = messageRecord.getThreadId(); - OpenGroupV2 openGroup = DatabaseFactory.getLokiThreadDatabase(this).getOpenGroupChat(threadID); - if (openGroup != null && messageRecord.isOutgoing()) { - toFrom.setVisibility(View.GONE); - separator.setVisibility(View.GONE); - } - conversationItem.bind(messageRecord, Optional.absent(), Optional.absent(), glideRequests, Locale.getDefault(), new HashSet<>(), recipient, null, false); - recipientsList.setAdapter(new MessageDetailsRecipientAdapter(this, glideRequests, messageRecord, recipients, isPushGroup)); - } - - private void inflateMessageViewIfAbsent(MessageRecord messageRecord) { - if (conversationItem == null) { - if (messageRecord.isOutgoing()) { - conversationItem = (ConversationItem) inflater.inflate(R.layout.conversation_item_sent, itemParent, false); - } else { - conversationItem = (ConversationItem) inflater.inflate(R.layout.conversation_item_received, itemParent, false); - } - itemParent.addView(conversationItem); - } - } - - private @Nullable MessageRecord getMessageRecord(Context context, Cursor cursor, String type) { - switch (type) { - case MmsSmsDatabase.SMS_TRANSPORT: - SmsDatabase smsDatabase = DatabaseFactory.getSmsDatabase(context); - SmsDatabase.Reader reader = smsDatabase.readerFor(cursor); - return reader.getNext(); - case MmsSmsDatabase.MMS_TRANSPORT: - MmsDatabase mmsDatabase = DatabaseFactory.getMmsDatabase(context); - MmsDatabase.Reader mmsReader = mmsDatabase.readerFor(cursor); - return mmsReader.getNext(); - default: - throw new AssertionError("no valid message type specified"); - } - } - - private void copyToClipboard(@NonNull String text) { - ((ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE)).setPrimaryClip(ClipData.newPlainText("text", text)); - } - - @Override - public @NonNull Loader onCreateLoader(int id, Bundle args) { - return new MessageDetailsLoader(this, getIntent().getStringExtra(TYPE_EXTRA), - getIntent().getLongExtra(MESSAGE_ID_EXTRA, -1)); - } - - @Override - public void onLoadFinished(@NonNull Loader loader, Cursor cursor) { - MessageRecord messageRecord = getMessageRecord(this, cursor, getIntent().getStringExtra(TYPE_EXTRA)); - - if (messageRecord == null) { - finish(); - } else { - new MessageRecipientAsyncTask(this, messageRecord).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); - } - } - - @Override - public void onLoaderReset(@NonNull Loader loader) { - recipientsList.setAdapter(null); - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - super.onOptionsItemSelected(item); - - switch (item.getItemId()) { - case android.R.id.home: finish(); return true; - } - - return false; - } - - @SuppressLint("StaticFieldLeak") - private class MessageRecipientAsyncTask extends AsyncTask> { - - private final WeakReference weakContext; - private final MessageRecord messageRecord; - - MessageRecipientAsyncTask(@NonNull Context context, @NonNull MessageRecord messageRecord) { - this.weakContext = new WeakReference<>(context); - this.messageRecord = messageRecord; - } - - protected Context getContext() { - return weakContext.get(); - } - - @Override - public List doInBackground(Void... voids) { - Context context = getContext(); - - if (context == null) { - Log.w(TAG, "associated context is destroyed, finishing early"); - return null; - } - - List recipients = new LinkedList<>(); - - if (!messageRecord.getRecipient().isGroupRecipient()) { - recipients.add(new RecipientDeliveryStatus(messageRecord.getRecipient(), getStatusFor(messageRecord.getDeliveryReceiptCount(), messageRecord.getReadReceiptCount(), messageRecord.isPending()), true, -1)); - } else { - List receiptInfoList = DatabaseFactory.getGroupReceiptDatabase(context).getGroupReceiptInfo(messageRecord.getId()); - - if (receiptInfoList.isEmpty()) { - List group = DatabaseFactory.getGroupDatabase(context).getGroupMembers(messageRecord.getRecipient().getAddress().toGroupString(), false); - - for (Recipient recipient : group) { - recipients.add(new RecipientDeliveryStatus(recipient, RecipientDeliveryStatus.Status.UNKNOWN, false, -1)); - } - } else { - for (GroupReceiptInfo info : receiptInfoList) { - recipients.add(new RecipientDeliveryStatus(Recipient.from(context, info.getAddress(), true), - getStatusFor(info.getStatus(), messageRecord.isPending(), messageRecord.isFailed()), - info.isUnidentified(), - info.getTimestamp())); - } - } - } - - return recipients; - } - - @Override - public void onPostExecute(List recipients) { - if (getContext() == null) { - Log.w(TAG, "AsyncTask finished with a destroyed context, leaving early."); - return; - } - - inflateMessageViewIfAbsent(messageRecord); - updateRecipients(messageRecord, messageRecord.getRecipient(), recipients); - - boolean isGroupNetworkFailure = messageRecord.isFailed() && !messageRecord.getNetworkFailures().isEmpty(); - boolean isIndividualNetworkFailure = messageRecord.isFailed() && !isPushGroup; - - LokiMessageDatabase lokiMessageDatabase = DatabaseFactory.getLokiMessageDatabase(getContext()); - String errorMessage = lokiMessageDatabase.getErrorMessage(messageRecord.id); - if (errorMessage != null) { - errorText.setText(errorMessage); - } - - if (isGroupNetworkFailure || isIndividualNetworkFailure) { - errorText.setVisibility(View.VISIBLE); - resendButton.setVisibility(View.VISIBLE); - resendButton.setOnClickListener(this::onResendClicked); - metadataContainer.setVisibility(View.GONE); - } else if (messageRecord.isFailed()) { - errorText.setVisibility(View.VISIBLE); - resendButton.setVisibility(View.GONE); - resendButton.setOnClickListener(null); - metadataContainer.setVisibility(View.GONE); - } else { - updateTransport(messageRecord); - updateTime(messageRecord); - updateExpirationTime(messageRecord); - errorText.setVisibility(View.GONE); - resendButton.setVisibility(View.GONE); - resendButton.setOnClickListener(null); - metadataContainer.setVisibility(View.VISIBLE); - } - } - - private RecipientDeliveryStatus.Status getStatusFor(int deliveryReceiptCount, int readReceiptCount, boolean pending) { - if (readReceiptCount > 0) return RecipientDeliveryStatus.Status.READ; - else if (deliveryReceiptCount > 0) return RecipientDeliveryStatus.Status.DELIVERED; - else if (!pending) return RecipientDeliveryStatus.Status.SENT; - else return RecipientDeliveryStatus.Status.PENDING; - } - - private RecipientDeliveryStatus.Status getStatusFor(int groupStatus, boolean pending, boolean failed) { - if (groupStatus == GroupReceiptDatabase.STATUS_READ) return RecipientDeliveryStatus.Status.READ; - else if (groupStatus == GroupReceiptDatabase.STATUS_DELIVERED) return RecipientDeliveryStatus.Status.DELIVERED; - else if (groupStatus == GroupReceiptDatabase.STATUS_UNDELIVERED && failed) return RecipientDeliveryStatus.Status.UNKNOWN; - else if (groupStatus == GroupReceiptDatabase.STATUS_UNDELIVERED && !pending) return RecipientDeliveryStatus.Status.SENT; - else if (groupStatus == GroupReceiptDatabase.STATUS_UNDELIVERED) return RecipientDeliveryStatus.Status.PENDING; - else if (groupStatus == GroupReceiptDatabase.STATUS_UNKNOWN) return RecipientDeliveryStatus.Status.UNKNOWN; - throw new AssertionError(); - } - - private void onResendClicked(View v) { - Recipient recipient = messageRecord.getRecipient(); - VisibleMessage message = new VisibleMessage(); - message.setId(messageRecord.getId()); - if (messageRecord.isOpenGroupInvitation()) { - OpenGroupInvitation openGroupInvitation = new OpenGroupInvitation(); - UpdateMessageData updateMessageData = UpdateMessageData.Companion.fromJSON(messageRecord.getBody()); - if (updateMessageData.getKind() instanceof UpdateMessageData.Kind.OpenGroupInvitation) { - UpdateMessageData.Kind.OpenGroupInvitation data = (UpdateMessageData.Kind.OpenGroupInvitation)updateMessageData.getKind(); - openGroupInvitation.setName(data.getGroupName()); - openGroupInvitation.setUrl(data.getGroupUrl()); - } - message.setOpenGroupInvitation(openGroupInvitation); - } else { - message.setText(messageRecord.getBody()); - } - message.setSentTimestamp(messageRecord.getTimestamp()); - if (recipient.isGroupRecipient()) { - message.setGroupPublicKey(recipient.getAddress().toGroupString()); - } else { - message.setRecipient(messageRecord.getRecipient().getAddress().serialize()); - } - message.setThreadID(messageRecord.getThreadId()); - if (messageRecord.isMms()) { - MmsMessageRecord mmsMessageRecord = (MmsMessageRecord) messageRecord; - if (!mmsMessageRecord.getLinkPreviews().isEmpty()) { - message.setLinkPreview(LinkPreview.Companion.from(mmsMessageRecord.getLinkPreviews().get(0))); - } - if (mmsMessageRecord.getQuote() != null) { - message.setQuote(Quote.Companion.from(mmsMessageRecord.getQuote().getQuoteModel())); - } - message.addSignalAttachments(mmsMessageRecord.getSlideDeck().asAttachments()); - } - MessageSender.send(message, recipient.getAddress()); - resendButton.setVisibility(View.GONE); - } - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/ShareActivity.java b/app/src/main/java/org/thoughtcrime/securesms/ShareActivity.java index 8f820cba5..ba7e145bf 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/ShareActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/ShareActivity.java @@ -37,7 +37,7 @@ import androidx.appcompat.widget.Toolbar; import org.session.libsession.utilities.DistributionTypes; import org.thoughtcrime.securesms.components.SearchToolbar; -import org.thoughtcrime.securesms.conversation.ConversationActivity; + import org.session.libsession.utilities.Address; import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2; import org.thoughtcrime.securesms.database.DatabaseFactory; @@ -219,7 +219,6 @@ public class ShareActivity extends PassphraseRequiredActionBarActivity final Intent intent = getBaseShareIntent(ConversationActivityV2.class); intent.putExtra(ConversationActivityV2.ADDRESS, address); intent.putExtra(ConversationActivityV2.THREAD_ID, threadId); - intent.putExtra(ConversationActivity.DISTRIBUTION_TYPE_EXTRA, distributionType); isPassingAlongMedia = true; startActivity(intent); @@ -227,11 +226,6 @@ public class ShareActivity extends PassphraseRequiredActionBarActivity private Intent getBaseShareIntent(final @NonNull Class target) { final Intent intent = new Intent(this, target); - final String textExtra = getIntent().getStringExtra(Intent.EXTRA_TEXT); - final ArrayList mediaExtra = getIntent().getParcelableArrayListExtra(ConversationActivity.MEDIA_EXTRA); - - intent.putExtra(ConversationActivity.TEXT_EXTRA, textExtra); - intent.putExtra(ConversationActivity.MEDIA_EXTRA, mediaExtra); if (resolvedExtra != null) intent.setDataAndType(resolvedExtra, mimeType); diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/ComposeText.java b/app/src/main/java/org/thoughtcrime/securesms/components/ComposeText.java index 012086dc9..42825360c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/ComposeText.java +++ b/app/src/main/java/org/thoughtcrime/securesms/components/ComposeText.java @@ -28,166 +28,166 @@ import org.session.libsession.utilities.TextSecurePreferences; public class ComposeText extends EmojiEditText { - private CharSequence hint; - private SpannableString subHint; + private CharSequence hint; + private SpannableString subHint; - @Nullable private InputPanel.MediaListener mediaListener; - @Nullable private CursorPositionChangedListener cursorPositionChangedListener; + @Nullable private InputPanel.MediaListener mediaListener; + @Nullable private CursorPositionChangedListener cursorPositionChangedListener; - public ComposeText(Context context) { - super(context); - initialize(); - } - - public ComposeText(Context context, AttributeSet attrs) { - super(context, attrs); - initialize(); - } - - public ComposeText(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - initialize(); - } - - public String getTextTrimmed(){ - return getText().toString().trim(); - } - - @Override - protected void onLayout(boolean changed, int left, int top, int right, int bottom) { - super.onLayout(changed, left, top, right, bottom); - - if (!TextUtils.isEmpty(hint)) { - if (!TextUtils.isEmpty(subHint)) { - setHint(new SpannableStringBuilder().append(ellipsizeToWidth(hint)) - .append("\n") - .append(ellipsizeToWidth(subHint))); - } else { - setHint(ellipsizeToWidth(hint)); - } - } - } - - @Override - protected void onSelectionChanged(int selStart, int selEnd) { - super.onSelectionChanged(selStart, selEnd); - - if (cursorPositionChangedListener != null) { - cursorPositionChangedListener.onCursorPositionChanged(selStart, selEnd); - } - } - - private CharSequence ellipsizeToWidth(CharSequence text) { - return TextUtils.ellipsize(text, - getPaint(), - getWidth() - getPaddingLeft() - getPaddingRight(), - TruncateAt.END); - } - - public void setHint(@NonNull String hint, @Nullable CharSequence subHint) { - this.hint = hint; - - if (subHint != null) { - this.subHint = new SpannableString(subHint); - this.subHint.setSpan(new RelativeSizeSpan(0.5f), 0, subHint.length(), Spannable.SPAN_INCLUSIVE_INCLUSIVE); - } else { - this.subHint = null; + public ComposeText(Context context) { + super(context); + initialize(); } - if (this.subHint != null) { - super.setHint(new SpannableStringBuilder().append(ellipsizeToWidth(this.hint)) - .append("\n") - .append(ellipsizeToWidth(this.subHint))); - } else { - super.setHint(ellipsizeToWidth(this.hint)); - } - } - - public void setCursorPositionChangedListener(@Nullable CursorPositionChangedListener listener) { - this.cursorPositionChangedListener = listener; - } - - public void setTransport() { - final boolean useSystemEmoji = TextSecurePreferences.isSystemEmojiPreferred(getContext()); - final boolean isIncognito = TextSecurePreferences.isIncognitoKeyboardEnabled(getContext()); - - int imeOptions = (getImeOptions() & ~EditorInfo.IME_MASK_ACTION) | EditorInfo.IME_ACTION_SEND; - int inputType = getInputType(); - - setImeActionLabel(null, 0); - - if (useSystemEmoji) { - inputType = (inputType & ~InputType.TYPE_MASK_VARIATION) | InputType.TYPE_TEXT_VARIATION_SHORT_MESSAGE; + public ComposeText(Context context, AttributeSet attrs) { + super(context, attrs); + initialize(); } - setInputType(inputType); - if (isIncognito) { - setImeOptions(imeOptions | 16777216); - } else { - setImeOptions(imeOptions); - } - } - - @Override - public InputConnection onCreateInputConnection(EditorInfo editorInfo) { - InputConnection inputConnection = super.onCreateInputConnection(editorInfo); - - if(TextSecurePreferences.isEnterSendsEnabled(getContext())) { - editorInfo.imeOptions &= ~EditorInfo.IME_FLAG_NO_ENTER_ACTION; + public ComposeText(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + initialize(); } - if (Build.VERSION.SDK_INT < 21) return inputConnection; - if (mediaListener == null) return inputConnection; - if (inputConnection == null) return null; - - EditorInfoCompat.setContentMimeTypes(editorInfo, new String[] {"image/jpeg", "image/png", "image/gif"}); - return InputConnectionCompat.createWrapper(inputConnection, editorInfo, new CommitContentListener(mediaListener)); - } - - public void setMediaListener(@Nullable InputPanel.MediaListener mediaListener) { - this.mediaListener = mediaListener; - } - - private void initialize() { - if (TextSecurePreferences.isIncognitoKeyboardEnabled(getContext())) { - setImeOptions(getImeOptions() | 16777216); - } - } - - @RequiresApi(api = Build.VERSION_CODES.HONEYCOMB_MR2) - private static class CommitContentListener implements InputConnectionCompat.OnCommitContentListener { - - private static final String TAG = CommitContentListener.class.getSimpleName(); - - private final InputPanel.MediaListener mediaListener; - - private CommitContentListener(@NonNull InputPanel.MediaListener mediaListener) { - this.mediaListener = mediaListener; + public String getTextTrimmed(){ + return getText().toString().trim(); } @Override - public boolean onCommitContent(InputContentInfoCompat inputContentInfo, int flags, Bundle opts) { - if (BuildCompat.isAtLeastNMR1() && (flags & InputConnectionCompat.INPUT_CONTENT_GRANT_READ_URI_PERMISSION) != 0) { - try { - inputContentInfo.requestPermission(); - } catch (Exception e) { - Log.w(TAG, e); - return false; + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + super.onLayout(changed, left, top, right, bottom); + + if (!TextUtils.isEmpty(hint)) { + if (!TextUtils.isEmpty(subHint)) { + setHint(new SpannableStringBuilder().append(ellipsizeToWidth(hint)) + .append("\n") + .append(ellipsizeToWidth(subHint))); + } else { + setHint(ellipsizeToWidth(hint)); + } } - } - - if (inputContentInfo.getDescription().getMimeTypeCount() > 0) { - mediaListener.onMediaSelected(inputContentInfo.getContentUri(), - inputContentInfo.getDescription().getMimeType(0)); - - return true; - } - - return false; } - } - public interface CursorPositionChangedListener { - void onCursorPositionChanged(int start, int end); - } + @Override + protected void onSelectionChanged(int selStart, int selEnd) { + super.onSelectionChanged(selStart, selEnd); + + if (cursorPositionChangedListener != null) { + cursorPositionChangedListener.onCursorPositionChanged(selStart, selEnd); + } + } + + private CharSequence ellipsizeToWidth(CharSequence text) { + return TextUtils.ellipsize(text, + getPaint(), + getWidth() - getPaddingLeft() - getPaddingRight(), + TruncateAt.END); + } + + public void setHint(@NonNull String hint, @Nullable CharSequence subHint) { + this.hint = hint; + + if (subHint != null) { + this.subHint = new SpannableString(subHint); + this.subHint.setSpan(new RelativeSizeSpan(0.5f), 0, subHint.length(), Spannable.SPAN_INCLUSIVE_INCLUSIVE); + } else { + this.subHint = null; + } + + if (this.subHint != null) { + super.setHint(new SpannableStringBuilder().append(ellipsizeToWidth(this.hint)) + .append("\n") + .append(ellipsizeToWidth(this.subHint))); + } else { + super.setHint(ellipsizeToWidth(this.hint)); + } + } + + public void setCursorPositionChangedListener(@Nullable CursorPositionChangedListener listener) { + this.cursorPositionChangedListener = listener; + } + + public void setTransport() { + final boolean useSystemEmoji = TextSecurePreferences.isSystemEmojiPreferred(getContext()); + final boolean isIncognito = TextSecurePreferences.isIncognitoKeyboardEnabled(getContext()); + + int imeOptions = (getImeOptions() & ~EditorInfo.IME_MASK_ACTION) | EditorInfo.IME_ACTION_SEND; + int inputType = getInputType(); + + setImeActionLabel(null, 0); + + if (useSystemEmoji) { + inputType = (inputType & ~InputType.TYPE_MASK_VARIATION) | InputType.TYPE_TEXT_VARIATION_SHORT_MESSAGE; + } + + setInputType(inputType); + if (isIncognito) { + setImeOptions(imeOptions | 16777216); + } else { + setImeOptions(imeOptions); + } + } + + @Override + public InputConnection onCreateInputConnection(EditorInfo editorInfo) { + InputConnection inputConnection = super.onCreateInputConnection(editorInfo); + + if(TextSecurePreferences.isEnterSendsEnabled(getContext())) { + editorInfo.imeOptions &= ~EditorInfo.IME_FLAG_NO_ENTER_ACTION; + } + + if (Build.VERSION.SDK_INT < 21) return inputConnection; + if (mediaListener == null) return inputConnection; + if (inputConnection == null) return null; + + EditorInfoCompat.setContentMimeTypes(editorInfo, new String[] {"image/jpeg", "image/png", "image/gif"}); + return InputConnectionCompat.createWrapper(inputConnection, editorInfo, new CommitContentListener(mediaListener)); + } + + public void setMediaListener(@Nullable InputPanel.MediaListener mediaListener) { + this.mediaListener = mediaListener; + } + + private void initialize() { + if (TextSecurePreferences.isIncognitoKeyboardEnabled(getContext())) { + setImeOptions(getImeOptions() | 16777216); + } + } + + @RequiresApi(api = Build.VERSION_CODES.HONEYCOMB_MR2) + private static class CommitContentListener implements InputConnectionCompat.OnCommitContentListener { + + private static final String TAG = CommitContentListener.class.getSimpleName(); + + private final InputPanel.MediaListener mediaListener; + + private CommitContentListener(@NonNull InputPanel.MediaListener mediaListener) { + this.mediaListener = mediaListener; + } + + @Override + public boolean onCommitContent(InputContentInfoCompat inputContentInfo, int flags, Bundle opts) { + if (BuildCompat.isAtLeastNMR1() && (flags & InputConnectionCompat.INPUT_CONTENT_GRANT_READ_URI_PERMISSION) != 0) { + try { + inputContentInfo.requestPermission(); + } catch (Exception e) { + Log.w(TAG, e); + return false; + } + } + + if (inputContentInfo.getDescription().getMimeTypeCount() > 0) { + mediaListener.onMediaSelected(inputContentInfo.getContentUri(), + inputContentInfo.getDescription().getMimeType(0)); + + return true; + } + + return false; + } + } + + public interface CursorPositionChangedListener { + void onCursorPositionChanged(int start, int end); + } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/ConversationSearchBottomBar.java b/app/src/main/java/org/thoughtcrime/securesms/components/ConversationSearchBottomBar.java deleted file mode 100644 index 603c4869a..000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/components/ConversationSearchBottomBar.java +++ /dev/null @@ -1,88 +0,0 @@ -package org.thoughtcrime.securesms.components; - -import android.content.Context; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.constraintlayout.widget.ConstraintLayout; -import android.util.AttributeSet; -import android.view.View; -import android.widget.TextView; - -import network.loki.messenger.R; - -/** - * Bottom navigation bar shown in the {@link org.thoughtcrime.securesms.conversation.ConversationActivity} - * when the user is searching within a conversation. Shows details about the results and allows the - * user to move between them. - */ -public class ConversationSearchBottomBar extends ConstraintLayout { - - private View searchDown; - private View searchUp; - private TextView searchPositionText; - private View progressWheel; - - private EventListener eventListener; - - - public ConversationSearchBottomBar(Context context) { - super(context); - } - - public ConversationSearchBottomBar(Context context, AttributeSet attrs) { - super(context, attrs); - } - - @Override - protected void onFinishInflate() { - super.onFinishInflate(); - - this.searchUp = findViewById(R.id.conversation_search_up); - this.searchDown = findViewById(R.id.conversation_search_down); - this.searchPositionText = findViewById(R.id.conversation_search_position); - this.progressWheel = findViewById(R.id.conversation_search_progress_wheel); - } - - public void setData(int position, int count) { - progressWheel.setVisibility(GONE); - - searchUp.setOnClickListener(v -> { - if (eventListener != null) { - eventListener.onSearchMoveUpPressed(); - } - }); - - searchDown.setOnClickListener(v -> { - if (eventListener != null) { - eventListener.onSearchMoveDownPressed(); - } - }); - - if (count > 0) { - searchPositionText.setText(getResources().getString(R.string.ConversationActivity_search_position, position + 1, count)); - } else { - searchPositionText.setText(R.string.ConversationActivity_no_results); - } - - setViewEnabled(searchUp, position < (count - 1)); - setViewEnabled(searchDown, position > 0); - } - - public void showLoading() { - progressWheel.setVisibility(VISIBLE); - } - - private void setViewEnabled(@NonNull View view, boolean enabled) { - view.setEnabled(enabled); - view.setAlpha(enabled ? 1f : 0.25f); - } - - public void setEventListener(@Nullable EventListener eventListener) { - this.eventListener = eventListener; - } - - public interface EventListener { - void onSearchMoveUpPressed(); - void onSearchMoveDownPressed(); - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/ConversationTypingView.java b/app/src/main/java/org/thoughtcrime/securesms/components/ConversationTypingView.java deleted file mode 100644 index ddc782628..000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/components/ConversationTypingView.java +++ /dev/null @@ -1,61 +0,0 @@ -package org.thoughtcrime.securesms.components; - -import android.content.Context; -import android.graphics.PorterDuff; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import android.util.AttributeSet; -import android.view.View; -import android.widget.LinearLayout; - -import org.thoughtcrime.securesms.conversation.v2.components.TypingIndicatorView; -import org.thoughtcrime.securesms.mms.GlideRequests; -import org.session.libsession.utilities.recipients.Recipient; -import org.session.libsession.utilities.ThemeUtil; - - -import java.util.List; - -import network.loki.messenger.R; - -public class ConversationTypingView extends LinearLayout { - - private AvatarImageView avatar; - private View bubble; - private TypingIndicatorView indicator; - - public ConversationTypingView(Context context, @Nullable AttributeSet attrs) { - super(context, attrs); - } - - @Override - protected void onFinishInflate() { - super.onFinishInflate(); - - avatar = findViewById(R.id.typing_avatar); - bubble = findViewById(R.id.typing_bubble); - indicator = findViewById(R.id.typing_indicator); - } - - public void setTypists(@NonNull GlideRequests glideRequests, @NonNull List typists, boolean isGroupThread) { - if (typists.isEmpty()) { - indicator.stopAnimation(); - return; - } - - Recipient typist = typists.get(0); - - bubble.getBackground().setColorFilter( - ThemeUtil.getThemedColor(getContext(), R.attr.message_received_background_color), - PorterDuff.Mode.MULTIPLY); - - if (isGroupThread) { - avatar.setAvatar(glideRequests, typist, false); - avatar.setVisibility(VISIBLE); - } else { - avatar.setVisibility(GONE); - } - - indicator.startAnimation(); - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/InputPanel.java b/app/src/main/java/org/thoughtcrime/securesms/components/InputPanel.java index 9b05c269a..e81757026 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/InputPanel.java +++ b/app/src/main/java/org/thoughtcrime/securesms/components/InputPanel.java @@ -4,443 +4,26 @@ import android.annotation.TargetApi; import android.content.Context; import android.net.Uri; import android.os.Build; -import androidx.annotation.DimenRes; -import androidx.annotation.MainThread; import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.core.view.ViewCompat; -import android.text.format.DateUtils; import android.util.AttributeSet; -import android.view.KeyEvent; -import android.view.View; -import android.view.animation.AlphaAnimation; -import android.view.animation.Animation; -import android.view.animation.AnimationSet; -import android.view.animation.Interpolator; -import android.view.animation.TranslateAnimation; import android.widget.LinearLayout; -import android.widget.TextView; -import android.widget.Toast; -import org.thoughtcrime.securesms.components.emoji.EmojiKeyboardProvider; -import org.thoughtcrime.securesms.components.emoji.EmojiToggle; -import org.thoughtcrime.securesms.components.emoji.MediaKeyboard; -import org.session.libsignal.utilities.Log; -import org.thoughtcrime.securesms.loki.utilities.MentionUtilities; -import org.thoughtcrime.securesms.mms.GlideRequests; -import org.thoughtcrime.securesms.mms.SlideDeck; +public class InputPanel extends LinearLayout { -import org.session.libsession.messaging.sending_receiving.link_preview.LinkPreview; -import org.session.libsession.messaging.sending_receiving.quotes.QuoteModel; -import org.session.libsession.utilities.recipients.Recipient; -import org.session.libsession.utilities.TextSecurePreferences; -import org.session.libsession.utilities.Util; -import org.session.libsession.utilities.ViewUtil; -import org.session.libsession.utilities.concurrent.AssertedSuccessListener; -import org.session.libsignal.utilities.ListenableFuture; -import org.session.libsignal.utilities.SettableFuture; -import org.session.libsignal.utilities.guava.Optional; - -import java.util.concurrent.TimeUnit; - -import network.loki.messenger.R; - -public class InputPanel extends LinearLayout - implements MicrophoneRecorderView.Listener, - KeyboardAwareLinearLayout.OnKeyboardShownListener, - EmojiKeyboardProvider.EmojiEventListener -{ - - private static final String TAG = InputPanel.class.getSimpleName(); - - private static final int FADE_TIME = 150; - - private QuoteView quoteView; - private LinkPreviewView linkPreview; - private EmojiToggle mediaKeyboard; - public ComposeText composeText; - private View quickCameraToggle; - private View quickAudioToggle; - private View buttonToggle; - private View recordingContainer; - private View recordLockCancel; - - private MicrophoneRecorderView microphoneRecorderView; - private SlideToCancel slideToCancel; - private RecordTime recordTime; - - private @Nullable Listener listener; - private boolean emojiVisible; - - public InputPanel(Context context) { - super(context); - } - - public InputPanel(Context context, AttributeSet attrs) { - super(context, attrs); - } - - @TargetApi(Build.VERSION_CODES.HONEYCOMB) - public InputPanel(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - } - - @Override - public void onFinishInflate() { - super.onFinishInflate(); - - View quoteDismiss = findViewById(R.id.quote_dismiss); - - this.quoteView = findViewById(R.id.quote_view); - this.linkPreview = findViewById(R.id.link_preview); - this.mediaKeyboard = findViewById(R.id.emoji_toggle); - this.composeText = findViewById(R.id.embedded_text_editor); - this.quickCameraToggle = findViewById(R.id.quick_camera_toggle); - this.quickAudioToggle = findViewById(R.id.quick_audio_toggle); - this.buttonToggle = findViewById(R.id.button_toggle); - this.recordingContainer = findViewById(R.id.recording_container); - this.recordLockCancel = findViewById(R.id.record_cancel); - View slideToCancelView = findViewById(R.id.slide_to_cancel); - this.slideToCancel = new SlideToCancel(slideToCancelView); - this.microphoneRecorderView = findViewById(R.id.recorder_view); - this.microphoneRecorderView.setListener(this); - this.recordTime = new RecordTime(findViewById(R.id.record_time), - findViewById(R.id.microphone), - TimeUnit.HOURS.toSeconds(1), - () -> microphoneRecorderView.cancelAction()); - - this.recordLockCancel.setOnClickListener(v -> microphoneRecorderView.cancelAction()); - - if (TextSecurePreferences.isSystemEmojiPreferred(getContext())) { - mediaKeyboard.setVisibility(View.GONE); - emojiVisible = false; - } else { - mediaKeyboard.setVisibility(View.VISIBLE); - emojiVisible = true; + public InputPanel(Context context) { + super(context); } - quoteDismiss.setOnClickListener(v -> clearQuote()); - - linkPreview.setCloseClickedListener(() -> { - if (listener != null) { - listener.onLinkPreviewCanceled(); - } - }); - } - - public void setListener(final @NonNull Listener listener) { - this.listener = listener; - - mediaKeyboard.setOnClickListener(v -> listener.onEmojiToggle()); - } - - public void setMediaListener(@NonNull MediaListener listener) { - composeText.setMediaListener(listener); - } - - public void setQuote(@NonNull GlideRequests glideRequests, long id, @NonNull Recipient author, @NonNull String body, @NonNull SlideDeck attachments, @NonNull Recipient conversationRecipient, long threadID) { - this.quoteView.setQuote(glideRequests, id, author, MentionUtilities.highlightMentions(body, threadID, getContext()), false, attachments, conversationRecipient); - this.quoteView.setVisibility(View.VISIBLE); - - if (this.linkPreview.getVisibility() == View.VISIBLE) { - int cornerRadius = readDimen(R.dimen.message_corner_collapse_radius); - this.linkPreview.setCorners(cornerRadius, cornerRadius); - } - } - - public void clearQuote() { - this.quoteView.dismiss(); - - if (this.linkPreview.getVisibility() == View.VISIBLE) { - int cornerRadius = readDimen(R.dimen.message_corner_radius); - this.linkPreview.setCorners(cornerRadius, cornerRadius); - } - } - - public Optional getQuote() { - if (quoteView.getQuoteId() > 0 && quoteView.getVisibility() == View.VISIBLE) { - return Optional.of(new QuoteModel(quoteView.getQuoteId(), quoteView.getAuthor().getAddress(), quoteView.getBody(), false, quoteView.getAttachments())); - } else { - return Optional.absent(); - } - } - - public void setLinkPreviewLoading() { - this.linkPreview.setVisibility(View.VISIBLE); - this.linkPreview.setLoading(); - } - - public void setLinkPreview(@NonNull GlideRequests glideRequests, @NonNull Optional preview) { - if (preview.isPresent()) { - this.linkPreview.setVisibility(View.VISIBLE); - this.linkPreview.setLinkPreview(glideRequests, preview.get(), true); - } else { - this.linkPreview.setVisibility(View.GONE); + public InputPanel(Context context, AttributeSet attrs) { + super(context, attrs); } - int largeCornerRadius = (int)(16 * getResources().getDisplayMetrics().density); - int cornerRadius = quoteView.getVisibility() == VISIBLE ? readDimen(R.dimen.message_corner_collapse_radius) : largeCornerRadius; - - this.linkPreview.setCorners(cornerRadius, cornerRadius); - } - - public void setMediaKeyboard(@NonNull MediaKeyboard mediaKeyboard) { - this.mediaKeyboard.attach(mediaKeyboard); - } - - @Override - public void onRecordPermissionRequired() { - if (listener != null) listener.onRecorderPermissionRequired(); - } - - @Override - public void onRecordPressed() { - if (listener != null) listener.onRecorderStarted(); - recordTime.display(); - slideToCancel.display(); - - if (emojiVisible) ViewUtil.fadeOut(mediaKeyboard, FADE_TIME, View.INVISIBLE); - ViewUtil.fadeOut(composeText, FADE_TIME, View.INVISIBLE); - ViewUtil.fadeOut(quickCameraToggle, FADE_TIME, View.INVISIBLE); - ViewUtil.fadeOut(quickAudioToggle, FADE_TIME, View.INVISIBLE); - buttonToggle.animate().alpha(0).setDuration(FADE_TIME).start(); - } - - @Override - public void onRecordReleased() { - long elapsedTime = onRecordHideEvent(); - - if (listener != null) { - Log.d(TAG, "Elapsed time: " + elapsedTime); - if (elapsedTime > 1000) { - listener.onRecorderFinished(); - } else { - Toast.makeText(getContext(), R.string.InputPanel_tap_and_hold_to_record_a_voice_message_release_to_send, Toast.LENGTH_LONG).show(); - listener.onRecorderCanceled(); - } - } - } - - @Override - public void onRecordMoved(float offsetX, float absoluteX) { - slideToCancel.moveTo(offsetX); - - int direction = ViewCompat.getLayoutDirection(this); - float position = absoluteX / recordingContainer.getWidth(); - - if (direction == ViewCompat.LAYOUT_DIRECTION_LTR && position <= 0.5 || - direction == ViewCompat.LAYOUT_DIRECTION_RTL && position >= 0.6) - { - this.microphoneRecorderView.cancelAction(); - } - } - - @Override - public void onRecordCanceled() { - onRecordHideEvent(); - if (listener != null) listener.onRecorderCanceled(); - } - - @Override - public void onRecordLocked() { - slideToCancel.hide(); - recordLockCancel.setVisibility(View.VISIBLE); - buttonToggle.animate().alpha(1).setDuration(FADE_TIME).start(); - if (listener != null) listener.onRecorderLocked(); - } - - public void onPause() { - this.microphoneRecorderView.cancelAction(); - } - - public void setEnabled(boolean enabled) { - composeText.setEnabled(enabled); - mediaKeyboard.setEnabled(enabled); - quickAudioToggle.setEnabled(enabled); - quickCameraToggle.setEnabled(enabled); - } - - public void setHint(@NonNull String hint) { - composeText.setHint(hint, null); - } - - private long onRecordHideEvent() { - recordLockCancel.setVisibility(View.GONE); - - ListenableFuture future = slideToCancel.hide(); - long elapsedTime = recordTime.hide(); - - future.addListener(new AssertedSuccessListener() { - @Override - public void onSuccess(Void result) { - if (emojiVisible) ViewUtil.fadeIn(mediaKeyboard, FADE_TIME); - ViewUtil.fadeIn(composeText, FADE_TIME); - ViewUtil.fadeIn(quickCameraToggle, FADE_TIME); - ViewUtil.fadeIn(quickAudioToggle, FADE_TIME); - buttonToggle.animate().alpha(1).setDuration(FADE_TIME).start(); - } - }); - - return elapsedTime; - } - - @Override - public void onKeyboardShown() { - mediaKeyboard.setToMedia(); - } - - @Override - public void onKeyEvent(KeyEvent keyEvent) { - composeText.dispatchKeyEvent(keyEvent); - } - - @Override - public void onEmojiSelected(String emoji) { - composeText.insertEmoji(emoji); - } - - private int readDimen(@DimenRes int dimenRes) { - return getResources().getDimensionPixelSize(dimenRes); - } - - public boolean isRecordingInLockedMode() { - return microphoneRecorderView.isRecordingLocked(); - } - - public void releaseRecordingLock() { - microphoneRecorderView.unlockAction(); - } - - public interface Listener { - void onRecorderStarted(); - void onRecorderLocked(); - void onRecorderFinished(); - void onRecorderCanceled(); - void onRecorderPermissionRequired(); - void onEmojiToggle(); - void onLinkPreviewCanceled(); - } - - private static class SlideToCancel { - - private final View slideToCancelView; - - SlideToCancel(View slideToCancelView) { - this.slideToCancelView = slideToCancelView; + @TargetApi(Build.VERSION_CODES.HONEYCOMB) + public InputPanel(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); } - public void display() { - ViewUtil.fadeIn(this.slideToCancelView, FADE_TIME); + public interface MediaListener { + void onMediaSelected(@NonNull Uri uri, String contentType); } - - public ListenableFuture hide() { - final SettableFuture future = new SettableFuture<>(); - - AnimationSet animation = new AnimationSet(true); - animation.addAnimation(new TranslateAnimation(Animation.ABSOLUTE, slideToCancelView.getTranslationX(), - Animation.ABSOLUTE, 0, - Animation.RELATIVE_TO_SELF, 0, - Animation.RELATIVE_TO_SELF, 0)); - animation.addAnimation(new AlphaAnimation(1, 0)); - - animation.setDuration(MicrophoneRecorderView.ANIMATION_DURATION); - animation.setFillBefore(true); - animation.setFillAfter(false); - - slideToCancelView.postDelayed(() -> future.set(null), MicrophoneRecorderView.ANIMATION_DURATION); - slideToCancelView.setVisibility(View.GONE); - slideToCancelView.startAnimation(animation); - - return future; - } - - void moveTo(float offset) { - Animation animation = new TranslateAnimation(Animation.ABSOLUTE, offset, - Animation.ABSOLUTE, offset, - Animation.RELATIVE_TO_SELF, 0, - Animation.RELATIVE_TO_SELF, 0); - - animation.setDuration(0); - animation.setFillAfter(true); - animation.setFillBefore(true); - - slideToCancelView.startAnimation(animation); - } - } - - private static class RecordTime implements Runnable { - - private final @NonNull TextView recordTimeView; - private final @NonNull View microphone; - private final @NonNull Runnable onLimitHit; - private final long limitSeconds; - private long startTime; - - private RecordTime(@NonNull TextView recordTimeView, @NonNull View microphone, long limitSeconds, @NonNull Runnable onLimitHit) { - this.recordTimeView = recordTimeView; - this.microphone = microphone; - this.limitSeconds = limitSeconds; - this.onLimitHit = onLimitHit; - } - - @MainThread - public void display() { - this.startTime = System.currentTimeMillis(); - this.recordTimeView.setText(DateUtils.formatElapsedTime(0)); - ViewUtil.fadeIn(this.recordTimeView, FADE_TIME); - Util.runOnMainDelayed(this, TimeUnit.SECONDS.toMillis(1)); - microphone.setVisibility(View.VISIBLE); - microphone.startAnimation(pulseAnimation()); - } - - @MainThread - public long hide() { - long elapsedTime = System.currentTimeMillis() - startTime; - this.startTime = 0; - ViewUtil.fadeOut(this.recordTimeView, FADE_TIME, View.INVISIBLE); - microphone.clearAnimation(); - ViewUtil.fadeOut(this.microphone, FADE_TIME, View.INVISIBLE); - return elapsedTime; - } - - @Override - @MainThread - public void run() { - long localStartTime = startTime; - if (localStartTime > 0) { - long elapsedTime = System.currentTimeMillis() - localStartTime; - long elapsedSeconds = TimeUnit.MILLISECONDS.toSeconds(elapsedTime); - if (elapsedSeconds >= limitSeconds) { - onLimitHit.run(); - } else { - recordTimeView.setText(DateUtils.formatElapsedTime(elapsedSeconds)); - Util.runOnMainDelayed(this, TimeUnit.SECONDS.toMillis(1)); - } - } - } - - private static Animation pulseAnimation() { - AlphaAnimation animation = new AlphaAnimation(0, 1); - - animation.setInterpolator(pulseInterpolator()); - animation.setRepeatCount(Animation.INFINITE); - animation.setDuration(1000); - - return animation; - } - - private static Interpolator pulseInterpolator() { - return input -> { - input *= 5; - if (input > 1) { - input = 4 - input; - } - return Math.max(0, Math.min(1, input)); - }; - } - } - - public interface MediaListener { - void onMediaSelected(@NonNull Uri uri, String contentType); - } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/MicrophoneRecorderView.java b/app/src/main/java/org/thoughtcrime/securesms/components/MicrophoneRecorderView.java deleted file mode 100644 index 26b3d4f62..000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/components/MicrophoneRecorderView.java +++ /dev/null @@ -1,272 +0,0 @@ -package org.thoughtcrime.securesms.components; - -import android.Manifest; -import android.content.Context; -import android.graphics.PorterDuff; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.core.view.ViewCompat; -import android.util.AttributeSet; -import android.view.MotionEvent; -import android.view.View; -import android.view.animation.Animation; -import android.view.animation.AnimationSet; -import android.view.animation.AnticipateOvershootInterpolator; -import android.view.animation.DecelerateInterpolator; -import android.view.animation.LinearInterpolator; -import android.view.animation.OvershootInterpolator; -import android.view.animation.ScaleAnimation; -import android.view.animation.TranslateAnimation; -import android.widget.FrameLayout; -import android.widget.ImageView; - -import org.thoughtcrime.securesms.permissions.Permissions; - -import org.session.libsession.utilities.ViewUtil; - -import network.loki.messenger.R; - -public final class MicrophoneRecorderView extends FrameLayout implements View.OnTouchListener { - - enum State { - NOT_RUNNING, - RUNNING_HELD, - RUNNING_LOCKED - } - - public static final int ANIMATION_DURATION = 200; - - private FloatingRecordButton floatingRecordButton; - private LockDropTarget lockDropTarget; - private @Nullable Listener listener; - private @NonNull State state = State.NOT_RUNNING; - - public MicrophoneRecorderView(Context context) { - super(context); - } - - public MicrophoneRecorderView(Context context, AttributeSet attrs) { - super(context, attrs); - } - - @Override - public void onFinishInflate() { - super.onFinishInflate(); - - floatingRecordButton = new FloatingRecordButton(getContext(), findViewById(R.id.quick_audio_fab)); - lockDropTarget = new LockDropTarget (getContext(), findViewById(R.id.lock_drop_target)); - - View recordButton = ViewUtil.findById(this, R.id.quick_audio_toggle); - recordButton.setOnTouchListener(this); - } - - public void cancelAction() { - if (state != State.NOT_RUNNING) { - state = State.NOT_RUNNING; - hideUi(); - - if (listener != null) listener.onRecordCanceled(); - } - } - - public boolean isRecordingLocked() { - return state == State.RUNNING_LOCKED; - } - - private void lockAction() { - if (state == State.RUNNING_HELD) { - state = State.RUNNING_LOCKED; - hideUi(); - - if (listener != null) listener.onRecordLocked(); - } - } - - public void unlockAction() { - if (state == State.RUNNING_LOCKED) { - state = State.NOT_RUNNING; - hideUi(); - - if (listener != null) listener.onRecordReleased(); - } - } - - private void hideUi() { - floatingRecordButton.hide(); - lockDropTarget.hide(); - } - - @Override - public boolean onTouch(View v, final MotionEvent event) { - switch (event.getAction()) { - case MotionEvent.ACTION_DOWN: - if (!Permissions.hasAll(getContext(), Manifest.permission.RECORD_AUDIO)) { - if (listener != null) listener.onRecordPermissionRequired(); - } else { - state = State.RUNNING_HELD; - floatingRecordButton.display(event.getX(), event.getY()); - lockDropTarget.display(); - if (listener != null) listener.onRecordPressed(); - } - break; - case MotionEvent.ACTION_CANCEL: - case MotionEvent.ACTION_UP: - if (this.state == State.RUNNING_HELD) { - state = State.NOT_RUNNING; - hideUi(); - if (listener != null) listener.onRecordReleased(); - } - break; - case MotionEvent.ACTION_MOVE: - if (this.state == State.RUNNING_HELD) { - this.floatingRecordButton.moveTo(event.getX(), event.getY()); - if (listener != null) listener.onRecordMoved(floatingRecordButton.lastOffsetX, event.getRawX()); - - int dimensionPixelSize = getResources().getDimensionPixelSize(R.dimen.recording_voice_lock_target); - if (floatingRecordButton.lastOffsetY <= dimensionPixelSize) { - lockAction(); - } - } - break; - } - - return false; - } - - public void setListener(@Nullable Listener listener) { - this.listener = listener; - } - - public interface Listener { - void onRecordPressed(); - void onRecordReleased(); - void onRecordCanceled(); - void onRecordLocked(); - void onRecordMoved(float offsetX, float absoluteX); - void onRecordPermissionRequired(); - } - - private static class FloatingRecordButton { - - private final ImageView recordButtonFab; - - private float startPositionX; - private float startPositionY; - private float lastOffsetX; - private float lastOffsetY; - - FloatingRecordButton(Context context, ImageView recordButtonFab) { - this.recordButtonFab = recordButtonFab; - this.recordButtonFab.getBackground().setColorFilter(context.getResources() - .getColor(R.color.destructive), - PorterDuff.Mode.SRC_IN); - } - - void display(float x, float y) { - this.startPositionX = x; - this.startPositionY = y; - - recordButtonFab.setVisibility(View.VISIBLE); - - AnimationSet animation = new AnimationSet(true); - animation.addAnimation(new TranslateAnimation(Animation.ABSOLUTE, 0, - Animation.ABSOLUTE, 0, - Animation.ABSOLUTE, 0, - Animation.ABSOLUTE, 0)); - - animation.addAnimation(new ScaleAnimation(.5f, 1f, .5f, 1f, - Animation.RELATIVE_TO_SELF, .5f, - Animation.RELATIVE_TO_SELF, .5f)); - - animation.setDuration(ANIMATION_DURATION); - animation.setInterpolator(new OvershootInterpolator()); - - recordButtonFab.startAnimation(animation); - } - - void moveTo(float x, float y) { - lastOffsetX = getXOffset(x); - lastOffsetY = getYOffset(y); - - if (Math.abs(lastOffsetX) > Math.abs(lastOffsetY)) { - lastOffsetY = 0; - } else { - lastOffsetX = 0; - } - - recordButtonFab.setTranslationX(lastOffsetX); - recordButtonFab.setTranslationY(lastOffsetY); - } - - void hide() { - recordButtonFab.setTranslationX(0); - recordButtonFab.setTranslationY(0); - if (recordButtonFab.getVisibility() != VISIBLE) return; - - AnimationSet animation = new AnimationSet(false); - Animation scaleAnimation = new ScaleAnimation(1, 0.5f, 1, 0.5f, - Animation.RELATIVE_TO_SELF, 0.5f, - Animation.RELATIVE_TO_SELF, 0.5f); - - Animation translateAnimation = new TranslateAnimation(Animation.ABSOLUTE, lastOffsetX, - Animation.ABSOLUTE, 0, - Animation.ABSOLUTE, lastOffsetY, - Animation.ABSOLUTE, 0); - - scaleAnimation.setInterpolator(new AnticipateOvershootInterpolator(1.5f)); - translateAnimation.setInterpolator(new DecelerateInterpolator()); - animation.addAnimation(scaleAnimation); - animation.addAnimation(translateAnimation); - animation.setDuration(ANIMATION_DURATION); - animation.setInterpolator(new AnticipateOvershootInterpolator(1.5f)); - - recordButtonFab.setVisibility(View.GONE); - recordButtonFab.clearAnimation(); - recordButtonFab.startAnimation(animation); - } - - private float getXOffset(float x) { - return ViewCompat.getLayoutDirection(recordButtonFab) == ViewCompat.LAYOUT_DIRECTION_LTR ? - -Math.max(0, this.startPositionX - x) : Math.max(0, x - this.startPositionX); - } - - private float getYOffset(float y) { - return Math.min(0, y - this.startPositionY); - } - } - - private static class LockDropTarget { - - private final View lockDropTarget; - private final int dropTargetPosition; - - LockDropTarget(Context context, View lockDropTarget) { - this.lockDropTarget = lockDropTarget; - this.dropTargetPosition = context.getResources().getDimensionPixelSize(R.dimen.recording_voice_lock_target); - } - - void display() { - lockDropTarget.setScaleX(1); - lockDropTarget.setScaleY(1); - lockDropTarget.setAlpha(0); - lockDropTarget.setTranslationY(0); - lockDropTarget.setVisibility(VISIBLE); - lockDropTarget.animate() - .setStartDelay(ANIMATION_DURATION * 2) - .setDuration(ANIMATION_DURATION) - .setInterpolator(new DecelerateInterpolator()) - .translationY(dropTargetPosition) - .alpha(1) - .start(); - } - - void hide() { - lockDropTarget.animate() - .setStartDelay(0) - .setDuration(ANIMATION_DURATION) - .setInterpolator(new LinearInterpolator()) - .scaleX(0).scaleY(0) - .start(); - } - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.java deleted file mode 100644 index d6db6ec16..000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.java +++ /dev/null @@ -1,2440 +0,0 @@ -/* - * Copyright (C) 2011 Whisper Systems - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package org.thoughtcrime.securesms.conversation; - -import android.Manifest; -import android.annotation.SuppressLint; -import android.annotation.TargetApi; -import android.content.ActivityNotFoundException; -import android.content.BroadcastReceiver; -import android.content.ClipData; -import android.content.ClipboardManager; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.content.pm.PackageManager; -import android.content.res.Configuration; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.graphics.PorterDuff; -import android.graphics.PorterDuffColorFilter; -import android.hardware.Camera; -import android.net.Uri; -import android.os.AsyncTask; -import android.os.Build; -import android.os.Bundle; -import android.os.Handler; -import android.os.Vibrator; -import android.provider.Browser; -import android.provider.Telephony; -import android.text.Editable; -import android.text.TextUtils; -import android.text.TextWatcher; -import android.util.Pair; -import android.util.TypedValue; -import android.view.KeyEvent; -import android.view.Menu; -import android.view.MenuInflater; -import android.view.MenuItem; -import android.view.View; -import android.view.View.OnClickListener; -import android.view.View.OnFocusChangeListener; -import android.view.View.OnKeyListener; -import android.view.WindowManager; -import android.view.inputmethod.EditorInfo; -import android.widget.Button; -import android.widget.ImageButton; -import android.widget.ImageView; -import android.widget.ProgressBar; -import android.widget.TextView; -import android.widget.Toast; -import androidx.annotation.ColorInt; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.appcompat.app.ActionBar; -import androidx.appcompat.app.AlertDialog; -import androidx.appcompat.widget.SearchView; -import androidx.appcompat.widget.Toolbar; -import androidx.core.content.pm.ShortcutInfoCompat; -import androidx.core.content.pm.ShortcutManagerCompat; -import androidx.core.graphics.drawable.IconCompat; -import androidx.core.view.MenuItemCompat; -import androidx.lifecycle.ViewModelProviders; -import androidx.loader.app.LoaderManager; -import androidx.localbroadcastmanager.content.LocalBroadcastManager; -import com.annimon.stream.Stream; -import org.greenrobot.eventbus.EventBus; -import org.greenrobot.eventbus.Subscribe; -import org.greenrobot.eventbus.ThreadMode; -import org.session.libsession.messaging.mentions.MentionsManager; -import org.session.libsession.messaging.messages.control.ExpirationTimerUpdate; -import org.session.libsession.messaging.messages.signal.OutgoingMediaMessage; -import org.session.libsession.messaging.messages.signal.OutgoingSecureMediaMessage; -import org.session.libsession.messaging.messages.signal.OutgoingTextMessage; -import org.session.libsession.messaging.messages.visible.OpenGroupInvitation; -import org.session.libsession.messaging.messages.visible.VisibleMessage; -import org.session.libsession.messaging.open_groups.OpenGroupV2; -import org.session.libsession.messaging.sending_receiving.MessageSender; -import org.session.libsession.messaging.sending_receiving.attachments.Attachment; -import org.session.libsession.messaging.sending_receiving.link_preview.LinkPreview; -import org.session.libsession.messaging.sending_receiving.quotes.QuoteModel; -import org.session.libsession.utilities.Contact; -import org.session.libsession.utilities.Address; -import org.session.libsession.utilities.DistributionTypes; -import org.session.libsession.utilities.GroupRecord; -import org.session.libsession.utilities.recipients.Recipient; -import org.session.libsession.utilities.recipients.RecipientFormattingException; -import org.session.libsession.utilities.recipients.RecipientModifiedListener; -import org.session.libsession.utilities.ExpirationUtil; -import org.session.libsession.utilities.GroupUtil; -import org.session.libsession.utilities.MediaTypes; -import org.session.libsession.utilities.ServiceUtil; -import org.session.libsession.utilities.TextSecurePreferences; -import org.session.libsession.utilities.Util; -import org.session.libsession.utilities.ViewUtil; -import org.session.libsession.utilities.concurrent.AssertedSuccessListener; -import org.session.libsession.utilities.Stub; -import org.session.libsignal.exceptions.InvalidMessageException; -import org.session.libsignal.utilities.guava.Optional; -import org.session.libsession.messaging.mentions.Mention; -import org.session.libsignal.utilities.HexEncodingKt; -import org.session.libsignal.utilities.PublicKeyValidation; -import org.session.libsignal.utilities.ListenableFuture; -import org.session.libsignal.utilities.SettableFuture; -import org.session.libsignal.utilities.Log; -import org.thoughtcrime.securesms.ApplicationContext; -import org.thoughtcrime.securesms.ExpirationDialog; -import org.thoughtcrime.securesms.MediaOverviewActivity; -import org.thoughtcrime.securesms.MuteDialog; -import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity; -import org.thoughtcrime.securesms.ShortcutLauncherActivity; -import org.thoughtcrime.securesms.audio.AudioRecorder; -import org.thoughtcrime.securesms.audio.AudioSlidePlayer; -import org.thoughtcrime.securesms.components.AnimatingToggle; -import org.thoughtcrime.securesms.components.AttachmentTypeSelector; -import org.thoughtcrime.securesms.components.ComposeText; -import org.thoughtcrime.securesms.components.ConversationSearchBottomBar; -import org.thoughtcrime.securesms.components.HidingLinearLayout; -import org.thoughtcrime.securesms.components.InputAwareLayout; -import org.thoughtcrime.securesms.components.InputPanel; -import org.thoughtcrime.securesms.components.KeyboardAwareLinearLayout.OnKeyboardShownListener; -import org.thoughtcrime.securesms.components.emoji.EmojiKeyboardProvider; -import org.thoughtcrime.securesms.components.emoji.EmojiStrings; -import org.thoughtcrime.securesms.components.emoji.MediaKeyboard; -import org.thoughtcrime.securesms.contacts.ContactAccessor; -import org.thoughtcrime.securesms.contacts.ContactAccessor.ContactData; -import org.thoughtcrime.securesms.contactshare.ContactUtil; -import org.thoughtcrime.securesms.contactshare.SimpleTextWatcher; -import org.thoughtcrime.securesms.database.DatabaseFactory; -import org.thoughtcrime.securesms.database.DraftDatabase; -import org.thoughtcrime.securesms.database.DraftDatabase.Draft; -import org.thoughtcrime.securesms.database.DraftDatabase.Drafts; -import org.thoughtcrime.securesms.database.MessagingDatabase.MarkedMessageInfo; -import org.thoughtcrime.securesms.database.MmsSmsColumns.Types; -import org.thoughtcrime.securesms.database.ThreadDatabase; -import org.thoughtcrime.securesms.database.model.MessageRecord; -import org.thoughtcrime.securesms.database.model.MmsMessageRecord; -import org.thoughtcrime.securesms.giph.ui.GiphyActivity; -import org.thoughtcrime.securesms.linkpreview.LinkPreviewRepository; -import org.thoughtcrime.securesms.linkpreview.LinkPreviewUtil; -import org.thoughtcrime.securesms.linkpreview.LinkPreviewViewModel; -import org.thoughtcrime.securesms.loki.activities.EditClosedGroupActivity; -import org.thoughtcrime.securesms.loki.activities.HomeActivity; -import org.thoughtcrime.securesms.loki.activities.SelectContactsActivity; -import org.thoughtcrime.securesms.loki.api.PublicChatInfoUpdateWorker; -import org.thoughtcrime.securesms.loki.protocol.SessionMetaProtocol; -import org.thoughtcrime.securesms.loki.utilities.GeneralUtilitiesKt; -import org.thoughtcrime.securesms.loki.utilities.MentionManagerUtilities; -import org.thoughtcrime.securesms.loki.utilities.OpenGroupUtilities; -import org.thoughtcrime.securesms.loki.views.MentionCandidateSelectionView; -import org.thoughtcrime.securesms.loki.views.ProfilePictureView; -import org.thoughtcrime.securesms.mediasend.Media; -import org.thoughtcrime.securesms.mediasend.MediaSendActivity; -import org.thoughtcrime.securesms.conversation.v2.utilities.AttachmentManager; -import org.thoughtcrime.securesms.conversation.v2.utilities.AttachmentManager.MediaType; -import org.thoughtcrime.securesms.mms.AudioSlide; -import org.thoughtcrime.securesms.mms.GifSlide; -import org.thoughtcrime.securesms.mms.GlideApp; -import org.thoughtcrime.securesms.mms.GlideRequests; -import org.thoughtcrime.securesms.mms.ImageSlide; -import org.thoughtcrime.securesms.mms.MediaConstraints; -import org.thoughtcrime.securesms.mms.MmsException; -import org.thoughtcrime.securesms.mms.QuoteId; -import org.thoughtcrime.securesms.mms.Slide; -import org.thoughtcrime.securesms.mms.SlideDeck; -import org.thoughtcrime.securesms.mms.TextSlide; -import org.thoughtcrime.securesms.mms.VideoSlide; -import org.thoughtcrime.securesms.notifications.MarkReadReceiver; -import org.thoughtcrime.securesms.permissions.Permissions; -import org.thoughtcrime.securesms.providers.BlobProvider; -import org.thoughtcrime.securesms.search.model.MessageResult; -import org.thoughtcrime.securesms.service.ExpiringMessageManager; -import org.thoughtcrime.securesms.util.BitmapUtil; -import org.thoughtcrime.securesms.util.DateUtils; -import org.thoughtcrime.securesms.util.MediaUtil; -import org.thoughtcrime.securesms.util.PushCharacterCalculator; -import java.io.IOException; -import java.text.SimpleDateFormat; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Date; -import java.util.List; -import java.util.Locale; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicInteger; -import kotlin.Unit; -import network.loki.messenger.R; - -/** - * Activity for displaying a message thread, as well as - * composing/sending a new message into that thread. - * - * @author Moxie Marlinspike - * - */ -@SuppressLint("StaticFieldLeak") -public class ConversationActivity extends PassphraseRequiredActionBarActivity - implements ConversationFragment.ConversationFragmentListener, - AttachmentManager.AttachmentListener, - RecipientModifiedListener, - OnKeyboardShownListener, - InputPanel.Listener, - InputPanel.MediaListener, - ComposeText.CursorPositionChangedListener, - ConversationSearchBottomBar.EventListener -{ - private static final String TAG = ConversationActivity.class.getSimpleName(); - - public static final String ADDRESS_EXTRA = "address"; - public static final String THREAD_ID_EXTRA = "thread_id"; - public static final String IS_ARCHIVED_EXTRA = "is_archived"; - public static final String TEXT_EXTRA = "draft_text"; - public static final String MEDIA_EXTRA = "media_list"; - public static final String STICKER_EXTRA = "media_list"; - public static final String DISTRIBUTION_TYPE_EXTRA = "distribution_type"; - public static final String TIMING_EXTRA = "timing"; - public static final String LAST_SEEN_EXTRA = "last_seen"; - public static final String STARTING_POSITION_EXTRA = "starting_position"; - - // private static final int PICK_GALLERY = 1; - private static final int PICK_DOCUMENT = 2; - private static final int PICK_AUDIO = 3; - private static final int PICK_CONTACT = 4; - // private static final int GET_CONTACT_DETAILS = 5; -// private static final int GROUP_EDIT = 6; - private static final int TAKE_PHOTO = 7; - private static final int ADD_CONTACT = 8; - private static final int PICK_LOCATION = 9; - private static final int PICK_GIF = 10; - private static final int SMS_DEFAULT = 11; - private static final int MEDIA_SENDER = 12; - private static final int INVITE_CONTACTS = 124; - - private GlideRequests glideRequests; - protected ComposeText composeText; - private AnimatingToggle buttonToggle; - private ImageButton sendButton; - private ImageButton attachButton; - private ProfilePictureView profilePictureView; - private TextView titleTextView; - private ConversationFragment fragment; - private Button unblockButton; - private Button makeDefaultSmsButton; - private InputAwareLayout container; - private TypingStatusTextWatcher typingTextWatcher; - private MentionTextWatcher mentionTextWatcher; - private ConversationSearchBottomBar searchNav; - private MenuItem searchViewItem; - private ProgressBar messageStatusProgressBar; - private ImageView muteIndicatorImageView; - private TextView subtitleTextView; - private View homeButtonContainer; - - private AttachmentTypeSelector attachmentTypeSelector; - private AttachmentManager attachmentManager; - private AudioRecorder audioRecorder; - private Handler audioHandler; - private Runnable stopRecordingTask; - private Stub emojiDrawerStub; - protected HidingLinearLayout quickAttachmentToggle; - protected HidingLinearLayout inlineAttachmentToggle; - private InputPanel inputPanel; - - private LinkPreviewViewModel linkPreviewViewModel; - private ConversationSearchViewModel searchViewModel; - - private Recipient recipient; - private long threadId; - private int distributionType; - private boolean isDefaultSms = false; - private boolean isSecurityInitialized = false; - private int expandedKeyboardHeight = 0; - private int collapsedKeyboardHeight = Integer.MAX_VALUE; - private int keyboardHeight = 0; - - // Message status bar - private ArrayList broadcastReceivers = new ArrayList<>(); - private String messageStatus = null; - - // Mentions - private View mentionCandidateSelectionViewContainer; - private MentionCandidateSelectionView mentionCandidateSelectionView; - private int currentMentionStartIndex = -1; - private ArrayList mentions = new ArrayList<>(); - private String oldText = ""; - - private final PushCharacterCalculator characterCalculator = new PushCharacterCalculator(); - - @Override - protected void onCreate(Bundle state, boolean ready) { - Log.i(TAG, "onCreate()"); - - setContentView(R.layout.conversation_activity); - - fragment = initFragment(R.id.fragment_content, new ConversationFragment(), Locale.getDefault()); - - registerMessageStatusObserver("calculatingPoW"); - registerMessageStatusObserver("contactingNetwork"); - registerMessageStatusObserver("sendingMessage"); - registerMessageStatusObserver("messageSent"); - registerMessageStatusObserver("messageFailed"); - BroadcastReceiver broadcastReceiver = new BroadcastReceiver() { - - @Override - public void onReceive(Context context, Intent intent) { - Toast.makeText(ConversationActivity.this, "Your clock is out of sync with the service node network.", Toast.LENGTH_LONG).show(); - } - }; - broadcastReceivers.add(broadcastReceiver); - LocalBroadcastManager.getInstance(this).registerReceiver(broadcastReceiver, new IntentFilter("clockOutOfSync")); - - initializeActionBar(); - initializeViews(); - initializeResources(); - initializeLinkPreviewObserver(); - initializeSearchObserver(); - initializeSecurity(false, isDefaultSms).addListener(new AssertedSuccessListener() { - @Override - public void onSuccess(Boolean result) { - initializeDraft().addListener(new AssertedSuccessListener() { - @Override - public void onSuccess(Boolean loadedDraft) { - if (loadedDraft != null && loadedDraft) { - Log.i(TAG, "Finished loading draft"); - Util.runOnMain(() -> { - if (fragment != null && fragment.isResumed()) { - fragment.moveToLastSeen(); - } else { - Log.w(TAG, "Wanted to move to the last seen position, but the fragment was in an invalid state"); - } - }); - } - - if (TextSecurePreferences.isTypingIndicatorsEnabled(ConversationActivity.this)) { - composeText.addTextChangedListener(typingTextWatcher); - } - composeText.setSelection(composeText.length(), composeText.length()); - composeText.addTextChangedListener(mentionTextWatcher); - mentionCandidateSelectionView.setGlide(glideRequests); - mentionCandidateSelectionView.setOnMentionCandidateSelected( mentionCandidate -> { - mentions.add(mentionCandidate); - String oldText = composeText.getText().toString(); - String newText = oldText.substring(0, currentMentionStartIndex) + "@" + mentionCandidate.getDisplayName() + " "; - composeText.setText(newText); - composeText.setSelection(newText.length()); - currentMentionStartIndex = -1; - mentionCandidateSelectionView.hide(); - ConversationActivity.this.oldText = newText; - return Unit.INSTANCE; - }); - } - }); - } - }); - - MentionManagerUtilities.INSTANCE.populateUserPublicKeyCacheIfNeeded(threadId, this); - - OpenGroupV2 openGroupV2 = DatabaseFactory.getLokiThreadDatabase(this).getOpenGroupChat(threadId); - if (openGroupV2 != null) { - PublicChatInfoUpdateWorker.scheduleInstant(this, openGroupV2.getServer(), openGroupV2.getRoom()); - if (openGroupV2.getRoom().equals("session") || openGroupV2.getRoom().equals("oxen") - || openGroupV2.getRoom().equals("lokinet") || openGroupV2.getRoom().equals("crypto")) { - View openGroupGuidelinesView = findViewById(R.id.open_group_guidelines_view); - openGroupGuidelinesView.setVisibility(View.VISIBLE); - } - } - - View rootView = findViewById(R.id.rootView); - rootView.getViewTreeObserver().addOnGlobalLayoutListener(() -> { - int height = rootView.getRootView().getHeight() - rootView.getHeight(); - int thresholdInDP = 120; - float scale = getResources().getDisplayMetrics().density; - int thresholdInPX = (int)(thresholdInDP * scale); - if (expandedKeyboardHeight == 0 || height > thresholdInPX) { - expandedKeyboardHeight = height; - } - collapsedKeyboardHeight = Math.min(collapsedKeyboardHeight, height); - keyboardHeight = expandedKeyboardHeight - collapsedKeyboardHeight; - - // Use 300dp if the keyboard wasn't opened yet. - if (keyboardHeight == 0) { - keyboardHeight = (int)(300f * getResources().getDisplayMetrics().density); - } - }); - } - - private void registerMessageStatusObserver(String status) { - BroadcastReceiver broadcastReceiver = new BroadcastReceiver() { - - @Override - public void onReceive(Context context, Intent intent) { - long timestamp = intent.getLongExtra("long", 0); - handleMessageStatusChanged(status, timestamp); - } - }; - broadcastReceivers.add(broadcastReceiver); - LocalBroadcastManager.getInstance(this).registerReceiver(broadcastReceiver, new IntentFilter(status)); - } - - @Override - protected void onNewIntent(Intent intent) { - super.onNewIntent(intent); - Log.i(TAG, "onNewIntent()"); - - if (isFinishing()) { - Log.w(TAG, "Activity is finishing..."); - return; - } - - if (!org.thoughtcrime.securesms.util.Util.isEmpty(composeText)) { - saveDraft(); - attachmentManager.clear(); - silentlySetComposeText(""); - } - - setIntent(intent); - initializeResources(); - initializeSecurity(false, isDefaultSms).addListener(new AssertedSuccessListener() { - @Override - public void onSuccess(Boolean result) { - initializeDraft(); - } - }); - - if (fragment != null) { - fragment.onNewIntent(); - } - - searchNav.setVisibility(View.GONE); - } - - @Override - protected void onResume() { - super.onResume(); - - EventBus.getDefault().register(this); - initializeEnabledCheck(); - composeText.setTransport(); - - updateTitleTextView(recipient); - updateProfilePicture(); - updateSubtitleTextView(); - updateInputUI(recipient); - - ApplicationContext.getInstance(this).messageNotifier.setVisibleThread(threadId); - markThreadAsRead(); - - inputPanel.setHint(getResources().getString(R.string.ConversationActivity_message)); - - Log.i(TAG, "onResume() Finished: " + (System.currentTimeMillis() - getIntent().getLongExtra(TIMING_EXTRA, 0))); - } - - @Override - protected void onPause() { - super.onPause(); - ApplicationContext.getInstance(this).messageNotifier.setVisibleThread(-1L); - if (isFinishing()) overridePendingTransition(R.anim.fade_scale_in, R.anim.slide_to_right); - inputPanel.onPause(); - - fragment.setLastSeen(System.currentTimeMillis()); - markLastSeen(); - AudioSlidePlayer.stopAll(); - EventBus.getDefault().unregister(this); - } - - @Override - protected void onStop() { - super.onStop(); - EventBus.getDefault().unregister(this); - } - - @Override - public void onConfigurationChanged(Configuration newConfig) { - Log.i(TAG, "onConfigurationChanged(" + newConfig.orientation + ")"); - super.onConfigurationChanged(newConfig); - composeText.setTransport(); - - if (emojiDrawerStub.resolved() && container.getCurrentInput() == emojiDrawerStub.get()) { - container.hideAttachedInput(true); - } - } - - @Override - protected void onDestroy() { - saveDraft(); - if (recipient != null) recipient.removeListener(this); - for (BroadcastReceiver broadcastReceiver : broadcastReceivers) { - LocalBroadcastManager.getInstance(this).unregisterReceiver(broadcastReceiver); - } - super.onDestroy(); - } - - @Override - public void onActivityResult(final int reqCode, int resultCode, Intent data) { - Log.i(TAG, "onActivityResult called: " + reqCode + ", " + resultCode + " , " + data); - super.onActivityResult(reqCode, resultCode, data); - - if ((data == null && reqCode != TAKE_PHOTO && reqCode != SMS_DEFAULT) || - (resultCode != RESULT_OK && reqCode != SMS_DEFAULT)) - { - updateLinkPreviewState(); - return; - } - - switch (reqCode) { - case PICK_DOCUMENT: - setMedia(data.getData(), MediaType.DOCUMENT); - break; - case PICK_AUDIO: - setMedia(data.getData(), MediaType.AUDIO); - break; - case TAKE_PHOTO: - if (attachmentManager.getCaptureUri() != null) { - setMedia(attachmentManager.getCaptureUri(), MediaType.IMAGE); - } - break; - case ADD_CONTACT: - recipient = Recipient.from(this, recipient.getAddress(), true); - recipient.addListener(this); - fragment.reloadList(); - break; - /* - case PICK_LOCATION: - SignalPlace place = new SignalPlace(PlacePicker.getPlace(data, this)); - attachmentManager.setLocation(place, getCurrentMediaConstraints()); - break; - */ - case PICK_GIF: - setMedia(data.getData(), - MediaType.GIF, - data.getIntExtra(GiphyActivity.EXTRA_WIDTH, 0), - data.getIntExtra(GiphyActivity.EXTRA_HEIGHT, 0)); - break; - case SMS_DEFAULT: - initializeSecurity(true, isDefaultSms); - break; - case MEDIA_SENDER: - long expiresIn = recipient.getExpireMessages() * 1000L; - int subscriptionId = -1; - boolean initiating = threadId == -1; - String message = data.getStringExtra(MediaSendActivity.EXTRA_MESSAGE); - SlideDeck slideDeck = new SlideDeck(); - - List mediaList = data.getParcelableArrayListExtra(MediaSendActivity.EXTRA_MEDIA); - - for (Media mediaItem : mediaList) { - if (MediaUtil.isVideoType(mediaItem.getMimeType())) { - slideDeck.addSlide(new VideoSlide(this, mediaItem.getUri(), 0, mediaItem.getCaption().orNull())); - } else if (MediaUtil.isGif(mediaItem.getMimeType())) { - slideDeck.addSlide(new GifSlide(this, mediaItem.getUri(), 0, mediaItem.getWidth(), mediaItem.getHeight(), mediaItem.getCaption().orNull())); - } else if (MediaUtil.isImageType(mediaItem.getMimeType())) { - slideDeck.addSlide(new ImageSlide(this, mediaItem.getUri(), 0, mediaItem.getWidth(), mediaItem.getHeight(), mediaItem.getCaption().orNull())); - } else { - Log.w(TAG, "Asked to send an unexpected mimeType: '" + mediaItem.getMimeType() + "'. Skipping."); - } - } - - final Context context = ConversationActivity.this.getApplicationContext(); - - sendMediaMessage(message, - slideDeck, - inputPanel.getQuote().orNull(), - Optional.absent(), - initiating).addListener(new AssertedSuccessListener() { - @Override - public void onSuccess(Void result) { - AsyncTask.THREAD_POOL_EXECUTOR.execute(() -> { - Stream.of(slideDeck.getSlides()) - .map(Slide::getUri) - .withoutNulls() - .filter(BlobProvider::isAuthority) - .forEach(uri -> BlobProvider.getInstance().delete(context, uri)); - }); - } - }); - break; - case INVITE_CONTACTS: - if (data.getExtras() == null || !data.hasExtra(SelectContactsActivity.Companion.getSelectedContactsKey())) return; - String[] selectedContacts = data.getExtras().getStringArray(SelectContactsActivity.Companion.getSelectedContactsKey()); - sendOpenGroupInvitations(selectedContacts); - break; - } - } - - @Override - public void startActivity(Intent intent) { - if (intent.getStringExtra(Browser.EXTRA_APPLICATION_ID) != null) { - intent.removeExtra(Browser.EXTRA_APPLICATION_ID); - } - - try { - super.startActivity(intent); - } catch (ActivityNotFoundException e) { - Log.w(TAG, e); - Toast.makeText(this, R.string.ConversationActivity_there_is_no_app_available_to_handle_this_link_on_your_device, Toast.LENGTH_LONG).show(); - } - } - - @Override - public boolean onPrepareOptionsMenu(Menu menu) { - MenuInflater inflater = this.getMenuInflater(); - menu.clear(); - - boolean isOpenGroupOrRSSFeed = recipient.getAddress().isOpenGroup(); - - if (!isOpenGroupOrRSSFeed) { - if (recipient.getExpireMessages() > 0) { - inflater.inflate(R.menu.conversation_expiring_on, menu); - - final MenuItem item = menu.findItem(R.id.menu_expiring_messages); - final View actionView = MenuItemCompat.getActionView(item); - final ImageView iconView = actionView.findViewById(R.id.menu_badge_icon); - final TextView badgeView = actionView.findViewById(R.id.expiration_badge); - - @ColorInt int color = GeneralUtilitiesKt.getColorWithID(getResources(), R.color.text, getTheme()); - iconView.setColorFilter(new PorterDuffColorFilter(color, PorterDuff.Mode.MULTIPLY)); - badgeView.setText(ExpirationUtil.getExpirationAbbreviatedDisplayValue(this, recipient.getExpireMessages())); - actionView.setOnClickListener(v -> onOptionsItemSelected(item)); - } else { - inflater.inflate(R.menu.conversation_expiring_off, menu); - } - } - - if (isSingleConversation()) { - if (recipient.isBlocked()) { - inflater.inflate(R.menu.conversation_unblock, menu); - } else { - inflater.inflate(R.menu.conversation_block, menu); - } - inflater.inflate(R.menu.conversation_copy_session_id, menu); - } else if (isGroupConversation() && !isOpenGroupOrRSSFeed) { -// inflater.inflate(R.menu.conversation_group_options, menu); - - if (!isPushGroupConversation()) { - inflater.inflate(R.menu.conversation_mms_group_options, menu); - if (distributionType == DistributionTypes.BROADCAST) { - menu.findItem(R.id.menu_distribution_broadcast).setChecked(true); - } else { - menu.findItem(R.id.menu_distribution_conversation).setChecked(true); - } - } else if (isActiveGroup()) { - inflater.inflate(R.menu.conversation_push_group_options, menu); - } - } else if (isOpenGroupOrRSSFeed) { - inflater.inflate(R.menu.conversation_invite_open_group, menu); - } - - inflater.inflate(R.menu.conversation, menu); - -// if (isSingleConversation()) { -// inflater.inflate(R.menu.conversation_secure, menu); -// } - - if (recipient != null && recipient.isMuted()) inflater.inflate(R.menu.conversation_muted, menu); - else inflater.inflate(R.menu.conversation_unmuted, menu); - - /* - if (isSingleConversation() && getRecipient().getContactUri() == null) { - inflater.inflate(R.menu.conversation_add_to_contacts, menu); - } - if (recipient != null && recipient.isLocalNumber()) { - if (isSecureText) menu.findItem(R.id.menu_call_secure).setVisible(false); - else menu.findItem(R.id.menu_call_insecure).setVisible(false); - MenuItem muteItem = menu.findItem(R.id.menu_mute_notifications); - if (muteItem != null) { - muteItem.setVisible(false); - } - } - */ - - searchViewItem = menu.findItem(R.id.menu_search); - - SearchView searchView = (SearchView)searchViewItem.getActionView(); - SearchView.OnQueryTextListener queryListener = new SearchView.OnQueryTextListener() { - @Override - public boolean onQueryTextSubmit(String query) { - searchViewModel.onQueryUpdated(query, threadId); - searchNav.showLoading(); - fragment.onSearchQueryUpdated(query); - return true; - } - - @Override - public boolean onQueryTextChange(String query) { - searchViewModel.onQueryUpdated(query, threadId); - searchNav.showLoading(); - fragment.onSearchQueryUpdated(query); - return true; - } - }; - - searchViewItem.setOnActionExpandListener(new MenuItem.OnActionExpandListener() { - @Override - public boolean onMenuItemActionExpand(MenuItem item) { - searchView.setOnQueryTextListener(queryListener); - searchViewModel.onSearchOpened(); - searchNav.setVisibility(View.VISIBLE); - searchNav.setData(0, 0); - inputPanel.setVisibility(View.GONE); - - for (int i = 0; i < menu.size(); i++) { - if (!menu.getItem(i).equals(searchViewItem)) { - menu.getItem(i).setVisible(false); - } - } - return true; - } - - @Override - public boolean onMenuItemActionCollapse(MenuItem item) { - searchView.setOnQueryTextListener(null); - searchViewModel.onSearchClosed(); - searchNav.setVisibility(View.GONE); - inputPanel.setVisibility(View.VISIBLE); - updateInputUI(recipient); - fragment.onSearchQueryUpdated(null); - invalidateOptionsMenu(); - return true; - } - }); - - super.onPrepareOptionsMenu(menu); - return true; - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - super.onOptionsItemSelected(item); - switch (item.getItemId()) { -// case R.id.menu_call_secure: handleDial(getRecipient(), true); return true; -// case R.id.menu_call_insecure: handleDial(getRecipient(), false); return true; - case R.id.menu_unblock: handleUnblock(); return true; - case R.id.menu_block: handleBlock(); return true; - case R.id.menu_copy_session_id: handleCopySessionID(); return true; - case R.id.menu_view_media: handleViewMedia(); return true; - case R.id.menu_add_shortcut: handleAddShortcut(); return true; - case R.id.menu_search: handleSearch(); return true; -// case R.id.menu_add_to_contacts: handleAddToContacts(); return true; -// case R.id.menu_reset_secure_session: handleResetSecureSession(); return true; -// case R.id.menu_group_recipients: handleDisplayGroupRecipients(); return true; - case R.id.menu_distribution_broadcast: handleDistributionBroadcastEnabled(item); return true; - case R.id.menu_distribution_conversation: handleDistributionConversationEnabled(item); return true; - case R.id.menu_edit_group: handleEditPushGroup(); return true; - case R.id.menu_leave: handleLeavePushGroup(); return true; - case R.id.menu_mute_notifications: handleMuteNotifications(); return true; - case R.id.menu_unmute_notifications: handleUnmuteNotifications(); return true; -// case R.id.menu_conversation_settings: handleConversationSettings(); return true; - case R.id.menu_expiring_messages_off: - case R.id.menu_expiring_messages: handleSelectMessageExpiration(); return true; - case R.id.menu_invite_to_open_group: handleInviteToOpenGroup(); return true; - case android.R.id.home: handleReturnToConversationList(); return true; - } - - return false; - } - - @Override - public void onBackPressed() { - Log.d(TAG, "onBackPressed()"); - if (container.isInputOpen()) container.hideCurrentInput(composeText); - else super.onBackPressed(); - } - - @Override - public void onKeyboardShown() { - inputPanel.onKeyboardShown(); - } - - @Override - public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { - super.onRequestPermissionsResult(requestCode, permissions, grantResults); - Permissions.onRequestPermissionsResult(this, requestCode, permissions, grantResults); - } - - //////// Event Handlers - - private void handleReturnToConversationList() { - Intent intent = new Intent(this, HomeActivity.class); - intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); - startActivity(intent); - finish(); - } - - private void handleSelectMessageExpiration() { - if (isPushGroupConversation() && !isActiveGroup()) { - return; - } - - //noinspection CodeBlock2Expr - ExpirationDialog.show(this, recipient.getExpireMessages(), expirationTime -> { - new AsyncTask() { - @Override - protected Void doInBackground(Void... params) { - DatabaseFactory.getRecipientDatabase(ConversationActivity.this).setExpireMessages(recipient, expirationTime); - ExpirationTimerUpdate message = new ExpirationTimerUpdate(expirationTime); - message.setRecipient(recipient.getAddress().serialize()); // we need the recipient in ExpiringMessageManager.insertOutgoingExpirationTimerMessage - message.setSentTimestamp(System.currentTimeMillis()); - ExpiringMessageManager expiringMessageManager = ApplicationContext.getInstance(getApplicationContext()).getExpiringMessageManager(); - expiringMessageManager.setExpirationTimer(message); - MessageSender.send(message, recipient.getAddress()); - - return null; - } - - @Override - protected void onPostExecute(Void result) { - invalidateOptionsMenu(); - if (fragment != null) fragment.setLastSeen(0); - } - }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); - }); - } - - private void handleMuteNotifications() { - MuteDialog.show(this, until -> { - recipient.setMuted(until); - - new AsyncTask() { - @Override - protected Void doInBackground(Void... params) { - DatabaseFactory.getRecipientDatabase(ConversationActivity.this) - .setMuted(recipient, until); - - return null; - } - }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); - }); - } - - private void handleUnmuteNotifications() { - recipient.setMuted(0); - - new AsyncTask() { - @Override - protected Void doInBackground(Void... params) { - DatabaseFactory.getRecipientDatabase(ConversationActivity.this) - .setMuted(recipient, 0); - - return null; - } - }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); - } - - private void handleUnblock() { - int titleRes = R.string.ConversationActivity_unblock_this_contact_question; - int bodyRes = R.string.ConversationActivity_you_will_once_again_be_able_to_receive_messages_and_calls_from_this_contact; - - new AlertDialog.Builder(this) - .setTitle(titleRes) - .setMessage(bodyRes) - .setNegativeButton(android.R.string.cancel, null) - .setPositiveButton(R.string.ConversationActivity_unblock, (dialog, which) -> { - new AsyncTask() { - @Override - protected Void doInBackground(Void... params) { - DatabaseFactory.getRecipientDatabase(ConversationActivity.this) - .setBlocked(recipient, false); - - return null; - } - }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); - }).show(); - } - - @TargetApi(Build.VERSION_CODES.KITKAT) - private void handleMakeDefaultSms() { - Intent intent = new Intent(Telephony.Sms.Intents.ACTION_CHANGE_DEFAULT); - intent.putExtra(Telephony.Sms.Intents.EXTRA_PACKAGE_NAME, getPackageName()); - startActivityForResult(intent, SMS_DEFAULT); - } - - private void handleBlock() { - int titleRes = R.string.RecipientPreferenceActivity_block_this_contact_question; - int bodyRes = R.string.RecipientPreferenceActivity_you_will_no_longer_receive_messages_and_calls_from_this_contact; - - new AlertDialog.Builder(this) - .setTitle(titleRes) - .setMessage(bodyRes) - .setNegativeButton(android.R.string.cancel, null) - .setPositiveButton(R.string.RecipientPreferenceActivity_block, (dialog, which) -> { - new AsyncTask() { - @Override - protected Void doInBackground(Void... params) { - DatabaseFactory.getRecipientDatabase(ConversationActivity.this) - .setBlocked(recipient, true); - - Util.runOnMain(() -> finish()); - - return null; - } - }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); - }).show(); - } - - private void handleCopySessionID() { - if (recipient.isGroupRecipient()) { return; } - String sessionID = recipient.getAddress().toString(); - ClipboardManager clipboard = (ClipboardManager)getSystemService(Context.CLIPBOARD_SERVICE); - ClipData clip = ClipData.newPlainText("Session ID", sessionID); - clipboard.setPrimaryClip(clip); - Toast.makeText(this, R.string.copied_to_clipboard, Toast.LENGTH_SHORT).show(); - } - - private void handleViewMedia() { - Intent intent = new Intent(this, MediaOverviewActivity.class); - intent.putExtra(MediaOverviewActivity.ADDRESS_EXTRA, recipient.getAddress()); - startActivity(intent); - } - - private void handleAddShortcut() { - Log.i(TAG, "Creating home screen shortcut for recipient " + recipient.getAddress()); - - new AsyncTask() { - - @Override - protected IconCompat doInBackground(Void... voids) { - Context context = getApplicationContext(); - IconCompat icon = null; - - if (recipient.getContactPhoto() != null) { - try { - Bitmap bitmap = BitmapFactory.decodeStream(recipient.getContactPhoto().openInputStream(context)); - bitmap = BitmapUtil.createScaledBitmap(bitmap, 300, 300); - icon = IconCompat.createWithAdaptiveBitmap(bitmap); - } catch (IOException e) { - Log.w(TAG, "Failed to decode contact photo during shortcut creation. Falling back to generic icon.", e); - } - } - - if (icon == null) { - icon = IconCompat.createWithResource(context, recipient.isGroupRecipient() ? R.mipmap.ic_group_shortcut - : R.mipmap.ic_person_shortcut); - } - - return icon; - } - - @Override - protected void onPostExecute(IconCompat icon) { - Context context = getApplicationContext(); - String name = Optional.fromNullable(recipient.getName()) - .or(Optional.fromNullable(recipient.getProfileName())) - .or(recipient.toShortString()); - - ShortcutInfoCompat shortcutInfo = new ShortcutInfoCompat.Builder(context, recipient.getAddress().serialize() + '-' + System.currentTimeMillis()) - .setShortLabel(name) - .setIcon(icon) - .setIntent(ShortcutLauncherActivity.createIntent(context, recipient.getAddress())) - .build(); - - if (ShortcutManagerCompat.requestPinShortcut(context, shortcutInfo, null)) { - Toast.makeText(context, getString(R.string.ConversationActivity_added_to_home_screen), Toast.LENGTH_LONG).show(); - } - } - }.execute(); - } - - private void handleSearch() { - searchViewModel.onSearchOpened(); - } - - private void handleLeavePushGroup() { - if (getRecipient() == null) { - Toast.makeText(this, getString(R.string.ConversationActivity_invalid_recipient), - Toast.LENGTH_LONG).show(); - return; - } - - AlertDialog.Builder builder = new AlertDialog.Builder(this); - builder.setTitle(getString(R.string.ConversationActivity_leave_group)); - builder.setIconAttribute(R.attr.dialog_info_icon); - builder.setCancelable(true); - - GroupRecord group = DatabaseFactory.getGroupDatabase(this).getGroup(getRecipient().getAddress().toGroupString()).orNull(); - List
admins = group.getAdmins(); - String userPublicKey = TextSecurePreferences.getLocalNumber(this); - String message = getString(R.string.ConversationActivity_are_you_sure_you_want_to_leave_this_group); - for (Address admin : admins) { - if (admin.toString().equals(userPublicKey)) { - message = "Because you are the creator of this group it will be deleted for everyone. This cannot be undone."; - } - } - - builder.setMessage(message); - builder.setPositiveButton(R.string.yes, (dialog, which) -> { - Recipient groupRecipient = getRecipient(); - String groupPublicKey; - boolean isClosedGroup; - try { - groupPublicKey = HexEncodingKt.toHexString(GroupUtil.doubleDecodeGroupID(groupRecipient.getAddress().toString())); - isClosedGroup = DatabaseFactory.getLokiAPIDatabase(this).isClosedGroup(groupPublicKey); - } catch (IOException e) { - groupPublicKey = null; - isClosedGroup = false; - } - try { - if (isClosedGroup) { - MessageSender.explicitLeave(groupPublicKey, true); - initializeEnabledCheck(); - } else { - Toast.makeText(this, R.string.ConversationActivity_error_leaving_group, Toast.LENGTH_LONG).show(); - } - } catch (Exception e) { - Toast.makeText(this, R.string.ConversationActivity_error_leaving_group, Toast.LENGTH_LONG).show(); - } - }); - - builder.setNegativeButton(R.string.no, null); - builder.show(); - } - - private void handleEditPushGroup() { - Intent intent = new Intent(this, EditClosedGroupActivity.class); - String groupID = this.recipient.getAddress().toGroupString(); - intent.putExtra(EditClosedGroupActivity.Companion.getGroupIDKey(), groupID); - startActivity(intent); - } - - private void handleInviteToOpenGroup() { - Intent intent = new Intent(this, SelectContactsActivity.class); - startActivityForResult(intent, INVITE_CONTACTS); - } - - private void handleDistributionBroadcastEnabled(MenuItem item) { - distributionType = DistributionTypes.BROADCAST; - item.setChecked(true); - - if (threadId != -1) { - new AsyncTask() { - @Override - protected Void doInBackground(Void... params) { - DatabaseFactory.getThreadDatabase(ConversationActivity.this) - .setDistributionType(threadId, DistributionTypes.BROADCAST); - return null; - } - }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); - } - } - - private void handleDistributionConversationEnabled(MenuItem item) { - distributionType = DistributionTypes.CONVERSATION; - item.setChecked(true); - - if (threadId != -1) { - new AsyncTask() { - @Override - protected Void doInBackground(Void... params) { - DatabaseFactory.getThreadDatabase(ConversationActivity.this) - .setDistributionType(threadId, DistributionTypes.CONVERSATION); - return null; - } - }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); - } - } - - private void handleAddAttachment() { - if (attachmentTypeSelector == null) { - attachmentTypeSelector = new AttachmentTypeSelector( - this, - LoaderManager.getInstance(this), - new AttachmentTypeListener(), - keyboardHeight); - } - attachmentTypeSelector.show(this, attachButton); - } - - private void handleSecurityChange(boolean isSecureText, boolean isDefaultSms) { - Log.i(TAG, "handleSecurityChange(" + isSecureText + ", " + isDefaultSms + ")"); - if (isSecurityInitialized && isSecureText == true && isDefaultSms == this.isDefaultSms) { - return; - } - - this.isDefaultSms = isDefaultSms; - this.isSecurityInitialized = true; - - if (recipient == null || attachmentManager == null) { return; } - - /* Loki - We don't support SMS - if (!isSecureText && !isPushGroupConversation()) sendButton.disableTransport(Type.TEXTSECURE); - if (recipient.isPushGroupRecipient()) sendButton.disableTransport(Type.SMS); - if (!recipient.isPushGroupRecipient() && recipient.isForceSmsSelection()) { - sendButton.setDefaultTransport(Type.SMS); - } else { - if (isSecureText || isPushGroupConversation()) sendButton.setDefaultTransport(Type.TEXTSECURE); - else sendButton.setDefaultTransport(Type.SMS); - } - */ - - supportInvalidateOptionsMenu(); - updateInputUI(recipient); - } - - ///// Initializers - - private ListenableFuture initializeDraft() { - final SettableFuture result = new SettableFuture<>(); - - final String draftText = getIntent().getStringExtra(TEXT_EXTRA); - final Uri draftMedia = getIntent().getData(); - final MediaType draftMediaType = MediaType.from(getIntent().getType()); - final List mediaList = getIntent().getParcelableArrayListExtra(MEDIA_EXTRA); - - if (!Util.isEmpty(mediaList)) { - Intent sendIntent = MediaSendActivity.buildEditorIntent(this, mediaList, recipient, draftText); - startActivityForResult(sendIntent, MEDIA_SENDER); - return new SettableFuture<>(false); - } - - if (draftText != null) { - composeText.setText(""); - composeText.append(draftText); - result.set(true); - } - - if (draftMedia != null && draftMediaType != null) { - return setMedia(draftMedia, draftMediaType); - } - - if (draftText == null && draftMedia == null && draftMediaType == null) { - return initializeDraftFromDatabase(); - } else { - updateToggleButtonState(); - result.set(false); - } - - return result; - } - - private void initializeEnabledCheck() { - boolean enabled = !(isPushGroupConversation() && !isActiveGroup()); - inputPanel.setEnabled(enabled); - sendButton.setEnabled(enabled); - attachButton.setEnabled(enabled); - } - - private ListenableFuture initializeDraftFromDatabase() { - SettableFuture future = new SettableFuture<>(); - - new AsyncTask>() { - @Override - protected List doInBackground(Void... params) { - DraftDatabase draftDatabase = DatabaseFactory.getDraftDatabase(ConversationActivity.this); - List results = draftDatabase.getDrafts(threadId); - - draftDatabase.clearDrafts(threadId); - - return results; - } - - @Override - protected void onPostExecute(List drafts) { - if (drafts.isEmpty()) { - future.set(false); - updateToggleButtonState(); - return; - } - - AtomicInteger draftsRemaining = new AtomicInteger(drafts.size()); - AtomicBoolean success = new AtomicBoolean(false); - ListenableFuture.Listener listener = new AssertedSuccessListener() { - @Override - public void onSuccess(Boolean result) { - success.compareAndSet(false, result); - - if (draftsRemaining.decrementAndGet() <= 0) { - future.set(success.get()); - } - } - }; - - for (Draft draft : drafts) { - switch (draft.getType()) { - case Draft.TEXT: - composeText.setText(draft.getValue()); - listener.onSuccess(true); - break; - case Draft.IMAGE: - setMedia(Uri.parse(draft.getValue()), MediaType.IMAGE).addListener(listener); - break; - case Draft.AUDIO: - setMedia(Uri.parse(draft.getValue()), MediaType.AUDIO).addListener(listener); - break; - case Draft.VIDEO: - setMedia(Uri.parse(draft.getValue()), MediaType.VIDEO).addListener(listener); - break; - case Draft.QUOTE: - SettableFuture quoteResult = new SettableFuture<>(); - new QuoteRestorationTask(draft.getValue(), quoteResult).execute(); - quoteResult.addListener(listener); - break; - } - } - - updateToggleButtonState(); - } - }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); - - return future; - } - - private ListenableFuture initializeSecurity(final boolean currentSecureText, - final boolean currentIsDefaultSms) - { - final SettableFuture future = new SettableFuture<>(); - - handleSecurityChange(currentSecureText || isPushGroupConversation(), currentIsDefaultSms); - - new AsyncTask() { - @Override - protected boolean[] doInBackground(Recipient... params) { - // Loki - Override the flag below - boolean signalEnabled = true; // TextSecurePreferences.isPushRegistered(context); - - - return new boolean[] { signalEnabled, false}; - } - - @Override - protected void onPostExecute(boolean[] result) { - if (result[0] != currentSecureText || result[1] != currentIsDefaultSms) { - Log.i(TAG, "onPostExecute() handleSecurityChange: " + result[0] + " , " + result[1]); - handleSecurityChange(result[0], result[1]); - } - future.set(true); - } - }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, recipient); - - return future; - } - - private void initializeViews() { - profilePictureView = findViewById(R.id.profilePictureView); - titleTextView = findViewById(R.id.titleTextView); - buttonToggle = ViewUtil.findById(this, R.id.button_toggle); - sendButton = ViewUtil.findById(this, R.id.send_button); - attachButton = ViewUtil.findById(this, R.id.attach_button); - composeText = ViewUtil.findById(this, R.id.embedded_text_editor); - emojiDrawerStub = ViewUtil.findStubById(this, R.id.emoji_drawer_stub); - unblockButton = ViewUtil.findById(this, R.id.unblock_button); - makeDefaultSmsButton = ViewUtil.findById(this, R.id.make_default_sms_button); - container = ViewUtil.findById(this, R.id.layout_container); - quickAttachmentToggle = ViewUtil.findById(this, R.id.quick_attachment_toggle); - inlineAttachmentToggle = ViewUtil.findById(this, R.id.inline_attachment_container); - inputPanel = ViewUtil.findById(this, R.id.bottom_panel); - searchNav = ViewUtil.findById(this, R.id.conversation_search_nav); - mentionCandidateSelectionViewContainer = ViewUtil.findById(this, R.id.mentionCandidateSelectionViewContainer); - mentionCandidateSelectionView = ViewUtil.findById(this, R.id.userSelectionView); - messageStatusProgressBar = ViewUtil.findById(this, R.id.messageStatusProgressBar); - muteIndicatorImageView = ViewUtil.findById(this, R.id.muteIndicatorImageView); - subtitleTextView = ViewUtil.findById(this, R.id.subtitleTextView); - homeButtonContainer = ViewUtil.findById(this, R.id.homeButtonContainer); - - ImageButton quickCameraToggle = ViewUtil.findById(this, R.id.quick_camera_toggle); - ImageButton inlineAttachmentButton = ViewUtil.findById(this, R.id.inline_attachment_button); - - container.addOnKeyboardShownListener(this); - inputPanel.setListener(this); - inputPanel.setMediaListener(this); - - attachmentTypeSelector = null; - attachmentManager = new AttachmentManager(this, this); - audioRecorder = new AudioRecorder(this); - typingTextWatcher = new TypingStatusTextWatcher(); - mentionTextWatcher = new MentionTextWatcher(); - - SendButtonListener sendButtonListener = new SendButtonListener(); - ComposeKeyPressedListener composeKeyPressedListener = new ComposeKeyPressedListener(); - - composeText.setOnEditorActionListener(sendButtonListener); - composeText.setCursorPositionChangedListener(this); - attachButton.setOnClickListener(new AttachButtonListener()); - attachButton.setOnLongClickListener(new AttachButtonLongClickListener()); - sendButton.setOnClickListener(sendButtonListener); - sendButton.setEnabled(true); - - unblockButton.setOnClickListener(v -> handleUnblock()); - makeDefaultSmsButton.setOnClickListener(v -> handleMakeDefaultSms()); - - composeText.setOnKeyListener(composeKeyPressedListener); - composeText.addTextChangedListener(composeKeyPressedListener); - composeText.setOnEditorActionListener(sendButtonListener); - composeText.setOnClickListener(composeKeyPressedListener); - composeText.setOnFocusChangeListener(composeKeyPressedListener); - - if (getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA) && Camera.getNumberOfCameras() > 0) { - quickCameraToggle.setVisibility(View.VISIBLE); - quickCameraToggle.setOnClickListener(new QuickCameraToggleListener()); - } else { - quickCameraToggle.setVisibility(View.GONE); - } - - searchNav.setEventListener(this); - - inlineAttachmentButton.setOnClickListener(v -> handleAddAttachment()); - - homeButtonContainer.setOnClickListener(v -> onSupportNavigateUp()); - } - - protected void initializeActionBar() { - Toolbar toolbar = findViewById(R.id.toolbar); - setSupportActionBar(toolbar); - - ActionBar supportActionBar = getSupportActionBar(); - if (supportActionBar == null) throw new AssertionError(); - -// supportActionBar.setDisplayHomeAsUpEnabled(true); - supportActionBar.setDisplayShowTitleEnabled(false); - } - - private void initializeResources() { - if (recipient != null) recipient.removeListener(this); - - Address address = getIntent().getParcelableExtra(ADDRESS_EXTRA); - if (address == null) { finish(); return; } - recipient = Recipient.from(this, address, true); - threadId = getIntent().getLongExtra(THREAD_ID_EXTRA, -1); - distributionType = getIntent().getIntExtra(DISTRIBUTION_TYPE_EXTRA, DistributionTypes.DEFAULT); - glideRequests = GlideApp.with(this); - - recipient.addListener(this); - } - - private void initializeLinkPreviewObserver() { - linkPreviewViewModel = ViewModelProviders.of(this, new LinkPreviewViewModel.Factory(new LinkPreviewRepository(this))).get(LinkPreviewViewModel.class); - - if (!TextSecurePreferences.isLinkPreviewsEnabled(this)) { - linkPreviewViewModel.onUserCancel(); - return; - } - - linkPreviewViewModel.getLinkPreviewState().observe(this, previewState -> { - if (previewState == null) return; - - if (previewState.isLoading()) { - Log.d(TAG, "Loading link preview."); - inputPanel.setLinkPreviewLoading(); - } else { - Log.d(TAG, "Setting link preview: " + previewState.getLinkPreview().isPresent()); - inputPanel.setLinkPreview(glideRequests, previewState.getLinkPreview()); - } - - updateToggleButtonState(); - }); - } - - private void initializeSearchObserver() { - searchViewModel = ViewModelProviders.of(this).get(ConversationSearchViewModel.class); - - searchViewModel.getSearchResults().observe(this, result -> { - if (result == null) return; - - if (!result.getResults().isEmpty()) { - MessageResult messageResult = result.getResults().get(result.getPosition()); - fragment.jumpToMessage(messageResult.messageRecipient.getAddress(), messageResult.receivedTimestampMs, searchViewModel::onMissingResult); - } - - searchNav.setData(result.getPosition(), result.getResults().size()); - }); - } - - @Override - public void onSearchMoveUpPressed() { - searchViewModel.onMoveUp(); - } - - @Override - public void onSearchMoveDownPressed() { - searchViewModel.onMoveDown(); - } - - @Override - public void onModified(final Recipient recipient) { - Log.i(TAG, "onModified(" + recipient.getAddress().serialize() + ")"); - Util.runOnMain(() -> { - Log.i(TAG, "onModifiedRun(): " + recipient.getRegistered()); - updateTitleTextView(recipient); - updateProfilePicture(); - updateSubtitleTextView(); - updateInputUI(recipient); - initializeSecurity(true, isDefaultSms); - - if (searchViewItem == null || !searchViewItem.isActionViewExpanded()) { - invalidateOptionsMenu(); - } - }); - } - - @Subscribe(threadMode = ThreadMode.MAIN) - public void onOpenGroupInfoUpdated(OpenGroupUtilities.GroupInfoUpdatedEvent event) { - OpenGroupV2 openGroup = DatabaseFactory.getLokiThreadDatabase(this).getOpenGroupChat(threadId); - if (openGroup != null && - openGroup.getRoom().equals(event.getRoom()) && - openGroup.getServer().equals(event.getUrl())) { - this.updateSubtitleTextView(); - } - } - - //////// Helper Methods - - private void addAttachment(int type) { - linkPreviewViewModel.onUserCancel(); - - Log.i(TAG, "Selected: " + type); - switch (type) { - case AttachmentTypeSelector.ADD_GALLERY: - AttachmentManager.selectGallery(this, MEDIA_SENDER, recipient, composeText.getTextTrimmed()); break; - case AttachmentTypeSelector.ADD_DOCUMENT: - AttachmentManager.selectDocument(this, PICK_DOCUMENT); break; - case AttachmentTypeSelector.ADD_SOUND: - AttachmentManager.selectAudio(this, PICK_AUDIO); break; - case AttachmentTypeSelector.ADD_CONTACT_INFO: - break; - case AttachmentTypeSelector.ADD_LOCATION: - break; - case AttachmentTypeSelector.TAKE_PHOTO: - attachmentManager.capturePhoto(this, TAKE_PHOTO); break; - case AttachmentTypeSelector.ADD_GIF: - boolean hasSeenGIFMetaDataWarning = TextSecurePreferences.hasSeenGIFMetaDataWarning(this); - if (!hasSeenGIFMetaDataWarning) { - AlertDialog.Builder builder = new AlertDialog.Builder(this); - builder.setTitle("Search GIFs?"); - builder.setMessage("You will not have full metadata protection when sending GIFs."); - builder.setPositiveButton("OK", (dialog, which) -> { - AttachmentManager.selectGif(this, PICK_GIF); - dialog.dismiss(); - }); - builder.setNegativeButton("Cancel", (dialog, which) -> dialog.dismiss()); - builder.create().show(); - TextSecurePreferences.setHasSeenGIFMetaDataWarning(this); - } else { - AttachmentManager.selectGif(this, PICK_GIF); - } - break; - } - } - - private ListenableFuture setMedia(@Nullable Uri uri, @NonNull MediaType mediaType) { - return setMedia(uri, mediaType, 0, 0); - } - - private ListenableFuture setMedia(@Nullable Uri uri, @NonNull MediaType mediaType, int width, int height) { - if (uri == null) { - return new SettableFuture<>(false); - } - - if (MediaType.VCARD.equals(mediaType)) { - return new SettableFuture<>(false); - } else if (MediaType.IMAGE.equals(mediaType) || MediaType.GIF.equals(mediaType) || MediaType.VIDEO.equals(mediaType)) { - Media media = new Media(uri, MediaUtil.getMimeType(this, uri), 0, width, height, 0, Optional.absent(), Optional.absent()); - startActivityForResult(MediaSendActivity.buildEditorIntent(ConversationActivity.this, Collections.singletonList(media), recipient, composeText.getTextTrimmed()), MEDIA_SENDER); - return new SettableFuture<>(false); - } else { - return attachmentManager.setMedia(glideRequests, uri, mediaType, MediaConstraints.getPushMediaConstraints(), width, height); - } - } - - private void addAttachmentContactInfo(Uri contactUri) { - ContactAccessor contactDataList = ContactAccessor.getInstance(); - ContactData contactData = contactDataList.getContactData(this, contactUri); - - if (contactData.numbers.size() == 1) composeText.append(contactData.numbers.get(0).number); - else if (contactData.numbers.size() > 1) selectContactInfo(contactData); - } - - private void selectContactInfo(ContactData contactData) { - final CharSequence[] numbers = new CharSequence[contactData.numbers.size()]; - final CharSequence[] numberItems = new CharSequence[contactData.numbers.size()]; - - for (int i = 0; i < contactData.numbers.size(); i++) { - numbers[i] = contactData.numbers.get(i).number; - numberItems[i] = contactData.numbers.get(i).type + ": " + contactData.numbers.get(i).number; - } - - AlertDialog.Builder builder = new AlertDialog.Builder(this); - builder.setIconAttribute(R.attr.conversation_attach_contact_info); - builder.setTitle(R.string.ConversationActivity_select_contact_info); - - builder.setItems(numberItems, (dialog, which) -> composeText.append(numbers[which])); - builder.show(); - } - - private Drafts getDraftsForCurrentState() { - Drafts drafts = new Drafts(); - - if (!org.thoughtcrime.securesms.util.Util.isEmpty(composeText)) { - drafts.add(new Draft(Draft.TEXT, composeText.getTextTrimmed())); - } - - for (Slide slide : attachmentManager.buildSlideDeck().getSlides()) { - if (slide.hasAudio() && slide.getUri() != null) drafts.add(new Draft(Draft.AUDIO, slide.getUri().toString())); - else if (slide.hasVideo() && slide.getUri() != null) drafts.add(new Draft(Draft.VIDEO, slide.getUri().toString())); - else if (slide.hasImage() && slide.getUri() != null) drafts.add(new Draft(Draft.IMAGE, slide.getUri().toString())); - } - - Optional quote = inputPanel.getQuote(); - - if (quote.isPresent()) { - drafts.add(new Draft(Draft.QUOTE, new QuoteId(quote.get().getId(), quote.get().getAuthor()).serialize())); - } - - return drafts; - } - - protected ListenableFuture saveDraft() { - final SettableFuture future = new SettableFuture<>(); - - if (this.recipient == null) { - future.set(threadId); - return future; - } - - final Drafts drafts = getDraftsForCurrentState(); - final long thisThreadId = this.threadId; - final int thisDistributionType = this.distributionType; - - new AsyncTask() { - @Override - protected Long doInBackground(Long... params) { - ThreadDatabase threadDatabase = DatabaseFactory.getThreadDatabase(ConversationActivity.this); - DraftDatabase draftDatabase = DatabaseFactory.getDraftDatabase(ConversationActivity.this); - long threadId = params[0]; - - if (drafts.size() > 0) { - if (threadId == -1) threadId = threadDatabase.getOrCreateThreadIdFor(getRecipient(), thisDistributionType); - - draftDatabase.insertDrafts(threadId, drafts); - threadDatabase.updateSnippet(threadId, drafts.getSnippet(ConversationActivity.this), - drafts.getUriSnippet(), - System.currentTimeMillis(), Types.BASE_DRAFT_TYPE, true); - } else if (threadId > 0) { - threadDatabase.update(threadId, false); - } - - return threadId; - } - - @Override - protected void onPostExecute(Long result) { - future.set(result); - } - - }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, thisThreadId); - - return future; - } - - private void updateInputUI(Recipient recipient) { - if (recipient.isGroupRecipient() && !isActiveGroup()) { - unblockButton.setVisibility(View.GONE); - inputPanel.setVisibility(View.GONE); - makeDefaultSmsButton.setVisibility(View.GONE); - } else if (recipient.isBlocked()) { - unblockButton.setVisibility(View.VISIBLE); - inputPanel.setVisibility(View.GONE); - makeDefaultSmsButton.setVisibility(View.GONE); - } else { - inputPanel.setVisibility(View.VISIBLE); - unblockButton.setVisibility(View.GONE); - makeDefaultSmsButton.setVisibility(View.GONE); - } - } - - private void initializeMediaKeyboardProviders(@NonNull MediaKeyboard mediaKeyboard) { - boolean isSystemEmojiPreferred = TextSecurePreferences.isSystemEmojiPreferred(this); - if (!isSystemEmojiPreferred) { - mediaKeyboard.setProviders(0, new EmojiKeyboardProvider(this, inputPanel)); - } - } - - - private boolean isSingleConversation() { - return getRecipient() != null && !getRecipient().isGroupRecipient(); - } - - private boolean isActiveGroup() { - if (!isGroupConversation()) return false; - - Optional record = DatabaseFactory.getGroupDatabase(this).getGroup(getRecipient().getAddress().toGroupString()); - return record.isPresent() && record.get().isActive(); - } - - @SuppressWarnings("SimplifiableIfStatement") - private boolean isSelfConversation() { - if (!TextSecurePreferences.isPushRegistered(this)) return false; - if (recipient.isGroupRecipient()) return false; - - return Util.isOwnNumber(this, recipient.getAddress().serialize()); - } - - private boolean isGroupConversation() { - return getRecipient() != null && getRecipient().isGroupRecipient(); - } - - private boolean isPushGroupConversation() { - return getRecipient() != null && getRecipient().isPushGroupRecipient(); - } - - protected Recipient getRecipient() { - return this.recipient; - } - - protected long getThreadId() { - return this.threadId; - } - - private String getMessage() throws InvalidMessageException { - String result = composeText.getTextTrimmed(); - if (result.length() < 1) throw new InvalidMessageException(); - for (Mention mention : mentions) { - try { - int startIndex = result.indexOf("@" + mention.getDisplayName()); - int endIndex = startIndex + mention.getDisplayName().length() + 1; // + 1 to include the @ - result = result.substring(0, startIndex) + "@" + mention.getPublicKey() + result.substring(endIndex); - } catch (Exception exception) { - Log.d("Loki", "Couldn't process mention due to error: " + exception.toString() + "."); - } - } - return result; - } - - private Pair> getSplitMessage(String rawText, int maxPrimaryMessageSize) { - String bodyText = rawText; - Optional textSlide = Optional.absent(); - - if (bodyText.length() > maxPrimaryMessageSize) { - bodyText = rawText.substring(0, maxPrimaryMessageSize); - - byte[] textData = rawText.getBytes(); - String timestamp = new SimpleDateFormat("yyyy-MM-dd-HHmmss", Locale.US).format(new Date()); - String filename = String.format("signal-%s.txt", timestamp); - Uri textUri = BlobProvider.getInstance() - .forData(textData) - .withMimeType(MediaTypes.LONG_TEXT) - .withFileName(filename) - .createForSingleSessionInMemory(); - - textSlide = Optional.of(new TextSlide(this, textUri, filename, textData.length)); - } - - return new Pair<>(bodyText, textSlide); - } - - private void markThreadAsRead() { - Recipient recipient = this.recipient; - new AsyncTask() { - @Override - protected Void doInBackground(Long... params) { - Context context = ConversationActivity.this; - List messageIds = DatabaseFactory.getThreadDatabase(context).setRead(params[0], false); - - if (!SessionMetaProtocol.shouldSendReadReceipt(recipient.getAddress())) { - for (MarkedMessageInfo messageInfo : messageIds) { - MarkReadReceiver.scheduleDeletion(context, messageInfo.getExpirationInfo()); - } - } else { - MarkReadReceiver.process(context, messageIds); - } - ApplicationContext.getInstance(context).messageNotifier.updateNotification(context); - return null; - } - }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, threadId); - } - - private void markLastSeen() { - new AsyncTask() { - @Override - protected Void doInBackground(Long... params) { - DatabaseFactory.getThreadDatabase(ConversationActivity.this).setLastSeen(params[0]); - return null; - } - }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, threadId); - } - - protected void sendComplete(long threadId) { - boolean refreshFragment = (threadId != this.threadId); - this.threadId = threadId; - - if (fragment == null || !fragment.isVisible() || isFinishing()) { - return; - } - - fragment.setLastSeen(0); - - if (refreshFragment) { - fragment.reload(recipient, threadId); - ApplicationContext.getInstance(this).messageNotifier.setVisibleThread(threadId); - } - - fragment.scrollToBottom(); - attachmentManager.cleanup(); - - updateLinkPreviewState(); - } - - private void sendMessage() { - if (inputPanel.isRecordingInLockedMode()) { - inputPanel.releaseRecordingLock(); - return; - } - - try { - Recipient recipient = getRecipient(); - - if (recipient == null) { - throw new RecipientFormattingException("Badly formatted"); - } - - String message = getMessage(); - boolean initiating = threadId == -1; - boolean needsSplit = message.length() > characterCalculator.calculateCharacters(message).maxPrimaryMessageSize; - boolean isMediaMessage = false || -// recipient.isGroupRecipient() || - inputPanel.getQuote().isPresent() || - linkPreviewViewModel.hasLinkPreview() || - LinkPreviewUtil.isValidMediaUrl(message) || // Loki - Send GIFs as media messages - needsSplit; - - if (isMediaMessage) { - sendMediaMessage(initiating); - } else { - sendTextMessage(initiating); - } - } catch (RecipientFormattingException ex) { - Log.w(TAG, ex); - } catch (InvalidMessageException ex) { - Log.w(TAG, ex); - } - - if (messageStatus == null && !isGroupConversation() && !(TextSecurePreferences.getLocalNumber(this).equals(recipient.getAddress().serialize()))) { - messageStatus = "calculatingPoW"; - updateSubtitleTextView(); - updateMessageStatusProgressBar(); - } - } - - private void sendMediaMessage(boolean initiating) - throws InvalidMessageException - { - Log.i(TAG, "Sending media message..."); - sendMediaMessage(getMessage(), attachmentManager.buildSlideDeck(), inputPanel.getQuote().orNull(), linkPreviewViewModel.getActiveLinkPreview(), initiating); - } - - private ListenableFuture sendMediaMessage(String body, - SlideDeck slideDeck, - QuoteModel quote, - Optional linkPreview, - final boolean initiating) - { - - Pair> splitMessage = getSplitMessage(body, characterCalculator.calculateCharacters(body).maxPrimaryMessageSize); - body = splitMessage.first; - - if (splitMessage.second.isPresent()) { - slideDeck.addSlide(splitMessage.second.get()); - } - - List attachments = slideDeck.asAttachments(); - - VisibleMessage message = new VisibleMessage(); - message.setSentTimestamp(System.currentTimeMillis()); - message.setText(body); - OutgoingMediaMessage outgoingMessageCandidate = OutgoingMediaMessage.from(message, recipient, attachments, quote, linkPreview.orNull()); - - final SettableFuture future = new SettableFuture<>(); - final Context context = getApplicationContext(); - - final OutgoingMediaMessage outgoingMessage; - - outgoingMessage = new OutgoingSecureMediaMessage(outgoingMessageCandidate); - ApplicationContext.getInstance(context).getTypingStatusSender().onTypingStopped(threadId); - - inputPanel.clearQuote(); - attachmentManager.clear(); - silentlySetComposeText(""); - - final long id = fragment.stageOutgoingMessage(outgoingMessage); - - if (initiating) { - DatabaseFactory.getRecipientDatabase(context).setProfileSharing(recipient, true); - } - - try { - long allocatedThreadId = getAllocatedThreadId(context); - message.setId(DatabaseFactory.getMmsDatabase(context).insertMessageOutbox(outgoingMessage, allocatedThreadId, false, ()->fragment.releaseOutgoingMessage(id))); - MessageSender.send(message, recipient.getAddress(), attachments, quote, linkPreview.orNull()); - sendComplete(allocatedThreadId); - } catch (MmsException e) { - Log.w(TAG, e); - sendComplete(threadId); - } - future.set(null); - - return future; - } - - private void sendTextMessage(final boolean initiating) - throws InvalidMessageException - { - final Context context = getApplicationContext(); - final String messageBody = getMessage(); - - VisibleMessage message = new VisibleMessage(); - message.setSentTimestamp(System.currentTimeMillis()); - message.setText(messageBody); - OutgoingTextMessage outgoingTextMessage = OutgoingTextMessage.from(message, recipient); - ApplicationContext.getInstance(context).getTypingStatusSender().onTypingStopped(threadId); - - silentlySetComposeText(""); - final long id = fragment.stageOutgoingMessage(outgoingTextMessage); - - if (initiating) { - DatabaseFactory.getRecipientDatabase(context).setProfileSharing(recipient, true); - } - - long allocatedThreadId = getAllocatedThreadId(context); - message.setId(DatabaseFactory.getSmsDatabase(context).insertMessageOutbox(allocatedThreadId, outgoingTextMessage, false, message.getSentTimestamp(), ()->fragment.releaseOutgoingMessage(id))); - MessageSender.send(message, recipient.getAddress()); - - sendComplete(allocatedThreadId); - } - - private void sendOpenGroupInvitations(String[] contactIDs) { - final Context context = getApplicationContext(); - OpenGroupV2 openGroup = DatabaseFactory.getLokiThreadDatabase(context).getOpenGroupChat(threadId); - for (String contactID : contactIDs) { - Recipient recipient = Recipient.from(context, Address.fromSerialized(contactID), true); - VisibleMessage message = new VisibleMessage(); - message.setSentTimestamp(System.currentTimeMillis()); - OpenGroupInvitation openGroupInvitationMessage = new OpenGroupInvitation(); - openGroupInvitationMessage.setName(openGroup.getName()); - openGroupInvitationMessage.setUrl(openGroup.getJoinURL()); - message.setOpenGroupInvitation(openGroupInvitationMessage); - OutgoingTextMessage outgoingTextMessage = OutgoingTextMessage.fromOpenGroupInvitation(openGroupInvitationMessage, recipient, message.getSentTimestamp()); - DatabaseFactory.getSmsDatabase(context).insertMessageOutbox(-1, outgoingTextMessage, message.getSentTimestamp()); - MessageSender.send(message, recipient.getAddress()); - } - } - - private void updateToggleButtonState() { - if (inputPanel.isRecordingInLockedMode()) { - buttonToggle.display(sendButton); - quickAttachmentToggle.show(); - inlineAttachmentToggle.hide(); - return; - } - - if (composeText.getText().length() == 0) { - buttonToggle.display(attachButton); - quickAttachmentToggle.show(); - inlineAttachmentToggle.hide(); - } else { - buttonToggle.display(sendButton); - quickAttachmentToggle.hide(); - - if (!linkPreviewViewModel.hasLinkPreview()) { - inlineAttachmentToggle.show(); - } else { - inlineAttachmentToggle.hide(); - } - } - } - - private void updateLinkPreviewState() { - if (TextSecurePreferences.isLinkPreviewsEnabled(this)) { - linkPreviewViewModel.onEnabled(); - linkPreviewViewModel.onTextChanged(this, composeText.getTextTrimmed(), composeText.getSelectionStart(), composeText.getSelectionEnd()); - } else { - linkPreviewViewModel.onUserCancel(); - } - } - - @Override - public void onRecorderPermissionRequired() { - Permissions.with(this) - .request(Manifest.permission.RECORD_AUDIO) - .withRationaleDialog(getString(R.string.ConversationActivity_to_send_audio_messages_allow_signal_access_to_your_microphone), R.drawable.ic_baseline_mic_48) - .withPermanentDenialDialog(getString(R.string.ConversationActivity_signal_requires_the_microphone_permission_in_order_to_send_audio_messages)) - .execute(); - } - - @Override - public void onRecorderStarted() { - Vibrator vibrator = ServiceUtil.getVibrator(this); - vibrator.vibrate(20); - - getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); - - audioRecorder.startRecording(); - - audioHandler = new Handler(); - stopRecordingTask = () -> inputPanel.onRecordReleased(); - audioHandler.postDelayed(stopRecordingTask, 60000); - } - - @Override - public void onRecorderLocked() { - updateToggleButtonState(); - } - - @Override - public void onRecorderFinished() { - if (audioHandler != null && stopRecordingTask != null) { - audioHandler.removeCallbacks(stopRecordingTask); - } - updateToggleButtonState(); - Vibrator vibrator = ServiceUtil.getVibrator(this); - vibrator.vibrate(20); - - getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); - - ListenableFuture> future = audioRecorder.stopRecording(); - future.addListener(new ListenableFuture.Listener>() { - @Override - public void onSuccess(final @NonNull Pair result) { - int subscriptionId = -1; - long expiresIn = recipient.getExpireMessages() * 1000L; - boolean initiating = threadId == -1; - AudioSlide audioSlide = new AudioSlide(ConversationActivity.this, result.first, result.second, MediaTypes.AUDIO_AAC, true); - SlideDeck slideDeck = new SlideDeck(); - slideDeck.addSlide(audioSlide); - - sendMediaMessage("", slideDeck, inputPanel.getQuote().orNull(), Optional.absent(), initiating).addListener(new AssertedSuccessListener() { - @Override - public void onSuccess(Void nothing) { - new AsyncTask() { - @Override - protected Void doInBackground(Void... params) { - BlobProvider.getInstance().delete(ConversationActivity.this, result.first); - return null; - } - }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); - } - }); - } - - @Override - public void onFailure(ExecutionException e) { - Toast.makeText(ConversationActivity.this, R.string.ConversationActivity_unable_to_record_audio, Toast.LENGTH_LONG).show(); - } - }); - } - - @Override - public void onRecorderCanceled() { - if (audioHandler != null && stopRecordingTask != null) { - audioHandler.removeCallbacks(stopRecordingTask); - } - updateToggleButtonState(); - Vibrator vibrator = ServiceUtil.getVibrator(this); - vibrator.vibrate(50); - - getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); - - ListenableFuture> future = audioRecorder.stopRecording(); - future.addListener(new ListenableFuture.Listener>() { - @Override - public void onSuccess(final Pair result) { - new AsyncTask() { - @Override - protected Void doInBackground(Void... params) { - BlobProvider.getInstance().delete(ConversationActivity.this, result.first); - return null; - } - }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); - } - - @Override - public void onFailure(ExecutionException e) {} - }); - } - - @Override - public void onEmojiToggle() { - if (!emojiDrawerStub.resolved()) { - initializeMediaKeyboardProviders(emojiDrawerStub.get()); - - inputPanel.setMediaKeyboard(emojiDrawerStub.get()); - } - - if (container.getCurrentInput() == emojiDrawerStub.get()) { - container.showSoftkey(composeText); - } else { - container.show(composeText, emojiDrawerStub.get()); - } - } - - @Override - public void onLinkPreviewCanceled() { - linkPreviewViewModel.onUserCancel(); - } - - @Override - public void onMediaSelected(@NonNull Uri uri, String contentType) { - if (!TextUtils.isEmpty(contentType) && contentType.trim().equals("image/gif")) { - setMedia(uri, MediaType.GIF); - } else if (MediaUtil.isImageType(contentType)) { - setMedia(uri, MediaType.IMAGE); - } else if (MediaUtil.isVideoType(contentType)) { - setMedia(uri, MediaType.VIDEO); - } else if (MediaUtil.isAudioType(contentType)) { - setMedia(uri, MediaType.AUDIO); - } - } - - @Override - public void onCursorPositionChanged(int start, int end) { - linkPreviewViewModel.onTextChanged(this, composeText.getTextTrimmed(), start, end); - } - - private void silentlySetComposeText(String text) { - typingTextWatcher.setEnabled(false); - composeText.setText(text); - if (text.isEmpty()) { resetMentions(); } - typingTextWatcher.setEnabled(true); - } - - // Listeners - - private class AttachmentTypeListener implements AttachmentTypeSelector.AttachmentClickedListener { - @Override - public void onClick(int type) { - addAttachment(type); - } - - @Override - public void onQuickAttachment(Uri uri, String mimeType, String bucketId, long dateTaken, int width, int height, long size) { - linkPreviewViewModel.onUserCancel(); - Media media = new Media(uri, mimeType, dateTaken, width, height, size, Optional.of(Media.ALL_MEDIA_BUCKET_ID), Optional.absent()); - startActivityForResult(MediaSendActivity.buildEditorIntent(ConversationActivity.this, Collections.singletonList(media), recipient, composeText.getTextTrimmed()), MEDIA_SENDER); - } - } - - private class QuickCameraToggleListener implements OnClickListener { - @Override - public void onClick(View v) { - Permissions.with(ConversationActivity.this) - .request(Manifest.permission.CAMERA) - .withRationaleDialog(getString(R.string.ConversationActivity_to_capture_photos_and_video_allow_signal_access_to_the_camera), R.drawable.ic_baseline_photo_camera_48) - .withPermanentDenialDialog(getString(R.string.ConversationActivity_signal_needs_the_camera_permission_to_take_photos_or_video)) - .onAllGranted(() -> { - composeText.clearFocus(); - startActivityForResult(MediaSendActivity.buildCameraIntent(ConversationActivity.this, recipient), MEDIA_SENDER); - overridePendingTransition(R.anim.camera_slide_from_bottom, R.anim.stationary); - }) - .onAnyDenied(() -> Toast.makeText(ConversationActivity.this, R.string.ConversationActivity_signal_needs_camera_permissions_to_take_photos_or_video, Toast.LENGTH_LONG).show()) - .execute(); - } - } - - private class SendButtonListener implements OnClickListener, TextView.OnEditorActionListener { - @Override - public void onClick(View v) { - sendMessage(); - } - - @Override - public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { - if (actionId == EditorInfo.IME_ACTION_SEND) { - sendButton.performClick(); - return true; - } - return false; - } - } - - private class AttachButtonListener implements OnClickListener { - @Override - public void onClick(View v) { - handleAddAttachment(); - } - } - - private class AttachButtonLongClickListener implements View.OnLongClickListener { - @Override - public boolean onLongClick(View v) { - return sendButton.performLongClick(); - } - } - - private class ComposeKeyPressedListener implements OnKeyListener, OnClickListener, TextWatcher, OnFocusChangeListener { - - int beforeLength; - - @Override - public boolean onKey(View v, int keyCode, KeyEvent event) { - if (event.getAction() == KeyEvent.ACTION_DOWN) { - if (keyCode == KeyEvent.KEYCODE_ENTER) { - if (TextSecurePreferences.isEnterSendsEnabled(ConversationActivity.this)) { - sendButton.dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER)); - sendButton.dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_ENTER)); - return true; - } - } - } - return false; - } - - @Override - public void onClick(View v) { - container.showSoftkey(composeText); - } - - @Override - public void beforeTextChanged(CharSequence s, int start, int count,int after) { - beforeLength = composeText.getTextTrimmed().length(); - } - - @Override - public void afterTextChanged(Editable s) { - if (composeText.getTextTrimmed().length() == 0 || beforeLength == 0) { - composeText.postDelayed(ConversationActivity.this::updateToggleButtonState, 50); - } - } - - @Override - public void onTextChanged(CharSequence s, int start, int before,int count) {} - - @Override - public void onFocusChange(View v, boolean hasFocus) {} - } - - private class TypingStatusTextWatcher extends SimpleTextWatcher { - private boolean enabled = true; - - @Override - public void onTextChanged(String text) { - if (enabled && threadId > 0) { - ApplicationContext.getInstance(ConversationActivity.this).getTypingStatusSender().onTypingStarted(threadId); - } - } - - public void setEnabled(boolean enabled) { - this.enabled = enabled; - } - } - - private class MentionTextWatcher extends SimpleTextWatcher { - - @Override - public void onTextChanged(String text) { - boolean isBackspace = text.length() < oldText.length(); - if (isBackspace) { - currentMentionStartIndex = -1; - mentionCandidateSelectionView.hide(); - mentionCandidateSelectionViewContainer.setVisibility(View.GONE); - ArrayList mentionsToRemove = new ArrayList<>(); - for (Mention mention : mentions) { - if (!text.contains(mention.getDisplayName())) { - mentionsToRemove.add(mention); - } - } - mentions.removeAll(mentionsToRemove); - } - if (text.length() > 0) { - if (currentMentionStartIndex > text.length()) { - resetMentions(); // Should never occur - } - int lastCharacterIndex = text.length() - 1; - char lastCharacter = text.charAt(lastCharacterIndex); - char secondToLastCharacter = ' '; - if (lastCharacterIndex > 0) { - secondToLastCharacter = text.charAt(lastCharacterIndex - 1); - } - if (lastCharacter == '@' && Character.isWhitespace(secondToLastCharacter)) { - List mentionCandidates = MentionsManager.INSTANCE.getMentionCandidates("", threadId, recipient.isOpenGroupRecipient()); - currentMentionStartIndex = lastCharacterIndex; - mentionCandidateSelectionViewContainer.setVisibility(View.VISIBLE); - mentionCandidateSelectionView.show(mentionCandidates, threadId); - } else if (Character.isWhitespace(lastCharacter)) { - currentMentionStartIndex = -1; - mentionCandidateSelectionView.hide(); - mentionCandidateSelectionViewContainer.setVisibility(View.GONE); - } else { - if (currentMentionStartIndex != -1) { - String query = text.substring(currentMentionStartIndex + 1); // + 1 to get rid of the @ - List mentionCandidates = MentionsManager.INSTANCE.getMentionCandidates(query, threadId, recipient.isOpenGroupRecipient()); - mentionCandidateSelectionViewContainer.setVisibility(View.VISIBLE); - mentionCandidateSelectionView.show(mentionCandidates, threadId); - } - } - } - ConversationActivity.this.oldText = text; - } - } - - private void resetMentions() { - oldText = ""; - currentMentionStartIndex = -1; - mentions.clear(); - } - - @Override - public void setThreadId(long threadId) { - this.threadId = threadId; - } - - @Override - public void handleReplyMessage(MessageRecord messageRecord) { - if (recipient.isGroupRecipient() && !isActiveGroup()) { return; } - - Recipient author; - - if (messageRecord.isOutgoing()) { - author = Recipient.from(this, Address.fromSerialized(TextSecurePreferences.getLocalNumber(this)), true); - } else { - author = messageRecord.getIndividualRecipient(); - } - - if (messageRecord.isMms() && !((MmsMessageRecord) messageRecord).getSharedContacts().isEmpty()) { - Contact contact = ((MmsMessageRecord) messageRecord).getSharedContacts().get(0); - String displayName = ContactUtil.getDisplayName(contact); - String body = getString(R.string.ConversationActivity_quoted_contact_message, EmojiStrings.BUST_IN_SILHOUETTE, displayName); - SlideDeck slideDeck = new SlideDeck(); - - if (contact.getAvatarAttachment() != null) { - slideDeck.addSlide(MediaUtil.getSlideForAttachment(this, contact.getAvatarAttachment())); - } - - inputPanel.setQuote(GlideApp.with(this), - messageRecord.getDateSent(), - author, - body, - slideDeck, - recipient, - threadId); - - } else if (messageRecord.isMms() && !((MmsMessageRecord) messageRecord).getLinkPreviews().isEmpty()) { - LinkPreview linkPreview = ((MmsMessageRecord) messageRecord).getLinkPreviews().get(0); - SlideDeck slideDeck = new SlideDeck(); - - if (linkPreview.getThumbnail().isPresent()) { - slideDeck.addSlide(MediaUtil.getSlideForAttachment(this, linkPreview.getThumbnail().get())); - } - - inputPanel.setQuote(GlideApp.with(this), - messageRecord.getDateSent(), - author, - messageRecord.getBody(), - slideDeck, - recipient, - threadId); - } else { - inputPanel.setQuote(GlideApp.with(this), - messageRecord.getDateSent(), - author, - messageRecord.getBody(), - messageRecord.isMms() ? ((MmsMessageRecord) messageRecord).getSlideDeck() : new SlideDeck(), - recipient, - threadId); - } - } - - @Override - public void onMessageActionToolbarOpened() { - searchViewItem.collapseActionView(); - } - - @Override - public void onForwardClicked() { - inputPanel.clearQuote(); - } - - @Override - public void onAttachmentChanged() { - handleSecurityChange(true, isDefaultSms); - updateToggleButtonState(); - updateLinkPreviewState(); - } - - private class QuoteRestorationTask extends AsyncTask { - - private final String serialized; - private final SettableFuture future; - - QuoteRestorationTask(@NonNull String serialized, @NonNull SettableFuture future) { - this.serialized = serialized; - this.future = future; - } - - @Override - protected MessageRecord doInBackground(Void... voids) { - QuoteId quoteId = QuoteId.deserialize(serialized); - - if (quoteId != null) { - return DatabaseFactory.getMmsSmsDatabase(getApplicationContext()).getMessageFor(quoteId.getId(), quoteId.getAuthor()); - } - - return null; - } - - @Override - protected void onPostExecute(MessageRecord messageRecord) { - if (messageRecord != null) { - handleReplyMessage(messageRecord); - future.set(true); - } else { - Log.e(TAG, "Failed to restore a quote from a draft. No matching message record."); - future.set(false); - } - } - } - - // region Loki - private long getAllocatedThreadId(Context context) { - long allocatedThreadId; - if (threadId == -1) { - allocatedThreadId = DatabaseFactory.getThreadDatabase(context).getOrCreateThreadIdFor(recipient); - } else { - allocatedThreadId = threadId; - } - return allocatedThreadId; - } - - private void updateTitleTextView(Recipient recipient) { - String userPublicKey = TextSecurePreferences.getLocalNumber(this); - if (recipient == null) { - titleTextView.setText(R.string.ConversationActivity_compose); - } else if (recipient.getAddress().toString().toLowerCase().equals(userPublicKey)) { - titleTextView.setText(R.string.note_to_self); - } else { - String displayName = recipient.getName(); // Uses the Contact API internally - boolean hasName = (displayName != null); - titleTextView.setText(hasName ? displayName : recipient.getAddress().toString()); - } - } - - private void updateProfilePicture() { - try { - profilePictureView.glide = GlideApp.with(this); - profilePictureView.update(recipient, threadId); - } catch (Exception exception) { - // Do nothing - } - } - - private void updateSubtitleTextView() { - muteIndicatorImageView.setVisibility(View.GONE); - subtitleTextView.setVisibility(View.VISIBLE); - if (recipient.isMuted()) { - muteIndicatorImageView.setVisibility(View.VISIBLE); - subtitleTextView.setText(getString(R.string.ConversationActivity_muted_until_date,DateUtils.getFormattedDateTime(recipient.mutedUntil, "EEE, MMM d, yyyy HH:mm", Locale.getDefault()))); - } else if (recipient.isGroupRecipient() && recipient.getName() != null && !recipient.getName().equals("Session Updates") && !recipient.getName().equals("Loki News")) { - OpenGroupV2 openGroup = DatabaseFactory.getLokiThreadDatabase(this).getOpenGroupChat(threadId); - if (openGroup != null) { - Integer userCount = DatabaseFactory.getLokiAPIDatabase(this).getUserCount(openGroup.getRoom(),openGroup.getServer()); - if (userCount == null) { userCount = 0; } - subtitleTextView.setText(getString(R.string.ConversationActivity_member_count,userCount)); - } else if (PublicKeyValidation.isValid(recipient.getAddress().toString())) { - subtitleTextView.setText(recipient.getAddress().toString()); - } else { - subtitleTextView.setVisibility(View.GONE); - } - } else { - subtitleTextView.setVisibility(View.GONE); - } - titleTextView.setTextSize(TypedValue.COMPLEX_UNIT_PX, getResources().getDimension((subtitleTextView.getVisibility() == View.GONE) ? R.dimen.very_large_font_size : R.dimen.large_font_size)); - } - - private void setMessageStatusProgressAnimatedIfPossible(int progress) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - messageStatusProgressBar.setProgress(progress, true); - } else { - messageStatusProgressBar.setProgress(progress); - } - } - - private void updateMessageStatusProgressBar() { - if (messageStatus != null) { - messageStatusProgressBar.setAlpha(1.0f); - switch (messageStatus) { - case "calculatingPoW": setMessageStatusProgressAnimatedIfPossible(25); break; - case "contactingNetwork": setMessageStatusProgressAnimatedIfPossible(50); break; - case "sendingMessage": setMessageStatusProgressAnimatedIfPossible(75); break; - case "messageSent": - setMessageStatusProgressAnimatedIfPossible(100); - new Handler().postDelayed(() -> messageStatusProgressBar.animate().alpha(0).setDuration(250).start(), 250); - new Handler().postDelayed(() -> messageStatusProgressBar.setProgress(0), 500); - break; - case "messageFailed": - messageStatusProgressBar.animate().alpha(0).setDuration(250).start(); - new Handler().postDelayed(() -> messageStatusProgressBar.setProgress(0), 250); - break; - } - } - } - - private void handleMessageStatusChanged(String newMessageStatus, long timestamp) { - if (timestamp == 0 || (TextSecurePreferences.getLocalNumber(this).equals(recipient.getAddress().serialize())) ) { return; } - updateForNewMessageStatusIfNeeded(newMessageStatus, timestamp); - if (newMessageStatus.equals("messageFailed") || newMessageStatus.equals("messageSent")) { - new Handler().postDelayed(() -> clearMessageStatusIfNeeded(timestamp), 1000); - } - } - - private int precedence(String messageStatus) { - if (messageStatus != null) { - switch (messageStatus) { - case "calculatingPoW": return 0; - case "contactingNetwork": return 1; - case "sendingMessage": return 2; - case "messageSent": return 3; - case "messageFailed": return 4; - default: return -1; - } - } else { - return -1; - } - } - - private void updateForNewMessageStatusIfNeeded(String newMessageStatus, long timestamp) { - if (!DatabaseFactory.getSmsDatabase(this).isOutgoingMessage(timestamp) && !DatabaseFactory.getMmsDatabase(this).isOutgoingMessage(timestamp)) { return; } - if (precedence(newMessageStatus) > precedence(messageStatus)) { - messageStatus = newMessageStatus; - updateSubtitleTextView(); - updateMessageStatusProgressBar(); - } - } - - private void clearMessageStatusIfNeeded(long timestamp) { - if (!DatabaseFactory.getSmsDatabase(this).isOutgoingMessage(timestamp) && !DatabaseFactory.getMmsDatabase(this).isOutgoingMessage(timestamp)) { return; } - messageStatus = null; - updateSubtitleTextView(); - updateMessageStatusProgressBar(); - } - // endregion -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationAdapter.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationAdapter.java deleted file mode 100644 index 8c7522a3f..000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationAdapter.java +++ /dev/null @@ -1,532 +0,0 @@ -/* - * Copyright (C) 2011 Whisper Systems - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package org.thoughtcrime.securesms.conversation; - -import android.content.Context; -import android.database.Cursor; -import androidx.annotation.LayoutRes; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.annotation.VisibleForTesting; -import androidx.recyclerview.widget.RecyclerView; - -import android.util.SparseArray; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.TextView; - -import com.annimon.stream.Stream; - -import org.thoughtcrime.securesms.BindableConversationItem; -import org.thoughtcrime.securesms.conversation.ConversationAdapter.HeaderViewHolder; -import org.thoughtcrime.securesms.database.DatabaseFactory; -import org.thoughtcrime.securesms.database.FastCursorRecyclerViewAdapter; -import org.thoughtcrime.securesms.database.MmsSmsColumns; -import org.thoughtcrime.securesms.database.MmsSmsDatabase; -import org.thoughtcrime.securesms.database.model.MessageRecord; -import org.thoughtcrime.securesms.database.model.MmsMessageRecord; -import org.session.libsignal.utilities.Log; -import org.thoughtcrime.securesms.mms.GlideRequests; -import org.thoughtcrime.securesms.mms.SlideDeck; -import org.session.libsession.utilities.recipients.Recipient; -import org.thoughtcrime.securesms.util.DateUtils; -import org.thoughtcrime.securesms.util.LRUCache; -import org.thoughtcrime.securesms.util.StickyHeaderDecoration; -import org.session.libsignal.utilities.guava.Optional; - -import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAttachment; -import org.session.libsession.utilities.Conversions; -import org.session.libsession.utilities.ViewUtil; -import org.session.libsession.utilities.Util; - -import java.lang.ref.SoftReference; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.util.Calendar; -import java.util.Collections; -import java.util.Date; -import java.util.HashSet; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.Set; - -import network.loki.messenger.R; - -/** - * A cursor adapter for a conversation thread. Ultimately - * used by ComposeMessageActivity to display a conversation - * thread in a ListActivity. - * - * @author Moxie Marlinspike - * - */ -public class ConversationAdapter - extends FastCursorRecyclerViewAdapter - implements StickyHeaderDecoration.StickyHeaderAdapter -{ - - private static final int MAX_CACHE_SIZE = 1000; - private static final String TAG = ConversationAdapter.class.getSimpleName(); - private final Map> messageRecordCache = - Collections.synchronizedMap(new LRUCache>(MAX_CACHE_SIZE)); - private final SparseArray positionToCacheRef = new SparseArray<>(); - - private static final int MESSAGE_TYPE_OUTGOING = 0; - private static final int MESSAGE_TYPE_INCOMING = 1; - private static final int MESSAGE_TYPE_UPDATE = 2; - private static final int MESSAGE_TYPE_AUDIO_OUTGOING = 3; - private static final int MESSAGE_TYPE_AUDIO_INCOMING = 4; - private static final int MESSAGE_TYPE_THUMBNAIL_OUTGOING = 5; - private static final int MESSAGE_TYPE_THUMBNAIL_INCOMING = 6; - private static final int MESSAGE_TYPE_DOCUMENT_OUTGOING = 7; - private static final int MESSAGE_TYPE_DOCUMENT_INCOMING = 8; - private static final int MESSAGE_TYPE_INVITATION_OUTGOING = 9; - private static final int MESSAGE_TYPE_INVITATION_INCOMING = 10; - - private final Set batchSelected = Collections.synchronizedSet(new HashSet()); - - private final @Nullable ItemClickListener clickListener; - private final @NonNull - GlideRequests glideRequests; - private final @NonNull Locale locale; - private final @NonNull Recipient recipient; - private final @NonNull MmsSmsDatabase db; - private final @NonNull LayoutInflater inflater; - private final @NonNull Calendar calendar; - private final @NonNull MessageDigest digest; - - private MessageRecord recordToPulseHighlight; - private String searchQuery; - - protected static class ViewHolder extends RecyclerView.ViewHolder { - public ViewHolder(final @NonNull V itemView) { - super(itemView); - } - - @SuppressWarnings("unchecked") - public V getView() { - return (V)itemView; - } - } - - - static class HeaderViewHolder extends RecyclerView.ViewHolder { - TextView textView; - - HeaderViewHolder(View itemView) { - super(itemView); - textView = ViewUtil.findById(itemView, R.id.text); - } - - HeaderViewHolder(TextView textView) { - super(textView); - this.textView = textView; - } - - public void setText(CharSequence text) { - textView.setText(text); - } - } - - - interface ItemClickListener extends BindableConversationItem.EventListener { - void onItemClick(MessageRecord item); - void onItemLongClick(MessageRecord item); - } - - @SuppressWarnings("ConstantConditions") - @VisibleForTesting - ConversationAdapter(Context context, Cursor cursor) { - super(context, cursor); - try { - this.glideRequests = null; - this.locale = null; - this.clickListener = null; - this.recipient = null; - this.inflater = null; - this.db = null; - this.calendar = null; - this.digest = MessageDigest.getInstance("SHA1"); - } catch (NoSuchAlgorithmException nsae) { - throw new AssertionError("SHA1 isn't supported!"); - } - } - - public ConversationAdapter(@NonNull Context context, - @NonNull GlideRequests glideRequests, - @NonNull Locale locale, - @Nullable ItemClickListener clickListener, - @Nullable Cursor cursor, - @NonNull Recipient recipient) - { - super(context, cursor); - - try { - this.glideRequests = glideRequests; - this.locale = locale; - this.clickListener = clickListener; - this.recipient = recipient; - this.inflater = LayoutInflater.from(context); - this.db = DatabaseFactory.getMmsSmsDatabase(context); - this.calendar = Calendar.getInstance(); - this.digest = MessageDigest.getInstance("SHA1"); - - setHasStableIds(true); - } catch (NoSuchAlgorithmException nsae) { - throw new AssertionError("SHA1 isn't supported!"); - } - } - - @Override - public void changeCursor(Cursor cursor) { - messageRecordCache.clear(); - positionToCacheRef.clear(); - super.cleanFastRecords(); - super.changeCursor(cursor); - } - - @Override - protected void onBindItemViewHolder(ViewHolder viewHolder, @NonNull MessageRecord messageRecord) { - int adapterPosition = viewHolder.getAdapterPosition(); - - String prevCachedId = positionToCacheRef.get(adapterPosition + 1,null); - String nextCachedId = positionToCacheRef.get(adapterPosition - 1, null); - - MessageRecord previousRecord = null; - if (adapterPosition < getItemCount() - 1 && !isFooterPosition(adapterPosition + 1)) { - if (prevCachedId != null && messageRecordCache.containsKey(prevCachedId)) { - SoftReference prevSoftRecord = messageRecordCache.get(prevCachedId); - MessageRecord prevCachedRecord = prevSoftRecord.get(); - if (prevCachedRecord != null) { - previousRecord = prevCachedRecord; - } else { - previousRecord = getRecordForPositionOrThrow(adapterPosition + 1); - } - } else { - previousRecord = getRecordForPositionOrThrow(adapterPosition + 1); - } - } - - MessageRecord nextRecord = null; - if (adapterPosition > 0 && !isHeaderPosition(adapterPosition - 1)) { - if (nextCachedId != null && messageRecordCache.containsKey(nextCachedId)) { - SoftReference nextSoftRecord = messageRecordCache.get(nextCachedId); - MessageRecord nextCachedRecord = nextSoftRecord.get(); - if (nextCachedRecord != null) { - nextRecord = nextCachedRecord; - } else { - nextRecord = getRecordForPositionOrThrow(adapterPosition - 1); - } - } else { - nextRecord = getRecordForPositionOrThrow(adapterPosition - 1); - } - } - - viewHolder.getView().bind(messageRecord, - Optional.fromNullable(previousRecord), - Optional.fromNullable(nextRecord), - glideRequests, - locale, - batchSelected, - recipient, - searchQuery, - messageRecord == recordToPulseHighlight); - - if (messageRecord == recordToPulseHighlight) { - recordToPulseHighlight = null; - } - } - - @Override - public ViewHolder onCreateItemViewHolder(ViewGroup parent, int viewType) { - long start = System.currentTimeMillis(); - final V itemView = ViewUtil.inflate(inflater, parent, getLayoutForViewType(viewType)); - itemView.setOnClickListener(view -> { - if (clickListener != null) { - clickListener.onItemClick(itemView.getMessageRecord()); - } - }); - itemView.setOnLongClickListener(view -> { - if (clickListener != null) { - clickListener.onItemLongClick(itemView.getMessageRecord()); - } - return true; - }); - itemView.setEventListener(clickListener); - Log.d(TAG, "Inflate time: " + (System.currentTimeMillis() - start)); - return new ViewHolder(itemView); - } - - @Override - public void onItemViewRecycled(ViewHolder holder) { - holder.getView().unbind(); - } - - private @LayoutRes int getLayoutForViewType(int viewType) { - switch (viewType) { - case MESSAGE_TYPE_AUDIO_OUTGOING: - case MESSAGE_TYPE_THUMBNAIL_OUTGOING: - case MESSAGE_TYPE_DOCUMENT_OUTGOING: - case MESSAGE_TYPE_INVITATION_OUTGOING: - case MESSAGE_TYPE_OUTGOING: return R.layout.conversation_item_sent; - case MESSAGE_TYPE_AUDIO_INCOMING: - case MESSAGE_TYPE_THUMBNAIL_INCOMING: - case MESSAGE_TYPE_DOCUMENT_INCOMING: - case MESSAGE_TYPE_INVITATION_INCOMING: - case MESSAGE_TYPE_INCOMING: return R.layout.conversation_item_received; - case MESSAGE_TYPE_UPDATE: return R.layout.conversation_item_update; - default: throw new IllegalArgumentException("unsupported item view type given to ConversationAdapter"); - } - } - - @Override - public int getItemViewType(@NonNull MessageRecord messageRecord) { - if (messageRecord.isUpdate()) { - return MESSAGE_TYPE_UPDATE; - } else if (messageRecord.isOpenGroupInvitation()) { - if (messageRecord.isOutgoing()) return MESSAGE_TYPE_INVITATION_OUTGOING; - else return MESSAGE_TYPE_INVITATION_INCOMING; - } else if (hasAudio(messageRecord)) { - if (messageRecord.isOutgoing()) return MESSAGE_TYPE_AUDIO_OUTGOING; - else return MESSAGE_TYPE_AUDIO_INCOMING; - } else if (hasDocument(messageRecord)) { - if (messageRecord.isOutgoing()) return MESSAGE_TYPE_DOCUMENT_OUTGOING; - else return MESSAGE_TYPE_DOCUMENT_INCOMING; - } else if (hasThumbnail(messageRecord)) { - if (messageRecord.isOutgoing()) return MESSAGE_TYPE_THUMBNAIL_OUTGOING; - else return MESSAGE_TYPE_THUMBNAIL_INCOMING; - } else if (messageRecord.isOutgoing()) { - return MESSAGE_TYPE_OUTGOING; - } else { - return MESSAGE_TYPE_INCOMING; - } - } - - @Override - protected boolean isRecordForId(@NonNull MessageRecord record, long id) { - return record.getId() == id; - } - - @Override - public long getItemId(@NonNull Cursor cursor) { - List attachments = DatabaseFactory.getAttachmentDatabase(getContext()).getAttachment(cursor); - List messageAttachments = Stream.of(attachments).filterNot(DatabaseAttachment::isQuote).toList(); - - if (messageAttachments.size() > 0 && messageAttachments.get(0).getFastPreflightId() != null) { - return Long.valueOf(messageAttachments.get(0).getFastPreflightId()); - } - - final String unique = cursor.getString(cursor.getColumnIndexOrThrow(MmsSmsColumns.UNIQUE_ROW_ID)); - final byte[] bytes = digest.digest(unique.getBytes()); - return Conversions.byteArrayToLong(bytes); - } - - @Override - protected long getItemId(@NonNull MessageRecord record) { - if (record.isOutgoing() && record.isMms()) { - MmsMessageRecord mmsRecord = (MmsMessageRecord) record; - SlideDeck slideDeck = mmsRecord.getSlideDeck(); - - if (slideDeck.getThumbnailSlide() != null && slideDeck.getThumbnailSlide().getFastPreflightId() != null) { - return Long.valueOf(slideDeck.getThumbnailSlide().getFastPreflightId()); - } - } - - return record.getId(); - } - - @Override - protected MessageRecord getRecordFromCursor(@NonNull Cursor cursor) { - long messageId = cursor.getLong(cursor.getColumnIndexOrThrow(MmsSmsColumns.ID)); - String type = cursor.getString(cursor.getColumnIndexOrThrow(MmsSmsDatabase.TRANSPORT)); - - final SoftReference reference = messageRecordCache.get(type + messageId); - if (reference != null) { - final MessageRecord record = reference.get(); - if (record != null) return record; - } - - final MessageRecord messageRecord = db.readerFor(cursor).getCurrent(); - messageRecordCache.put(type + messageId, new SoftReference<>(messageRecord)); - - return messageRecord; - } - - public void close() { - getCursor().close(); - } - - public int findLastSeenPosition(long lastSeen) { - if (lastSeen <= 0) return -1; - if (!isActiveCursor()) return -1; - - int count = getItemCount() - (hasFooterView() ? 1 : 0); - - for (int i=(hasHeaderView() ? 1 : 0);i getSelectedItems() { - return Collections.unmodifiableSet(new HashSet<>(batchSelected)); - } - - public void pulseHighlightItem(int position) { - if (position < getItemCount()) { - recordToPulseHighlight = getRecordForPositionOrThrow(position); - notifyItemChanged(position); - } - } - - public void onSearchQueryUpdated(@Nullable String query) { - this.searchQuery = query; - notifyDataSetChanged(); - } - - private boolean hasAudio(MessageRecord messageRecord) { - return messageRecord.isMms() && ((MmsMessageRecord)messageRecord).getSlideDeck().getAudioSlide() != null; - } - - private boolean hasDocument(MessageRecord messageRecord) { - return messageRecord.isMms() && ((MmsMessageRecord)messageRecord).getSlideDeck().getDocumentSlide() != null; - } - - private boolean hasThumbnail(MessageRecord messageRecord) { - return messageRecord.isMms() && ((MmsMessageRecord)messageRecord).getSlideDeck().getThumbnailSlide() != null; - } - - @Override - public long getHeaderId(int position) { - if (!isActiveCursor()) return -1; - if (isHeaderPosition(position)) return -1; - if (isFooterPosition(position)) return -1; - if (position >= getItemCount()) return -1; - if (position < 0) return -1; - - MessageRecord record = getRecordForPositionOrThrow(position); - if (record.getRecipient().getAddress().isOpenGroup()) { - calendar.setTime(new Date(record.getDateReceived())); - } else { - calendar.setTime(new Date(record.getDateSent())); - } - return Util.hashCode(calendar.get(Calendar.YEAR), calendar.get(Calendar.DAY_OF_YEAR)); - } - - public long getReceivedTimestamp(int position) { - if (!isActiveCursor()) return 0; - if (isHeaderPosition(position)) return 0; - if (isFooterPosition(position)) return 0; - if (position >= getItemCount()) return 0; - if (position < 0) return 0; - - MessageRecord messageRecord = getRecordForPositionOrThrow(position); - - if (messageRecord.isOutgoing()) return 0; - else return messageRecord.getDateReceived(); - } - - @Override - public HeaderViewHolder onCreateHeaderViewHolder(ViewGroup parent) { - return new HeaderViewHolder(LayoutInflater.from(getContext()).inflate(R.layout.conversation_item_header, parent, false)); - } - - public HeaderViewHolder onCreateLastSeenViewHolder(ViewGroup parent) { - return new HeaderViewHolder(LayoutInflater.from(getContext()).inflate(R.layout.conversation_item_last_seen, parent, false)); - } - - @Override - public void onBindHeaderViewHolder(HeaderViewHolder viewHolder, int position) { - MessageRecord messageRecord = getRecordForPositionOrThrow(position); - long timestamp = messageRecord.getDateReceived(); - if (recipient.getAddress().isOpenGroup()) { timestamp = messageRecord.getTimestamp(); } - viewHolder.setText(DateUtils.getRelativeDate(getContext(), locale, timestamp)); - } - - public void onBindLastSeenViewHolder(HeaderViewHolder viewHolder, int position) { - viewHolder.setText(getContext().getResources().getQuantityString(R.plurals.ConversationAdapter_n_unread_messages, (position + 1), (position + 1))); - } - - static class LastSeenHeader extends StickyHeaderDecoration { - - private final ConversationAdapter adapter; - private final long lastSeenTimestamp; - - LastSeenHeader(ConversationAdapter adapter, long lastSeenTimestamp) { - super(adapter, false, false); - this.adapter = adapter; - this.lastSeenTimestamp = lastSeenTimestamp; - } - - @Override - protected boolean hasHeader(RecyclerView parent, StickyHeaderAdapter stickyAdapter, int position) { - if (!adapter.isActiveCursor()) { - return false; - } - - if (lastSeenTimestamp <= 0) { - return false; - } - - long currentRecordTimestamp = adapter.getReceivedTimestamp(position); - long previousRecordTimestamp = adapter.getReceivedTimestamp(position + 1); - - return currentRecordTimestamp > lastSeenTimestamp && previousRecordTimestamp < lastSeenTimestamp; - } - - @Override - protected int getHeaderTop(RecyclerView parent, View child, View header, int adapterPos, int layoutPos) { - return parent.getLayoutManager().getDecoratedTop(child); - } - - @Override - protected HeaderViewHolder getHeader(RecyclerView parent, StickyHeaderAdapter stickyAdapter, int position) { - HeaderViewHolder viewHolder = adapter.onCreateLastSeenViewHolder(parent); - adapter.onBindLastSeenViewHolder(viewHolder, position); - - int widthSpec = View.MeasureSpec.makeMeasureSpec(parent.getWidth(), View.MeasureSpec.EXACTLY); - int heightSpec = View.MeasureSpec.makeMeasureSpec(parent.getHeight(), View.MeasureSpec.UNSPECIFIED); - - int childWidth = ViewGroup.getChildMeasureSpec(widthSpec, parent.getPaddingLeft() + parent.getPaddingRight(), viewHolder.itemView.getLayoutParams().width); - int childHeight = ViewGroup.getChildMeasureSpec(heightSpec, parent.getPaddingTop() + parent.getPaddingBottom(), viewHolder.itemView.getLayoutParams().height); - - viewHolder.itemView.measure(childWidth, childHeight); - viewHolder.itemView.layout(0, 0, viewHolder.itemView.getMeasuredWidth(), viewHolder.itemView.getMeasuredHeight()); - - return viewHolder; - } - } - -} - diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationFragment.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationFragment.java deleted file mode 100644 index 74a3b5edd..000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationFragment.java +++ /dev/null @@ -1,1220 +0,0 @@ -/* - * Copyright (C) 2015 Open Whisper Systems - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package org.thoughtcrime.securesms.conversation; - -import android.annotation.SuppressLint; -import android.app.Activity; -import android.content.ClipData; -import android.content.Context; -import android.content.DialogInterface; -import android.content.Intent; -import android.database.Cursor; -import android.net.Uri; -import android.os.AsyncTask; -import android.os.Build; -import android.os.Bundle; -import android.text.ClipboardManager; -import android.text.TextUtils; -import android.view.LayoutInflater; -import android.view.Menu; -import android.view.MenuInflater; -import android.view.MenuItem; -import android.view.View; -import android.view.ViewGroup; -import android.view.ViewParent; -import android.view.Window; -import android.view.animation.Animation; -import android.view.animation.AnimationUtils; -import android.widget.TextView; -import android.widget.Toast; -import android.widget.ViewSwitcher; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.appcompat.app.AlertDialog; -import androidx.appcompat.app.AppCompatActivity; -import androidx.appcompat.view.ActionMode; -import androidx.fragment.app.Fragment; -import androidx.loader.app.LoaderManager; -import androidx.loader.content.Loader; -import androidx.recyclerview.widget.LinearLayoutManager; -import androidx.recyclerview.widget.RecyclerView; -import androidx.recyclerview.widget.RecyclerView.OnScrollListener; -import com.annimon.stream.Stream; -import org.session.libsession.messaging.MessagingModuleConfiguration; -import org.session.libsession.messaging.messages.control.DataExtractionNotification; -import org.session.libsession.messaging.messages.signal.OutgoingMediaMessage; -import org.session.libsession.messaging.messages.signal.OutgoingTextMessage; -import org.session.libsession.messaging.messages.visible.Quote; -import org.session.libsession.messaging.messages.visible.VisibleMessage; -import org.session.libsession.messaging.open_groups.OpenGroupAPIV2; -import org.session.libsession.messaging.open_groups.OpenGroupV2; -import org.session.libsession.messaging.sending_receiving.MessageSender; -import org.session.libsession.messaging.sending_receiving.attachments.Attachment; -import org.session.libsession.messaging.sending_receiving.link_preview.LinkPreview; -import org.session.libsession.utilities.Address; -import org.session.libsession.utilities.recipients.Recipient; -import org.session.libsession.utilities.TextSecurePreferences; -import org.session.libsession.utilities.Util; -import org.session.libsession.utilities.ViewUtil; -import org.session.libsession.utilities.concurrent.SimpleTask; -import org.session.libsession.utilities.task.ProgressDialogAsyncTask; -import org.session.libsignal.utilities.guava.Optional; -import org.session.libsignal.utilities.Log; -import org.thoughtcrime.securesms.ApplicationContext; -import org.thoughtcrime.securesms.MessageDetailsActivity; -import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity; -import org.thoughtcrime.securesms.ShareActivity; -import org.thoughtcrime.securesms.components.ConversationTypingView; -import org.thoughtcrime.securesms.components.recyclerview.SmoothScrollingLinearLayoutManager; -import org.thoughtcrime.securesms.conversation.ConversationAdapter.HeaderViewHolder; -import org.thoughtcrime.securesms.conversation.ConversationAdapter.ItemClickListener; -import org.thoughtcrime.securesms.database.DatabaseFactory; -import org.thoughtcrime.securesms.database.MmsSmsDatabase; -import org.thoughtcrime.securesms.database.loaders.ConversationLoader; -import org.thoughtcrime.securesms.database.model.MediaMmsMessageRecord; -import org.thoughtcrime.securesms.database.model.MessageRecord; -import org.thoughtcrime.securesms.database.model.MmsMessageRecord; -import org.thoughtcrime.securesms.longmessage.LongMessageActivity; -import org.thoughtcrime.securesms.mediasend.Media; -import org.thoughtcrime.securesms.mms.GlideApp; -import org.thoughtcrime.securesms.mms.PartAuthority; -import org.thoughtcrime.securesms.mms.Slide; -import org.thoughtcrime.securesms.permissions.Permissions; -import org.thoughtcrime.securesms.util.CommunicationActions; -import org.thoughtcrime.securesms.util.SaveAttachmentTask; -import org.thoughtcrime.securesms.util.StickyHeaderDecoration; - -import java.io.IOException; -import java.io.InputStream; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.HashSet; -import java.util.LinkedList; -import java.util.List; -import java.util.Locale; -import java.util.Set; - -import kotlin.Unit; -import network.loki.messenger.R; - -@SuppressLint("StaticFieldLeak") -public class ConversationFragment extends Fragment - implements LoaderManager.LoaderCallbacks -{ - private static final String TAG = ConversationFragment.class.getSimpleName(); - private static final String KEY_LIMIT = "limit"; - - private static final int PARTIAL_CONVERSATION_LIMIT = 500; - private static final int SCROLL_ANIMATION_THRESHOLD = 50; - private static final int CODE_ADD_EDIT_CONTACT = 77; - - private final ActionModeCallback actionModeCallback = new ActionModeCallback(); - private final ItemClickListener selectionClickListener = new ConversationFragmentItemClickListener(); - - private ConversationFragmentListener listener; - - private Recipient recipient; - private long threadId; - private long lastSeen; - private int startingPosition; - private int previousOffset; - private int activeOffset; - private boolean firstLoad; - private long loaderStartTime; - private ActionMode actionMode; - private Locale locale; - private RecyclerView list; - private RecyclerView.ItemDecoration lastSeenDecoration; - private ViewSwitcher topLoadMoreView; - private ViewSwitcher bottomLoadMoreView; - private ConversationTypingView typingView; - private View composeDivider; - private View scrollToBottomButton; - private TextView scrollDateHeader; - - @Override - public void onCreate(Bundle icicle) { - super.onCreate(icicle); - this.locale = (Locale) getArguments().getSerializable(PassphraseRequiredActionBarActivity.LOCALE_EXTRA); - } - - @Override - public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle bundle) { - final View view = inflater.inflate(R.layout.conversation_fragment, container, false); - list = ViewUtil.findById(view, android.R.id.list); - composeDivider = ViewUtil.findById(view, R.id.compose_divider); - scrollToBottomButton = ViewUtil.findById(view, R.id.scroll_to_bottom_button); - scrollDateHeader = ViewUtil.findById(view, R.id.scroll_date_header); - - scrollToBottomButton.setOnClickListener(v -> scrollToBottom()); - - final LinearLayoutManager layoutManager = new SmoothScrollingLinearLayoutManager(getActivity(), true); - list.setHasFixedSize(false); - list.setLayoutManager(layoutManager); - list.setItemAnimator(null); - - topLoadMoreView = (ViewSwitcher) inflater.inflate(R.layout.load_more_header, container, false); - bottomLoadMoreView = (ViewSwitcher) inflater.inflate(R.layout.load_more_header, container, false); - initializeLoadMoreView(topLoadMoreView); - initializeLoadMoreView(bottomLoadMoreView); - - typingView = (ConversationTypingView) inflater.inflate(R.layout.conversation_typing_view, container, false); - - return view; - } - - @Override - public void onActivityCreated(Bundle bundle) { - super.onActivityCreated(bundle); - - initializeResources(); - initializeListAdapter(); - } - - @Override - public void onAttach(Activity activity) { - super.onAttach(activity); - this.listener = (ConversationFragmentListener)activity; - } - - @Override - public void onStart() { - super.onStart(); - initializeTypingObserver(); - } - - @Override - public void onResume() { - super.onResume(); - - if (list.getAdapter() != null) { - list.getAdapter().notifyDataSetChanged(); - } - } - - @Override - public void onStop() { - super.onStop(); - ApplicationContext.getInstance(requireContext()).getTypingStatusRepository().getTypists(threadId).removeObservers(this); - } - - public void onNewIntent() { - if (actionMode != null) { - actionMode.finish(); - } - - initializeResources(); - initializeListAdapter(); - - if (threadId == -1) { - getLoaderManager().restartLoader(0, Bundle.EMPTY, this); - } - } - - public void reloadList() { - getLoaderManager().restartLoader(0, Bundle.EMPTY, this); - } - - public void moveToLastSeen() { - if (lastSeen <= 0) { - Log.i(TAG, "No need to move to last seen."); - return; - } - - if (list == null || getListAdapter() == null) { - Log.w(TAG, "Tried to move to last seen position, but we hadn't initialized the view yet."); - return; - } - - int position = getListAdapter().findLastSeenPosition(lastSeen); - scrollToLastSeenPosition(position); - } - - private void initializeResources() { - this.recipient = Recipient.from(getActivity(), getActivity().getIntent().getParcelableExtra(ConversationActivity.ADDRESS_EXTRA), true); - this.threadId = this.getActivity().getIntent().getLongExtra(ConversationActivity.THREAD_ID_EXTRA, -1); - this.lastSeen = this.getActivity().getIntent().getLongExtra(ConversationActivity.LAST_SEEN_EXTRA, -1); - this.startingPosition = this.getActivity().getIntent().getIntExtra(ConversationActivity.STARTING_POSITION_EXTRA, -1); - this.firstLoad = true; - - OnScrollListener scrollListener = new ConversationScrollListener(getActivity()); - list.addOnScrollListener(scrollListener); - } - - private void initializeListAdapter() { - if (this.recipient != null && this.threadId != -1) { - ConversationAdapter adapter = new ConversationAdapter(getActivity(), GlideApp.with(this), locale, selectionClickListener, null, this.recipient); - list.setAdapter(adapter); - list.addItemDecoration(new StickyHeaderDecoration(adapter, false, false)); - - setLastSeen(lastSeen); - getLoaderManager().restartLoader(0, Bundle.EMPTY, this); - } - } - - private void initializeLoadMoreView(ViewSwitcher loadMoreView) { - loadMoreView.setOnClickListener(v -> { - Bundle args = new Bundle(); - args.putInt(KEY_LIMIT, 0); - getLoaderManager().restartLoader(0, args, ConversationFragment.this); - loadMoreView.showNext(); - loadMoreView.setOnClickListener(null); - }); - } - - private void initializeTypingObserver() { - if (!TextSecurePreferences.isTypingIndicatorsEnabled(requireContext())) { - return; - } - - ApplicationContext.getInstance(requireContext()).getTypingStatusRepository().getTypists(threadId).observe(this, typingState -> { - List recipients; - boolean replacedByIncomingMessage; - - if (typingState != null) { - recipients = typingState.getTypists(); - replacedByIncomingMessage = typingState.isReplacedByIncomingMessage(); - } else { - recipients = Collections.emptyList(); - replacedByIncomingMessage = false; - } - - typingView.setTypists(GlideApp.with(ConversationFragment.this), recipients, recipient.isGroupRecipient()); - - ConversationAdapter adapter = getListAdapter(); - - if (adapter.getHeaderView() != null && adapter.getHeaderView() != typingView) { - Log.i(TAG, "Skipping typing indicator -- the header slot is occupied."); - return; - } - - if (recipients.size() > 0) { - if (adapter.getHeaderView() == null && isAtBottom()) { - list.setVerticalScrollBarEnabled(false); - list.post(() -> getListLayoutManager().smoothScrollToPosition(requireContext(), 0, 250)); - list.postDelayed(() -> list.setVerticalScrollBarEnabled(true), 300); - adapter.setHeaderView(typingView); - adapter.notifyItemInserted(0); - } else { - if (adapter.getHeaderView() == null) { - adapter.setHeaderView(typingView); - adapter.notifyItemInserted(0); - } else { - adapter.setHeaderView(typingView); - adapter.notifyItemChanged(0); - } - } - } else { - if (getListLayoutManager().findFirstCompletelyVisibleItemPosition() == 0 && getListLayoutManager().getItemCount() > 1 && !replacedByIncomingMessage) { - getListLayoutManager().smoothScrollToPosition(requireContext(), 1, 250); - list.setVerticalScrollBarEnabled(false); - list.postDelayed(() -> { - adapter.setHeaderView(null); - adapter.notifyItemRemoved(0); - list.post(() -> list.setVerticalScrollBarEnabled(true)); - }, 200); - } else if (!replacedByIncomingMessage) { - adapter.setHeaderView(null); - adapter.notifyItemRemoved(0); - } else { - adapter.setHeaderView(null); - } - } - }); - } - - private void setCorrectMenuVisibility(Menu menu) { - Set messageRecords = getListAdapter().getSelectedItems(); - boolean actionMessage = false; - boolean hasText = false; - boolean sharedContact = false; - - if (actionMode != null && messageRecords.size() == 0) { - actionMode.finish(); - return; - } - - for (MessageRecord messageRecord : messageRecords) { - if (messageRecord.isCallLog() || messageRecord.isExpirationTimerUpdate()) - { - actionMessage = true; - } - - if (messageRecord.getBody().length() > 0) { - hasText = true; - } - - if (messageRecord.isMms() && !((MmsMessageRecord) messageRecord).getSharedContacts().isEmpty()) { - sharedContact = true; - } - } - - if (messageRecords.size() > 1) { - menu.findItem(R.id.menu_context_details).setVisible(false); - menu.findItem(R.id.menu_context_reply).setVisible(false); - menu.findItem(R.id.menu_context_save_attachment).setVisible(false); - menu.findItem(R.id.menu_context_resend).setVisible(false); - } else { - MessageRecord messageRecord = messageRecords.iterator().next(); - - menu.findItem(R.id.menu_context_details).setVisible(true); - menu.findItem(R.id.menu_context_resend).setVisible(messageRecord.isFailed()); - menu.findItem(R.id.menu_context_save_attachment).setVisible(!actionMessage && - messageRecord.isMms() && - !messageRecord.isMmsNotification() && - ((MediaMmsMessageRecord)messageRecord).containsMediaSlide()); - - menu.findItem(R.id.menu_context_reply).setVisible(!actionMessage && - !messageRecord.isPending() && - !messageRecord.isFailed()); - } - - menu.findItem(R.id.menu_context_copy).setVisible(!actionMessage && hasText); - - boolean isGroupChat = recipient.isGroupRecipient(); - - if (isGroupChat) { - OpenGroupV2 openGroupChat = DatabaseFactory.getLokiThreadDatabase(getContext()).getOpenGroupChat(threadId); - boolean isPublicChat = (openGroupChat != null); - int selectedMessageCount = messageRecords.size(); - boolean areAllSentByUser = true; - Set uniqueUserSet = new HashSet<>(); - for (MessageRecord message : messageRecords) { - if (!message.isOutgoing()) { areAllSentByUser = false; } - uniqueUserSet.add(message.getRecipient().getAddress().toString()); - } - menu.findItem(R.id.menu_context_copy_public_key).setVisible(selectedMessageCount == 1 && !areAllSentByUser); - menu.findItem(R.id.menu_context_reply).setVisible(selectedMessageCount == 1); - String userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(requireContext()); - boolean userCanModerate = - (isPublicChat && (OpenGroupAPIV2.isUserModerator(userHexEncodedPublicKey, openGroupChat.getRoom(), openGroupChat.getServer()))); - boolean isDeleteOptionVisible = !isPublicChat || (areAllSentByUser || userCanModerate); - // allow banning if moderating a public chat and only one user's messages are selected - boolean isBanOptionVisible = isPublicChat && userCanModerate && !areAllSentByUser && uniqueUserSet.size() == 1; - menu.findItem(R.id.menu_context_delete_message).setVisible(isDeleteOptionVisible); - menu.findItem(R.id.menu_context_ban_user).setVisible(isBanOptionVisible); - } else { - menu.findItem(R.id.menu_context_copy_public_key).setVisible(false); - menu.findItem(R.id.menu_context_delete_message).setVisible(true); - menu.findItem(R.id.menu_context_ban_user).setVisible(false); - } - } - - private ConversationAdapter getListAdapter() { - return (ConversationAdapter) list.getAdapter(); - } - - private SmoothScrollingLinearLayoutManager getListLayoutManager() { - return (SmoothScrollingLinearLayoutManager) list.getLayoutManager(); - } - - private MessageRecord getSelectedMessageRecord() { - Set messageRecords = getListAdapter().getSelectedItems(); - return messageRecords.iterator().next(); - } - - public void reload(Recipient recipient, long threadId) { - this.recipient = recipient; - - if (this.threadId != threadId) { - this.threadId = threadId; - initializeListAdapter(); - } - } - - public void scrollToBottom() { - if (getListLayoutManager().findFirstVisibleItemPosition() < SCROLL_ANIMATION_THRESHOLD) { - list.smoothScrollToPosition(0); - } else { - list.scrollToPosition(0); - } - } - - public void setLastSeen(long lastSeen) { - this.lastSeen = lastSeen; - if (lastSeenDecoration != null) { - list.removeItemDecoration(lastSeenDecoration); - } - - lastSeenDecoration = new ConversationAdapter.LastSeenHeader(getListAdapter(), lastSeen); - list.addItemDecoration(lastSeenDecoration); - } - - private void handleCopyMessage(final Set messageRecords) { - List messageList = new LinkedList<>(messageRecords); - Collections.sort(messageList, new Comparator() { - @Override - public int compare(MessageRecord lhs, MessageRecord rhs) { - if (lhs.getDateReceived() < rhs.getDateReceived()) return -1; - else if (lhs.getDateReceived() == rhs.getDateReceived()) return 0; - else return 1; - } - }); - - StringBuilder bodyBuilder = new StringBuilder(); - ClipboardManager clipboard = (ClipboardManager) getActivity().getSystemService(Context.CLIPBOARD_SERVICE); - - for (MessageRecord messageRecord : messageList) { - String body = messageRecord.getDisplayBody(requireContext()).toString(); - if (!TextUtils.isEmpty(body)) { - bodyBuilder.append(body).append('\n'); - } - } - if (bodyBuilder.length() > 0 && bodyBuilder.charAt(bodyBuilder.length() - 1) == '\n') { - bodyBuilder.deleteCharAt(bodyBuilder.length() - 1); - } - - String result = bodyBuilder.toString(); - - if (!TextUtils.isEmpty(result)) - clipboard.setText(result); - } - - private void handleCopyPublicKey(MessageRecord messageRecord) { - String sessionID = messageRecord.getRecipient().getAddress().toString(); - android.content.ClipboardManager clipboard = (android.content.ClipboardManager)requireActivity().getSystemService(Context.CLIPBOARD_SERVICE); - ClipData clip = ClipData.newPlainText("Session ID", sessionID); - clipboard.setPrimaryClip(clip); - Toast.makeText(getContext(), R.string.copied_to_clipboard, Toast.LENGTH_SHORT).show(); - } - - private void handleDeleteMessages(final Set messageRecords) { - int messagesCount = messageRecords.size(); - AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); - - builder.setIconAttribute(R.attr.dialog_alert_icon); - builder.setTitle(getActivity().getResources().getQuantityString(R.plurals.ConversationFragment_delete_selected_messages, messagesCount, messagesCount)); - builder.setMessage(getActivity().getResources().getQuantityString(R.plurals.ConversationFragment_this_will_permanently_delete_all_n_selected_messages, messagesCount, messagesCount)); - builder.setCancelable(true); - - OpenGroupV2 openGroupChat = DatabaseFactory.getLokiThreadDatabase(getContext()).getOpenGroupChat(threadId); - - builder.setPositiveButton(R.string.delete, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - new ProgressDialogAsyncTask(getActivity(), - R.string.ConversationFragment_deleting, - R.string.ConversationFragment_deleting_messages) - { - @Override - protected Void doInBackground(MessageRecord... messageRecords) { - if (openGroupChat != null) { - ArrayList serverIDs = new ArrayList<>(); - ArrayList ignoredMessages = new ArrayList<>(); - ArrayList failedMessages = new ArrayList<>(); - boolean isSentByUser = true; - for (MessageRecord messageRecord : messageRecords) { - isSentByUser = isSentByUser && messageRecord.isOutgoing(); - Long serverID = DatabaseFactory.getLokiMessageDatabase(getContext()).getServerID(messageRecord.id, !messageRecord.isMms()); - if (serverID != null) { - serverIDs.add(serverID); - } else { - ignoredMessages.add(messageRecord.getId()); - } - } - if (openGroupChat != null) { - for (Long serverId : serverIDs) { - OpenGroupAPIV2 - .deleteMessage(serverId, openGroupChat.getRoom(), openGroupChat.getServer()) - .success(l -> { - for (MessageRecord messageRecord : messageRecords) { - Long serverID = DatabaseFactory.getLokiMessageDatabase(getContext()).getServerID(messageRecord.id, !messageRecord.isMms()); - if (serverID != null && serverID.equals(serverId)) { - MessagingModuleConfiguration.shared.getMessageDataProvider().deleteMessage(messageRecord.id, !messageRecord.isMms()); - break; - } - } - return null; - }).fail(e->{ - Log.e("Loki", "Couldn't delete message due to error",e); - return null; - }); - } - } - } else { - for (MessageRecord messageRecord : messageRecords) { - if (messageRecord.isMms()) { - DatabaseFactory.getMmsDatabase(getActivity()).delete(messageRecord.getId()); - } else { - DatabaseFactory.getSmsDatabase(getActivity()).deleteMessage(messageRecord.getId()); - } - } - } - return null; - } - }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, messageRecords.toArray(new MessageRecord[messageRecords.size()])); - } - }); - - builder.setNegativeButton(android.R.string.cancel, null); - builder.show(); - } - - private void handleBanUser(Set messageRecords) { - AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); - - String userPublicKey = null; - for (MessageRecord record: messageRecords) { - String currentPublicKey = record.getRecipient().getAddress().toString(); - if (userPublicKey == null) { - userPublicKey = currentPublicKey; - } - } - final String finalPublicKey = userPublicKey; - - builder.setIconAttribute(R.attr.dialog_alert_icon); - builder.setTitle(R.string.ConversationFragment_ban_selected_user); - builder.setCancelable(true); - - final OpenGroupV2 openGroupChat = DatabaseFactory.getLokiThreadDatabase(getContext()).getOpenGroupChat(threadId); - - builder.setPositiveButton(R.string.ban, (dialog, which) -> { - ConversationAdapter chatAdapter = getListAdapter(); - chatAdapter.clearSelection(); - chatAdapter.notifyDataSetChanged(); - new ProgressDialogAsyncTask(getActivity(), - R.string.ConversationFragment_banning, - R.string.ConversationFragment_banning_user) { - @Override - protected Void doInBackground(String... userPublicKeyParam) { - String userPublicKey = userPublicKeyParam[0]; - if (openGroupChat != null) { - OpenGroupAPIV2 - .ban(userPublicKey, openGroupChat.getRoom(), openGroupChat.getServer()) - .success(l -> { - Log.d("Loki", "User banned"); - return Unit.INSTANCE; - }).fail(e -> { - Log.e("Loki", "Failed to ban user",e); - return null; - }); - } else { - Log.d("Loki", "Tried to ban user from a non-public chat"); - } - return null; - } - }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, finalPublicKey); - }); - - builder.setNegativeButton(android.R.string.cancel, null); - builder.show(); - } - - private void handleDisplayDetails(MessageRecord message) { - Intent intent = new Intent(getActivity(), MessageDetailsActivity.class); - intent.putExtra(MessageDetailsActivity.MESSAGE_ID_EXTRA, message.getId()); - intent.putExtra(MessageDetailsActivity.THREAD_ID_EXTRA, threadId); - intent.putExtra(MessageDetailsActivity.TYPE_EXTRA, message.isMms() ? MmsSmsDatabase.MMS_TRANSPORT : MmsSmsDatabase.SMS_TRANSPORT); - intent.putExtra(MessageDetailsActivity.ADDRESS_EXTRA, recipient.getAddress()); - intent.putExtra(MessageDetailsActivity.IS_PUSH_GROUP_EXTRA, recipient.isGroupRecipient()); - startActivity(intent); - } - - private void handleForwardMessage(MessageRecord message) { - listener.onForwardClicked(); - - SimpleTask.run(getLifecycle(), () -> { - Intent composeIntent = new Intent(getActivity(), ShareActivity.class); - composeIntent.putExtra(Intent.EXTRA_TEXT, message.getDisplayBody(requireContext()).toString()); - - if (message.isMms()) { - MmsMessageRecord mediaMessage = (MmsMessageRecord) message; - boolean isAlbum = mediaMessage.containsMediaSlide() && - mediaMessage.getSlideDeck().getSlides().size() > 1 && - mediaMessage.getSlideDeck().getAudioSlide() == null && - mediaMessage.getSlideDeck().getDocumentSlide() == null; - - if (isAlbum) { - ArrayList mediaList = new ArrayList<>(mediaMessage.getSlideDeck().getSlides().size()); - List attachments = Stream.of(mediaMessage.getSlideDeck().getSlides()) - .filter(s -> s.hasImage() || s.hasVideo()) - .map(Slide::asAttachment) - .toList(); - - for (Attachment attachment : attachments) { - Uri uri = attachment.getDataUri() != null ? attachment.getDataUri() : attachment.getThumbnailUri(); - - if (uri != null) { - mediaList.add(new Media(uri, - attachment.getContentType(), - System.currentTimeMillis(), - attachment.getWidth(), - attachment.getHeight(), - attachment.getSize(), - Optional.absent(), - Optional.fromNullable(attachment.getCaption()))); - } - }; - - if (!mediaList.isEmpty()) { - composeIntent.putExtra(ConversationActivity.MEDIA_EXTRA, mediaList); - } - } else if (mediaMessage.containsMediaSlide()) { - Slide slide = mediaMessage.getSlideDeck().getSlides().get(0); - composeIntent.putExtra(Intent.EXTRA_STREAM, slide.getUri()); - composeIntent.setType(slide.getContentType()); - } - - if (mediaMessage.getSlideDeck().getTextSlide() != null && mediaMessage.getSlideDeck().getTextSlide().getUri() != null) { - try (InputStream stream = PartAuthority.getAttachmentStream(requireContext(), mediaMessage.getSlideDeck().getTextSlide().getUri())) { - String fullBody = Util.readFullyAsString(stream); - composeIntent.putExtra(Intent.EXTRA_TEXT, fullBody); - } catch (IOException e) { - Log.w(TAG, "Failed to read long message text when forwarding."); - } - } - } - - return composeIntent; - }, this::startActivity); - } - - private void handleResendMessage(final MessageRecord message) { - new AsyncTask() { - @Override - protected Void doInBackground(MessageRecord... messageRecords) { - MessageRecord messageRecord = messageRecords[0]; - Recipient recipient = messageRecord.getRecipient(); - VisibleMessage message = new VisibleMessage(); - message.setId(messageRecord.getId()); - message.setText(messageRecord.getBody()); - message.setSentTimestamp(messageRecord.getTimestamp()); - if (recipient.isGroupRecipient()) { - message.setGroupPublicKey(recipient.getAddress().toGroupString()); - } else { - message.setRecipient(messageRecord.getRecipient().getAddress().serialize()); - } - message.setThreadID(messageRecord.getThreadId()); - if (messageRecord.isMms()) { - MmsMessageRecord mmsMessageRecord = (MmsMessageRecord) messageRecord; - if (!mmsMessageRecord.getLinkPreviews().isEmpty()) { - message.setLinkPreview(org.session.libsession.messaging.messages.visible.LinkPreview.Companion.from(mmsMessageRecord.getLinkPreviews().get(0))); - } - if (mmsMessageRecord.getQuote() != null) { - message.setQuote(Quote.Companion.from(mmsMessageRecord.getQuote().getQuoteModel())); - } - message.addSignalAttachments(mmsMessageRecord.getSlideDeck().asAttachments()); - } - MessageSender.send(message, recipient.getAddress()); - return null; - } - }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, message); - } - - private void handleReplyMessage(final MessageRecord message) { - listener.handleReplyMessage(message); - } - - private void handleSaveAttachment(final MediaMmsMessageRecord message) { - SaveAttachmentTask.showWarningDialog(getActivity(), (dialog, which) -> { - Permissions.with(this) - .request(android.Manifest.permission.WRITE_EXTERNAL_STORAGE) - .maxSdkVersion(Build.VERSION_CODES.P) - .withPermanentDenialDialog(getString(R.string.MediaPreviewActivity_signal_needs_the_storage_permission_in_order_to_write_to_external_storage_but_it_has_been_permanently_denied)) - .onAnyDenied(() -> Toast.makeText(getContext(), R.string.MediaPreviewActivity_unable_to_write_to_external_storage_without_permission, Toast.LENGTH_LONG).show()) - .onAllGranted(() -> { - List attachments = - Stream.of(message.getSlideDeck().getSlides()) - .filter(s -> s.getUri() != null && (s.hasImage() || s.hasVideo() || s.hasAudio() || s.hasDocument())) - .map(s -> new SaveAttachmentTask.Attachment(s.getUri(), s.getContentType(), message.getDateReceived(), s.getFileName().orNull())) - .toList(); - if (!Util.isEmpty(attachments)) { - SaveAttachmentTask saveTask = new SaveAttachmentTask(getActivity()); - saveTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, attachments.toArray(new SaveAttachmentTask.Attachment[0])); - if (!message.isOutgoing()) { - sendMediaSavedNotificationIfNeeded(); - } - return; - } - - Log.w(TAG, "No slide with attachable media found, failing nicely."); - Toast.makeText(getActivity(), - getResources().getQuantityString(R.plurals.ConversationFragment_error_while_saving_attachments_to_sd_card, 1), - Toast.LENGTH_LONG).show(); - }) - .execute(); - }); - } - - private void sendMediaSavedNotificationIfNeeded() { - if (recipient.isGroupRecipient()) return; - DataExtractionNotification message = new DataExtractionNotification(new DataExtractionNotification.Kind.MediaSaved(System.currentTimeMillis())); - MessageSender.send(message, recipient.getAddress()); - } - - @Override - public @NonNull Loader onCreateLoader(int id, Bundle args) { - Log.i(TAG, "onCreateLoader"); - loaderStartTime = System.currentTimeMillis(); - - int limit = args.getInt(KEY_LIMIT, PARTIAL_CONVERSATION_LIMIT); - int offset = 0; - if (limit != 0 && startingPosition >= limit) { - offset = Math.max(startingPosition - (limit / 2) + 1, 0); - startingPosition -= offset - 1; - } - - return new ConversationLoader(getActivity(), threadId, offset, limit, lastSeen); - } - - @Override - public void onLoadFinished(@NonNull Loader cursorLoader, Cursor cursor) { - long loadTime = System.currentTimeMillis() - loaderStartTime; - int count = cursor.getCount(); - Log.i(TAG, "onLoadFinished - took " + loadTime + " ms to load a cursor of size " + count); - ConversationLoader loader = (ConversationLoader)cursorLoader; - - ConversationAdapter adapter = getListAdapter(); - if (adapter == null) { - return; - } - - if (cursor.getCount() >= PARTIAL_CONVERSATION_LIMIT && loader.hasLimit()) { - adapter.setFooterView(topLoadMoreView); - } else { - adapter.setFooterView(null); - } - - if (lastSeen == -1) { - setLastSeen(loader.getLastSeen()); - } - - if (!loader.hasSent() && !recipient.isSystemContact() && !recipient.isGroupRecipient() && recipient.getRegistered() == Recipient.RegisteredState.REGISTERED) { -// adapter.setHeaderView(unknownSenderView); - } else { - clearHeaderIfNotTyping(adapter); - } - - if (loader.hasOffset()) { - adapter.setHeaderView(bottomLoadMoreView); - } - - if (firstLoad || loader.hasOffset()) { - previousOffset = loader.getOffset(); - } - - activeOffset = loader.getOffset(); - - adapter.changeCursor(cursor); - - int lastSeenPosition = adapter.findLastSeenPosition(lastSeen); - - if (adapter.getHeaderView() == typingView) { - lastSeenPosition = Math.max(lastSeenPosition - 1, 0); - } - - if (firstLoad) { - if (startingPosition >= 0) { - scrollToStartingPosition(startingPosition); - } else { - scrollToLastSeenPosition(lastSeenPosition); - } - firstLoad = false; - } else if (previousOffset > 0) { - int scrollPosition = previousOffset + getListLayoutManager().findFirstVisibleItemPosition(); - scrollPosition = Math.min(scrollPosition, count - 1); - - View firstView = list.getLayoutManager().getChildAt(scrollPosition); - int pixelOffset = (firstView == null) ? 0 : (firstView.getBottom() - list.getPaddingBottom()); - - getListLayoutManager().scrollToPositionWithOffset(scrollPosition, pixelOffset); - previousOffset = 0; - } - - if (lastSeenPosition <= 0) { - setLastSeen(0); - } - } - - private void clearHeaderIfNotTyping(ConversationAdapter adapter) { - if (adapter.getHeaderView() != typingView) { - adapter.setHeaderView(null); - } - } - - @Override - public void onLoaderReset(@NonNull Loader arg0) { - if (list.getAdapter() != null) { - getListAdapter().changeCursor(null); - } - } - - public long stageOutgoingMessage(OutgoingMediaMessage message) { - MessageRecord messageRecord = DatabaseFactory.getMmsDatabase(getContext()).readerFor(message, threadId).getCurrent(); - - if (getListAdapter() != null) { - clearHeaderIfNotTyping(getListAdapter()); - setLastSeen(0); - getListAdapter().addFastRecord(messageRecord); - } - - return messageRecord.getId(); - } - - public long stageOutgoingMessage(OutgoingTextMessage message) { - MessageRecord messageRecord = DatabaseFactory.getSmsDatabase(getContext()).readerFor(message, threadId).getCurrent(); - - if (getListAdapter() != null) { - clearHeaderIfNotTyping(getListAdapter()); - setLastSeen(0); - getListAdapter().addFastRecord(messageRecord); - } - - return messageRecord.getId(); - } - - public void releaseOutgoingMessage(long id) { - if (getListAdapter() != null) { - getListAdapter().releaseFastRecord(id); - } - } - - private void scrollToStartingPosition(final int startingPosition) { - list.post(() -> { - list.getLayoutManager().scrollToPosition(startingPosition); - getListAdapter().pulseHighlightItem(startingPosition); - }); - } - - private void scrollToLastSeenPosition(final int lastSeenPosition) { - if (lastSeenPosition > 0) { - list.post(() -> getListLayoutManager().scrollToPositionWithOffset(lastSeenPosition, list.getHeight())); - } - } - - private boolean isAtBottom() { - if (list.getChildCount() == 0) return true; - - int firstVisiblePosition = getListLayoutManager().findFirstVisibleItemPosition(); - - if (getListAdapter().getHeaderView() == typingView) { - RecyclerView.ViewHolder item1 = list.findViewHolderForAdapterPosition(1); - return firstVisiblePosition <= 1 && item1 != null && item1.itemView.getBottom() <= list.getHeight(); - } - - return firstVisiblePosition == 0 && list.getChildAt(0).getBottom() <= list.getHeight(); - } - - public void onSearchQueryUpdated(@Nullable String query) { - if (getListAdapter() != null) { - getListAdapter().onSearchQueryUpdated(query); - } - } - - public void jumpToMessage(@NonNull Address author, long timestamp, @Nullable Runnable onMessageNotFound) { - SimpleTask.run(getLifecycle(), () -> { - return DatabaseFactory.getMmsSmsDatabase(getContext()) - .getMessagePositionInConversation(threadId, timestamp, author); - }, p -> moveToMessagePosition(p, onMessageNotFound)); - } - - private void moveToMessagePosition(int position, @Nullable Runnable onMessageNotFound) { - Log.d(TAG, "Moving to message position: " + position + " activeOffset: " + activeOffset + " cursorCount: " + getListAdapter().getCursorCount()); - - if (position >= activeOffset && position >= 0 && position < getListAdapter().getCursorCount()) { - int offset = activeOffset > 0 ? activeOffset - 1 : 0; - list.scrollToPosition(position - offset); - getListAdapter().pulseHighlightItem(position - offset); - } else if (position < 0) { - Log.w(TAG, "Tried to navigate to message, but it wasn't found."); - if (onMessageNotFound != null) { - onMessageNotFound.run(); - } - } else { - Log.i(TAG, "Message was outside of the loaded range. Need to restart the loader."); - - firstLoad = true; - startingPosition = position; - getLoaderManager().restartLoader(0, Bundle.EMPTY, ConversationFragment.this); - } - } - - public interface ConversationFragmentListener { - void setThreadId(long threadId); - void handleReplyMessage(MessageRecord messageRecord); - void onMessageActionToolbarOpened(); - void onForwardClicked(); - } - - private class ConversationScrollListener extends OnScrollListener { - - private final Animation scrollButtonInAnimation; - private final Animation scrollButtonOutAnimation; - private final ConversationDateHeader conversationDateHeader; - - private boolean wasAtBottom = true; - private boolean wasAtZoomScrollHeight = false; - private long lastPositionId = -1; - - ConversationScrollListener(@NonNull Context context) { - this.scrollButtonInAnimation = AnimationUtils.loadAnimation(context, R.anim.fade_scale_in); - this.scrollButtonOutAnimation = AnimationUtils.loadAnimation(context, R.anim.fade_scale_out); - this.conversationDateHeader = new ConversationDateHeader(context, scrollDateHeader); - - this.scrollButtonInAnimation.setDuration(100); - this.scrollButtonOutAnimation.setDuration(50); - } - - @Override - public void onScrolled(@NonNull final RecyclerView rv, final int dx, final int dy) { - boolean currentlyAtBottom = isAtBottom(); - boolean currentlyAtZoomScrollHeight = isAtZoomScrollHeight(); - int positionId = getHeaderPositionId(); - - if (currentlyAtBottom && !wasAtBottom) { - ViewUtil.fadeOut(composeDivider, 50, View.INVISIBLE); - ViewUtil.animateOut(scrollToBottomButton, scrollButtonOutAnimation, View.INVISIBLE); - } else if (!currentlyAtBottom && wasAtBottom) { - ViewUtil.fadeIn(composeDivider, 500); - } - - if (currentlyAtZoomScrollHeight && !wasAtZoomScrollHeight) { - ViewUtil.animateIn(scrollToBottomButton, scrollButtonInAnimation); - } - - if (positionId != lastPositionId) { - bindScrollHeader(conversationDateHeader, positionId); - } - - wasAtBottom = currentlyAtBottom; - wasAtZoomScrollHeight = currentlyAtZoomScrollHeight; - lastPositionId = positionId; - } - - @Override - public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) { - if (newState == RecyclerView.SCROLL_STATE_DRAGGING) { - conversationDateHeader.show(); - } else if (newState == RecyclerView.SCROLL_STATE_IDLE) { - conversationDateHeader.hide(); - } - } - - private boolean isAtZoomScrollHeight() { - return getListLayoutManager().findFirstCompletelyVisibleItemPosition() > 4; - } - - private int getHeaderPositionId() { - return getListLayoutManager().findLastVisibleItemPosition(); - } - - private void bindScrollHeader(HeaderViewHolder headerViewHolder, int positionId) { - if (((ConversationAdapter)list.getAdapter()).getHeaderId(positionId) != -1) { - ((ConversationAdapter) list.getAdapter()).onBindHeaderViewHolder(headerViewHolder, positionId); - } - } - } - - private class ConversationFragmentItemClickListener implements ItemClickListener { - - @Override - public void onItemClick(MessageRecord messageRecord) { - if (messageRecord.isUpdate()) return; - if (actionMode != null) { - ((ConversationAdapter) list.getAdapter()).toggleSelection(messageRecord); - list.getAdapter().notifyDataSetChanged(); - - if (getListAdapter().getSelectedItems().size() == 0) { - actionMode.finish(); - } else { - setCorrectMenuVisibility(actionMode.getMenu()); - actionMode.setTitle(String.valueOf(getListAdapter().getSelectedItems().size())); - } - } - } - - @Override - public void onItemLongClick(MessageRecord messageRecord) { - if (messageRecord.isUpdate()) return; - if (actionMode == null) { - ((ConversationAdapter) list.getAdapter()).toggleSelection(messageRecord); - list.getAdapter().notifyDataSetChanged(); - - actionMode = ((AppCompatActivity)getActivity()).startSupportActionMode(actionModeCallback); - - View titleTextView = (getActivity().findViewById(R.id.action_bar_title)); - if (titleTextView != null) { - titleTextView.setBackgroundColor(getResources().getColor(R.color.transparent)); - ViewParent titleTextViewContainerView = titleTextView.getParent(); - if (titleTextViewContainerView != null) { - ((View)titleTextViewContainerView).setBackgroundColor(getResources().getColor(R.color.transparent)); - } - } - } - } - - @Override - public void onQuoteClicked(MmsMessageRecord messageRecord) { - if (messageRecord.getQuote() == null) { - Log.w(TAG, "Received a 'quote clicked' event, but there's no quote..."); - return; - } - - if (messageRecord.getQuote().isOriginalMissing()) { - Log.i(TAG, "Clicked on a quote whose original message we never had."); - Toast.makeText(getContext(), R.string.ConversationFragment_quoted_message_not_found, Toast.LENGTH_SHORT).show(); - return; - } - - SimpleTask.run(getLifecycle(), () -> { - return DatabaseFactory.getMmsSmsDatabase(getContext()) - .getQuotedMessagePosition(threadId, - messageRecord.getQuote().getId(), - messageRecord.getQuote().getAuthor()); - }, p -> moveToMessagePosition(p, () -> { - Toast.makeText(getContext(), R.string.ConversationFragment_quoted_message_no_longer_available, Toast.LENGTH_SHORT).show(); - })); - } - - @Override - public void onLinkPreviewClicked(@NonNull LinkPreview linkPreview) { - if (getContext() != null && getActivity() != null) { - CommunicationActions.openBrowserLink(getActivity(), linkPreview.getUrl()); - } - } - - @Override - public void onMoreTextClicked(@NonNull Address conversationAddress, long messageId, boolean isMms) { - if (getContext() != null && getActivity() != null) { - startActivity(LongMessageActivity.getIntent(getContext(), conversationAddress, messageId, isMms)); - } - } - } - - private class ActionModeCallback implements ActionMode.Callback { - - private int statusBarColor; - - @Override - public boolean onCreateActionMode(ActionMode mode, Menu menu) { - MenuInflater inflater = mode.getMenuInflater(); - inflater.inflate(R.menu.conversation_context, menu); - - mode.setTitle("1"); - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - Window window = getActivity().getWindow(); - statusBarColor = window.getStatusBarColor(); - } - - setCorrectMenuVisibility(menu); - listener.onMessageActionToolbarOpened(); - return true; - } - - @Override - public boolean onPrepareActionMode(ActionMode actionMode, Menu menu) { - return false; - } - - @Override - public void onDestroyActionMode(ActionMode mode) { - ((ConversationAdapter)list.getAdapter()).clearSelection(); - list.getAdapter().notifyDataSetChanged(); - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - getActivity().getWindow().setStatusBarColor(statusBarColor); - } - - actionMode = null; - } - - @Override - public boolean onActionItemClicked(ActionMode mode, MenuItem item) { - switch(item.getItemId()) { - case R.id.menu_context_copy: - handleCopyMessage(getListAdapter().getSelectedItems()); - actionMode.finish(); - return true; - case R.id.menu_context_copy_public_key: - handleCopyPublicKey((MessageRecord) getListAdapter().getSelectedItems().toArray()[0]); - actionMode.finish(); - return true; - case R.id.menu_context_delete_message: - handleDeleteMessages(getListAdapter().getSelectedItems()); - actionMode.finish(); - return true; - case R.id.menu_context_ban_user: - handleBanUser(getListAdapter().getSelectedItems()); - return true; - case R.id.menu_context_details: - handleDisplayDetails(getSelectedMessageRecord()); - actionMode.finish(); - return true; -// case R.id.menu_context_forward: -// handleForwardMessage(getSelectedMessageRecord()); -// actionMode.finish(); -// return true; - case R.id.menu_context_resend: - handleResendMessage(getSelectedMessageRecord()); - actionMode.finish(); - return true; - case R.id.menu_context_save_attachment: - handleSaveAttachment((MediaMmsMessageRecord)getSelectedMessageRecord()); - actionMode.finish(); - return true; - case R.id.menu_context_reply: - handleReplyMessage(getSelectedMessageRecord()); - actionMode.finish(); - return true; - } - - return false; - } - } - - private static class ConversationDateHeader extends HeaderViewHolder { - - private final Animation animateIn; - private final Animation animateOut; - - private boolean pendingHide = false; - - private ConversationDateHeader(Context context, TextView textView) { - super(textView); - this.animateIn = AnimationUtils.loadAnimation(context, R.anim.slide_from_top); - this.animateOut = AnimationUtils.loadAnimation(context, R.anim.slide_to_top); - - this.animateIn.setDuration(100); - this.animateOut.setDuration(100); - } - - public void show() { - if (pendingHide) { - pendingHide = false; - } else { - ViewUtil.animateIn(textView, animateIn); - } - } - - public void hide() { - pendingHide = true; - - textView.postDelayed(new Runnable() { - @Override - public void run() { - if (pendingHide) { - pendingHide = false; - ViewUtil.animateOut(textView, animateOut, View.GONE); - } - } - }, 400); - } - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationItem.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationItem.java deleted file mode 100644 index aa6060307..000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationItem.java +++ /dev/null @@ -1,1203 +0,0 @@ -/* - * Copyright (C) 2011 Whisper Systems - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package org.thoughtcrime.securesms.conversation; - -import android.annotation.SuppressLint; -import android.content.ActivityNotFoundException; -import android.content.Context; -import android.content.Intent; -import android.content.res.TypedArray; -import android.graphics.Color; -import android.graphics.PorterDuff; -import android.graphics.Typeface; -import android.net.Uri; -import android.text.Spannable; -import android.text.SpannableString; -import android.text.SpannableStringBuilder; -import android.text.Spanned; -import android.text.TextPaint; -import android.text.TextUtils; -import android.text.style.BackgroundColorSpan; -import android.text.style.CharacterStyle; -import android.text.style.ClickableSpan; -import android.text.style.ForegroundColorSpan; -import android.text.style.URLSpan; -import android.text.util.Linkify; -import android.util.AttributeSet; -import android.util.TypedValue; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ImageView; -import android.widget.LinearLayout; -import android.widget.TextView; -import android.widget.Toast; - -import androidx.annotation.DimenRes; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import com.annimon.stream.Stream; - -import org.session.libsession.messaging.contacts.Contact; -import org.session.libsession.messaging.jobs.AttachmentDownloadJob; -import org.session.libsession.messaging.jobs.JobQueue; -import org.session.libsession.messaging.open_groups.OpenGroupAPIV2; -import org.session.libsession.messaging.open_groups.OpenGroupV2; -import org.session.libsession.messaging.sending_receiving.attachments.AttachmentTransferProgress; -import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAttachment; -import org.session.libsession.messaging.sending_receiving.link_preview.LinkPreview; -import org.session.libsession.messaging.utilities.UpdateMessageData; -import org.session.libsession.utilities.Stub; -import org.session.libsession.utilities.TextSecurePreferences; -import org.session.libsession.utilities.ThemeUtil; -import org.session.libsession.utilities.Util; -import org.session.libsession.utilities.ViewUtil; -import org.session.libsession.utilities.recipients.Recipient; -import org.session.libsession.utilities.recipients.RecipientModifiedListener; -import org.session.libsignal.utilities.Log; -import org.session.libsignal.utilities.guava.Optional; -import org.thoughtcrime.securesms.BindableConversationItem; -import org.thoughtcrime.securesms.MediaPreviewActivity; -import org.thoughtcrime.securesms.MessageDetailsActivity; -import org.thoughtcrime.securesms.components.ConversationItemAlertView; -import org.thoughtcrime.securesms.components.ConversationItemFooter; -import org.thoughtcrime.securesms.components.ConversationItemThumbnail; -import org.thoughtcrime.securesms.components.DocumentView; -import org.thoughtcrime.securesms.components.LinkPreviewView; -import org.thoughtcrime.securesms.components.QuoteView; -import org.thoughtcrime.securesms.components.StickerView; -import org.thoughtcrime.securesms.components.emoji.EmojiTextView; -import org.thoughtcrime.securesms.database.DatabaseFactory; -import org.thoughtcrime.securesms.database.MmsSmsDatabase; -import org.thoughtcrime.securesms.database.model.MediaMmsMessageRecord; -import org.thoughtcrime.securesms.database.model.MessageRecord; -import org.thoughtcrime.securesms.database.model.MmsMessageRecord; -import org.thoughtcrime.securesms.database.model.Quote; -import org.thoughtcrime.securesms.linkpreview.LinkPreviewUtil; -import org.thoughtcrime.securesms.loki.utilities.MentionUtilities; -import org.thoughtcrime.securesms.loki.views.MessageAudioView; -import org.thoughtcrime.securesms.loki.views.OpenGroupInvitationView; -import org.thoughtcrime.securesms.loki.views.ProfilePictureView; -import org.thoughtcrime.securesms.mms.GlideRequests; -import org.thoughtcrime.securesms.mms.ImageSlide; -import org.thoughtcrime.securesms.mms.PartAuthority; -import org.thoughtcrime.securesms.mms.Slide; -import org.thoughtcrime.securesms.mms.SlideClickListener; -import org.thoughtcrime.securesms.mms.SlidesClickedListener; -import org.thoughtcrime.securesms.mms.TextSlide; -import org.thoughtcrime.securesms.util.DateUtils; -import org.thoughtcrime.securesms.util.LongClickCopySpan; -import org.thoughtcrime.securesms.util.LongClickMovementMethod; -import org.thoughtcrime.securesms.util.SearchUtil; - -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Locale; -import java.util.Set; - -import network.loki.messenger.R; - -/** - * A view that displays an individual conversation item within a conversation - * thread. Used by ComposeMessageActivity's ListActivity via a ConversationAdapter. - * - * @author Moxie Marlinspike - * - */ - -public class ConversationItem extends LinearLayout - implements RecipientModifiedListener, BindableConversationItem -{ - private static final String TAG = ConversationItem.class.getSimpleName(); - - private static final int MAX_MEASURE_CALLS = 3; - private static final int MAX_BODY_DISPLAY_LENGTH = 1000; - - private MessageRecord messageRecord; - private Locale locale; - private boolean groupThread; - private Recipient recipient; - private GlideRequests glideRequests; - - protected ViewGroup bodyBubble; - private QuoteView quoteView; - private EmojiTextView bodyText; - private ConversationItemFooter footer; - private ConversationItemFooter stickerFooter; - private TextView groupSender; - private TextView groupSenderProfileName; - private View groupSenderHolder; - private ProfilePictureView profilePictureView; - private ImageView moderatorIconImageView; - private ViewGroup contactPhotoHolder; - private ConversationItemAlertView alertView; - private ViewGroup container; - - private @NonNull Set batchSelected = new HashSet<>(); - private Recipient conversationRecipient; - private Stub mediaThumbnailStub; - private Stub audioViewStub; - private Stub documentViewStub; - private Stub linkPreviewStub; - private Stub stickerStub; - private Stub openGroupInvitationViewStub; - private @Nullable EventListener eventListener; - - private int defaultBubbleColor; - private int measureCalls; - - private final PassthroughClickListener passthroughClickListener = new PassthroughClickListener(); - private final AttachmentDownloadClickListener downloadClickListener = new AttachmentDownloadClickListener(); - private final SlideClickPassthroughListener singleDownloadClickListener = new SlideClickPassthroughListener(downloadClickListener); - private final LinkPreviewClickListener linkPreviewClickListener = new LinkPreviewClickListener(); - - private final Context context; - - public ConversationItem(Context context) { - this(context, null); - } - - public ConversationItem(Context context, AttributeSet attrs) { - super(context, attrs); - this.context = context; - } - - @Override - public void setOnClickListener(OnClickListener l) { - super.setOnClickListener(new ClickListener(l)); - } - - @Override - protected void onFinishInflate() { - super.onFinishInflate(); - - initializeAttributes(); - - this.bodyText = findViewById(R.id.conversation_item_body); - this.footer = findViewById(R.id.conversation_item_footer); - this.stickerFooter = findViewById(R.id.conversation_item_sticker_footer); - this.groupSender = findViewById(R.id.group_message_sender); - this.groupSenderProfileName = findViewById(R.id.group_message_sender_profile); - this.alertView = findViewById(R.id.indicators_parent); - this.profilePictureView = findViewById(R.id.profilePictureView); - this.moderatorIconImageView = findViewById(R.id.moderator_icon_image_view); - this.contactPhotoHolder = findViewById(R.id.contact_photo_container); - this.bodyBubble = findViewById(R.id.body_bubble); - this.mediaThumbnailStub = new Stub<>(findViewById(R.id.image_view_stub)); - this.audioViewStub = new Stub<>(findViewById(R.id.audio_view_stub)); - this.documentViewStub = new Stub<>(findViewById(R.id.document_view_stub)); - this.linkPreviewStub = new Stub<>(findViewById(R.id.link_preview_stub)); - this.stickerStub = new Stub<>(findViewById(R.id.sticker_view_stub)); - this.openGroupInvitationViewStub = new Stub<>(findViewById(R.id.open_group_invitation_stub)); - this.groupSenderHolder = findViewById(R.id.group_sender_holder); - this.quoteView = findViewById(R.id.quote_view); - this.container = findViewById(R.id.container); - - setOnClickListener(new ClickListener(null)); - - bodyText.setOnLongClickListener(passthroughClickListener); - bodyText.setOnClickListener(passthroughClickListener); - - bodyText.setMovementMethod(LongClickMovementMethod.getInstance(getContext())); - } - - @Override - public void bind(@NonNull MessageRecord messageRecord, - @NonNull Optional previousMessageRecord, - @NonNull Optional nextMessageRecord, - @NonNull GlideRequests glideRequests, - @NonNull Locale locale, - @NonNull Set batchSelected, - @NonNull Recipient conversationRecipient, - @Nullable String searchQuery, - boolean pulseHighlight) - { - this.messageRecord = messageRecord; - this.locale = locale; - this.glideRequests = glideRequests; - this.batchSelected = batchSelected; - this.conversationRecipient = conversationRecipient; - this.groupThread = conversationRecipient.isGroupRecipient(); - this.recipient = messageRecord.getIndividualRecipient(); - - this.recipient.addListener(this); - this.conversationRecipient.addListener(this); - - setGutterSizes(messageRecord, groupThread); - setMessageShape(messageRecord, previousMessageRecord, nextMessageRecord, groupThread); - setMediaAttributes(messageRecord, previousMessageRecord, nextMessageRecord, conversationRecipient, groupThread); - setInteractionState(messageRecord, pulseHighlight); - setBodyText(messageRecord, searchQuery, groupThread); - setBubbleState(messageRecord); - setStatusIcons(messageRecord); - setContactPhoto(recipient); - setGroupMessageStatus(messageRecord, recipient); - setGroupAuthorColor(messageRecord); - setAuthor(messageRecord, previousMessageRecord, nextMessageRecord, groupThread); - setQuote(messageRecord, previousMessageRecord, nextMessageRecord, groupThread); - setMessageSpacing(context, messageRecord, previousMessageRecord, nextMessageRecord, groupThread); - setFooter(messageRecord, nextMessageRecord, locale, groupThread); - adjustMarginsIfNeeded(messageRecord); - } - - @Override - public void setEventListener(@Nullable EventListener eventListener) { - this.eventListener = eventListener; - } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - - if (isInEditMode()) { - return; - } - - boolean needsMeasure = false; - - if (hasQuote(messageRecord)) { - int quoteWidth = quoteView.getMeasuredWidth(); - int availableWidth = getAvailableMessageBubbleWidth(quoteView); - - if (quoteWidth != availableWidth) { - quoteView.getLayoutParams().width = availableWidth; - needsMeasure = true; - } - } - - if (hasThumbnail(messageRecord) && messageRecord.getDisplayBody(context).length() > 0) { - ViewUtil.updateLayoutParams(bodyText, getAvailableMessageBubbleWidth(bodyText), ViewGroup.LayoutParams.WRAP_CONTENT); - } - - ConversationItemFooter activeFooter = getActiveFooter(messageRecord); - int availableWidth = getAvailableMessageBubbleWidth(footer); - - if (activeFooter.getVisibility() != GONE && activeFooter.getMeasuredWidth() != availableWidth) { - activeFooter.getLayoutParams().width = availableWidth; - needsMeasure = true; - } - - if (needsMeasure) { - if (measureCalls < MAX_MEASURE_CALLS) { - measureCalls++; - measure(widthMeasureSpec, heightMeasureSpec); - } else { - Log.w(TAG, "Hit measure() cap of " + MAX_MEASURE_CALLS); - } - } else { - measureCalls = 0; - } - } - - private int getAvailableMessageBubbleWidth(@NonNull View forView) { - int availableWidth; - if (hasAudio(messageRecord)) { - availableWidth = audioViewStub.get().getMeasuredWidth() + ViewUtil.getLeftMargin(audioViewStub.get()) + ViewUtil.getRightMargin(audioViewStub.get()); - } else if (hasThumbnail(messageRecord) || hasBigImageLinkPreview(messageRecord)) { - availableWidth = mediaThumbnailStub.get().getMeasuredWidth(); - } else { - availableWidth = bodyBubble.getMeasuredWidth() - bodyBubble.getPaddingLeft() - bodyBubble.getPaddingRight(); - } - - availableWidth -= ViewUtil.getLeftMargin(forView) + ViewUtil.getRightMargin(forView); - - return availableWidth; - } - - private void initializeAttributes() { - final int[] attributes = new int[] {R.attr.conversation_item_bubble_background}; - final TypedArray attrs = context.obtainStyledAttributes(attributes); - - defaultBubbleColor = attrs.getColor(0, Color.WHITE); - attrs.recycle(); - } - - @Override - public void unbind() { - if (recipient != null) { - recipient.removeListener(this); - } - if (profilePictureView != null) { - profilePictureView.recycle(); - } - } - - public MessageRecord getMessageRecord() { - return messageRecord; - } - - /// MessageRecord Attribute Parsers - - private void setBubbleState(MessageRecord messageRecord) { - int bubbleColor = ThemeUtil.getThemedColor(getContext(), messageRecord.isOutgoing() ? - R.attr.message_sent_background_color : - R.attr.message_received_background_color); - bodyBubble.getBackground().setColorFilter(bubbleColor, PorterDuff.Mode.MULTIPLY); - - if (audioViewStub.resolved()) { - setAudioViewTint(messageRecord, this.conversationRecipient); - } - } - - private void setAudioViewTint(MessageRecord messageRecord, Recipient recipient) { -// audioViewStub.get().setTint(Color.WHITE, getResources().getColor(R.color.action_bar_background)); - } - - private void setInteractionState(MessageRecord messageRecord, boolean pulseHighlight) { - if (batchSelected.contains(messageRecord)) { - setBackgroundResource(R.drawable.conversation_item_background); - setSelected(true); - } else if (pulseHighlight) { - setBackgroundResource(R.drawable.conversation_item_background_animated); - setSelected(true); - postDelayed(() -> setSelected(false), 500); - } else { - setSelected(false); - } - - if (mediaThumbnailStub.resolved()) { - mediaThumbnailStub.get().setFocusable(!shouldInterceptClicks(messageRecord) && batchSelected.isEmpty()); - mediaThumbnailStub.get().setClickable(!shouldInterceptClicks(messageRecord) && batchSelected.isEmpty()); - mediaThumbnailStub.get().setLongClickable(batchSelected.isEmpty()); - } - - if (audioViewStub.resolved()) { - audioViewStub.get().setFocusable(!shouldInterceptClicks(messageRecord) && batchSelected.isEmpty()); - audioViewStub.get().setClickable(batchSelected.isEmpty()); - audioViewStub.get().setEnabled(batchSelected.isEmpty()); - } - - if (documentViewStub.resolved()) { - documentViewStub.get().setFocusable(!shouldInterceptClicks(messageRecord) && batchSelected.isEmpty()); - documentViewStub.get().setClickable(batchSelected.isEmpty()); - } - } - - private boolean isCaptionlessMms(MessageRecord messageRecord) { - return TextUtils.isEmpty(messageRecord.getDisplayBody(getContext())) && messageRecord.isMms() && ((MmsMessageRecord) messageRecord).getSlideDeck().getTextSlide() == null; - } - - private boolean hasAudio(MessageRecord messageRecord) { - return messageRecord.isMms() && ((MmsMessageRecord)messageRecord).getSlideDeck().getAudioSlide() != null; - } - - private boolean hasThumbnail(MessageRecord messageRecord) { - return messageRecord.isMms() && ((MmsMessageRecord)messageRecord).getSlideDeck().getThumbnailSlide() != null; - } - - private boolean hasOnlyThumbnail(MessageRecord messageRecord) { - return hasThumbnail(messageRecord) && - !hasAudio(messageRecord) && - !hasDocument(messageRecord); - } - - private boolean hasOnlyDocument(MessageRecord messageRecord) { - return messageRecord.getBody().length() == 0 && - !hasThumbnail(messageRecord) && - !hasAudio(messageRecord) && - hasDocument(messageRecord) && - !hasQuote(messageRecord); - } - - private boolean hasOnlyText(MessageRecord messageRecord) { - return messageRecord.getBody().length() != 0 && - !hasThumbnail(messageRecord) && - !hasAudio(messageRecord) && - !hasDocument(messageRecord) && - !hasQuote(messageRecord); - } - - private boolean hasDocument(MessageRecord messageRecord) { - return messageRecord.isMms() && ((MmsMessageRecord)messageRecord).getSlideDeck().getDocumentSlide() != null; - } - - private boolean hasExtraText(MessageRecord messageRecord) { - boolean hasTextSlide = messageRecord.isMms() && ((MmsMessageRecord)messageRecord).getSlideDeck().getTextSlide() != null; - boolean hasOverflowText = messageRecord.getBody().length() > MAX_BODY_DISPLAY_LENGTH; - - return hasTextSlide || hasOverflowText; - } - - private boolean hasQuote(MessageRecord messageRecord) { - return messageRecord.isMms() && ((MmsMessageRecord)messageRecord).getQuote() != null; - } - - private boolean hasLinkPreview(MessageRecord messageRecord) { - return messageRecord.isMms() && !((MmsMessageRecord)messageRecord).getLinkPreviews().isEmpty(); - } - - private boolean hasBigImageLinkPreview(MessageRecord messageRecord) { - if (!hasLinkPreview(messageRecord)) return false; - - LinkPreview linkPreview = ((MmsMessageRecord) messageRecord).getLinkPreviews().get(0); - int minWidth = getResources().getDimensionPixelSize(R.dimen.media_bubble_min_width); - - return linkPreview.getThumbnail().isPresent() && - linkPreview.getThumbnail().get().getWidth() >= minWidth; - } - - private void setBodyText(MessageRecord messageRecord, @Nullable String searchQuery, boolean isGroupThread) { - bodyText.setClickable(false); - bodyText.setFocusable(false); - bodyText.setTextSize(TypedValue.COMPLEX_UNIT_SP, TextSecurePreferences.getMessageBodyTextSize(context)); - if (isCaptionlessMms(messageRecord)) { - bodyText.setVisibility(View.GONE); - } else { - Spannable text = MentionUtilities.highlightMentions(linkifyMessageBody(messageRecord.getDisplayBody(context), batchSelected.isEmpty()), messageRecord.isOutgoing(), messageRecord.getThreadId(), context); - text = SearchUtil.getHighlightedSpan(locale, () -> new BackgroundColorSpan(Color.WHITE), text, searchQuery); - text = SearchUtil.getHighlightedSpan(locale, () -> new ForegroundColorSpan(Color.BLACK), text, searchQuery); - - if (hasExtraText(messageRecord)) { - bodyText.setOverflowText(getLongMessageSpan(messageRecord)); - } else { - bodyText.setOverflowText(null); - } - - if (!messageRecord.isOpenGroupInvitation()) - bodyText.setText(text); - - bodyText.setVisibility(View.VISIBLE); - } - } - - private void adjustMarginsIfNeeded(MessageRecord messageRecord) { - LinearLayout.LayoutParams bodyTextLayoutParams = (LinearLayout.LayoutParams)bodyText.getLayoutParams(); - bodyTextLayoutParams.topMargin = 0; - if (hasOnlyThumbnail(messageRecord) || hasLinkPreview(messageRecord)) { - int topPadding = 0; - if (groupSenderHolder.getVisibility() == VISIBLE) { - topPadding = (int)getResources().getDimension(R.dimen.medium_spacing); - } - int bottomPadding = 0; - if (messageRecord.getBody().length() > 0) { - bodyTextLayoutParams.topMargin = (int)getResources().getDimension(R.dimen.medium_spacing); - bottomPadding = (int)getResources().getDimension(R.dimen.medium_spacing); - } - bodyBubble.setPadding(0, topPadding, 0, bottomPadding); - } else { - bodyBubble.setPadding(0, (int)getResources().getDimension(R.dimen.medium_spacing), 0, (int)getResources().getDimension(R.dimen.medium_spacing)); - } - bodyText.setLayoutParams(bodyTextLayoutParams); - LinearLayout.LayoutParams senderHolderLayoutParams = (LinearLayout.LayoutParams)groupSenderHolder.getLayoutParams(); - if (groupSenderHolder.getVisibility() == VISIBLE && hasOnlyText(messageRecord)) { - senderHolderLayoutParams.bottomMargin = (int)(getResources().getDisplayMetrics().density * 4); - } else { - senderHolderLayoutParams.bottomMargin = (int)getResources().getDimension(R.dimen.medium_spacing); - } - groupSenderHolder.setLayoutParams(senderHolderLayoutParams); - if (documentViewStub.resolved()) { - LinearLayout.LayoutParams documentViewLayoutParams = (LinearLayout.LayoutParams)documentViewStub.get().getLayoutParams(); - int bottomMargin = 0; - if (hasOnlyDocument(messageRecord)) { - if (footer.getVisibility() == VISIBLE) { - bottomMargin = (int)(4 * getResources().getDisplayMetrics().density); - } else { - bottomMargin = (int)(-4 * getResources().getDisplayMetrics().density); - } - } else { - bottomMargin = (int)(4 * getResources().getDisplayMetrics().density); - } - documentViewLayoutParams.bottomMargin = bottomMargin; - documentViewStub.get().setLayoutParams(documentViewLayoutParams); - } - } - - private void setMediaAttributes(@NonNull MessageRecord messageRecord, - @NonNull Optional previousRecord, - @NonNull Optional nextRecord, - @NonNull Recipient conversationRecipient, - boolean isGroupThread) - { - boolean showControls = !messageRecord.isFailed(); - - if (hasLinkPreview(messageRecord)) { - linkPreviewStub.get().setVisibility(View.VISIBLE); - if (audioViewStub.resolved()) audioViewStub.get().setVisibility(View.GONE); - if (mediaThumbnailStub.resolved()) mediaThumbnailStub.get().setVisibility(View.GONE); - if (documentViewStub.resolved()) documentViewStub.get().setVisibility(View.GONE); - if (stickerStub.resolved()) stickerStub.get().setVisibility(View.GONE); - if (openGroupInvitationViewStub.resolved()) openGroupInvitationViewStub.get().setVisibility(View.GONE); - - //noinspection ConstantConditions - LinkPreview linkPreview = ((MmsMessageRecord) messageRecord).getLinkPreviews().get(0); - - if (hasBigImageLinkPreview(messageRecord)) { - mediaThumbnailStub.get().setVisibility(VISIBLE); - mediaThumbnailStub.get().setImageResource(glideRequests, Collections.singletonList(new ImageSlide(context, linkPreview.getThumbnail().get())), showControls, false); - mediaThumbnailStub.get().setThumbnailClickListener(new LinkPreviewThumbnailClickListener()); - mediaThumbnailStub.get().setDownloadClickListener(downloadClickListener); - mediaThumbnailStub.get().setOnLongClickListener(passthroughClickListener); - - linkPreviewStub.get().setLinkPreview(glideRequests, linkPreview, false, false); - - setThumbnailCorners(messageRecord, previousRecord, nextRecord, isGroupThread); - setLinkPreviewCorners(messageRecord, previousRecord, nextRecord, isGroupThread, true); - - ViewUtil.updateLayoutParams(bodyText, ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); - ViewUtil.updateLayoutParams(groupSenderHolder, ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); - } else { - linkPreviewStub.get().setLinkPreview(glideRequests, linkPreview, true, false); - linkPreviewStub.get().setDownloadClickedListener(downloadClickListener); - setLinkPreviewCorners(messageRecord, previousRecord, nextRecord, isGroupThread, false); - ViewUtil.updateLayoutParams(bodyText, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); - ViewUtil.updateLayoutParams(groupSenderHolder, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); - } - - linkPreviewStub.get().setOnClickListener(linkPreviewClickListener); - linkPreviewStub.get().setOnLongClickListener(passthroughClickListener); - - footer.setVisibility(VISIBLE); - } else if (hasAudio(messageRecord)) { - audioViewStub.get().setVisibility(View.VISIBLE); - if (mediaThumbnailStub.resolved()) mediaThumbnailStub.get().setVisibility(View.GONE); - if (documentViewStub.resolved()) documentViewStub.get().setVisibility(View.GONE); - if (linkPreviewStub.resolved()) linkPreviewStub.get().setVisibility(GONE); - if (stickerStub.resolved()) stickerStub.get().setVisibility(View.GONE); - if (openGroupInvitationViewStub.resolved()) openGroupInvitationViewStub.get().setVisibility(View.GONE); - - //noinspection ConstantConditions - audioViewStub.get().setAudio(((MediaMmsMessageRecord) messageRecord).getSlideDeck().getAudioSlide(), showControls); - audioViewStub.get().setDownloadClickListener(singleDownloadClickListener); - audioViewStub.get().setOnLongClickListener(passthroughClickListener); - - ViewUtil.updateLayoutParams(bodyText, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); - ViewUtil.updateLayoutParams(groupSenderHolder, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); - - footer.setVisibility(VISIBLE); - } else if (hasDocument(messageRecord)) { - documentViewStub.get().setVisibility(View.VISIBLE); - if (mediaThumbnailStub.resolved()) mediaThumbnailStub.get().setVisibility(View.GONE); - if (audioViewStub.resolved()) audioViewStub.get().setVisibility(View.GONE); - if (linkPreviewStub.resolved()) linkPreviewStub.get().setVisibility(GONE); - if (stickerStub.resolved()) stickerStub.get().setVisibility(View.GONE); - if (openGroupInvitationViewStub.resolved()) openGroupInvitationViewStub.get().setVisibility(View.GONE); - - //noinspection ConstantConditions - documentViewStub.get().setDocument(((MediaMmsMessageRecord) messageRecord).getSlideDeck().getDocumentSlide(), showControls); - documentViewStub.get().setDocumentClickListener(new ThumbnailClickListener()); - documentViewStub.get().setDownloadClickListener(singleDownloadClickListener); - documentViewStub.get().setOnLongClickListener(passthroughClickListener); - - ViewUtil.updateLayoutParams(bodyText, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); - ViewUtil.updateLayoutParams(groupSenderHolder, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); - - footer.setVisibility(VISIBLE); - } else if (hasThumbnail(messageRecord)) { - mediaThumbnailStub.get().setVisibility(View.VISIBLE); - if (audioViewStub.resolved()) audioViewStub.get().setVisibility(View.GONE); - if (documentViewStub.resolved()) documentViewStub.get().setVisibility(View.GONE); - if (linkPreviewStub.resolved()) linkPreviewStub.get().setVisibility(GONE); - if (stickerStub.resolved()) stickerStub.get().setVisibility(View.GONE); - if (openGroupInvitationViewStub.resolved()) openGroupInvitationViewStub.get().setVisibility(View.GONE); - - //noinspection ConstantConditions - List thumbnailSlides = ((MmsMessageRecord) messageRecord).getSlideDeck().getThumbnailSlides(); - mediaThumbnailStub.get().setImageResource(glideRequests, - thumbnailSlides, - showControls, - false); - mediaThumbnailStub.get().setThumbnailClickListener(new ThumbnailClickListener()); - mediaThumbnailStub.get().setDownloadClickListener(downloadClickListener); - mediaThumbnailStub.get().setOnLongClickListener(passthroughClickListener); - mediaThumbnailStub.get().setOnClickListener(passthroughClickListener); - mediaThumbnailStub.get().showShade(TextUtils.isEmpty(messageRecord.getDisplayBody(getContext())) && !hasExtraText(messageRecord)); - mediaThumbnailStub.get().setConversationColor(messageRecord.isOutgoing() ? defaultBubbleColor - : messageRecord.getRecipient().getColor().toConversationColor(context)); - mediaThumbnailStub.get().setBorderless(false); - - setThumbnailCorners(messageRecord, previousRecord, nextRecord, isGroupThread); - - ViewUtil.updateLayoutParams(bodyText, ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); - ViewUtil.updateLayoutParams(groupSenderHolder, ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); - - footer.setVisibility(VISIBLE); - } else if (messageRecord.isOpenGroupInvitation()) { - openGroupInvitationViewStub.get().setVisibility(View.VISIBLE); - if (mediaThumbnailStub.resolved()) mediaThumbnailStub.get().setVisibility(View.GONE); - if (audioViewStub.resolved()) audioViewStub.get().setVisibility(View.GONE); - if (documentViewStub.resolved()) documentViewStub.get().setVisibility(View.GONE); - if (linkPreviewStub.resolved()) linkPreviewStub.get().setVisibility(GONE); - if (stickerStub.resolved()) stickerStub.get().setVisibility(View.GONE); - - UpdateMessageData updateMessageData = UpdateMessageData.Companion.fromJSON(messageRecord.getBody()); - String name = null, url = null; - if (updateMessageData.getKind() instanceof UpdateMessageData.Kind.OpenGroupInvitation) { - UpdateMessageData.Kind.OpenGroupInvitation data = (UpdateMessageData.Kind.OpenGroupInvitation)updateMessageData.getKind(); - name = data.getGroupName(); - url = data.getGroupUrl(); - } - - openGroupInvitationViewStub.get().setOpenGroup(name, url, messageRecord.isOutgoing()); - openGroupInvitationViewStub.get().setOnLongClickListener(passthroughClickListener); - - bodyText.setVisibility(View.GONE); - - ViewUtil.updateLayoutParams(bodyText, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); - ViewUtil.updateLayoutParams(groupSenderHolder, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); - } else { - if (mediaThumbnailStub.resolved()) mediaThumbnailStub.get().setVisibility(View.GONE); - if (audioViewStub.resolved()) audioViewStub.get().setVisibility(View.GONE); - if (documentViewStub.resolved()) documentViewStub.get().setVisibility(View.GONE); - if (linkPreviewStub.resolved()) linkPreviewStub.get().setVisibility(GONE); - if (stickerStub.resolved()) stickerStub.get().setVisibility(View.GONE); - - ViewUtil.updateLayoutParams(bodyText, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); - ViewUtil.updateLayoutParams(groupSenderHolder, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); - - footer.setVisibility(VISIBLE); - } - } - - private void setThumbnailCorners(@NonNull MessageRecord current, - @NonNull Optional previous, - @NonNull Optional next, - boolean isGroupThread) - { - int defaultRadius = readDimen(R.dimen.message_corner_radius); - int collapseRadius = readDimen(R.dimen.message_corner_collapse_radius); - - int topLeft = defaultRadius; - int topRight = defaultRadius; - int bottomLeft = defaultRadius; - int bottomRight = defaultRadius; - - if (isSingularMessage(current, previous, next, isGroupThread)) { - topLeft = defaultRadius; - topRight = defaultRadius; - bottomLeft = defaultRadius; - bottomRight = defaultRadius; - } else if (isStartOfMessageCluster(current, previous, isGroupThread)) { - if (current.isOutgoing()) { - bottomRight = collapseRadius; - } else { - bottomLeft = collapseRadius; - } - } else if (isEndOfMessageCluster(current, next, isGroupThread)) { - if (current.isOutgoing()) { - topRight = collapseRadius; - } else { - topLeft = collapseRadius; - } - } else { - if (current.isOutgoing()) { - topRight = collapseRadius; - bottomRight = collapseRadius; - } else { - topLeft = collapseRadius; - bottomLeft = collapseRadius; - } - } - - if (!TextUtils.isEmpty(current.getDisplayBody(getContext()))) { - bottomLeft = 0; - bottomRight = 0; - } - - if (isStartOfMessageCluster(current, previous, isGroupThread) && !current.isOutgoing() && isGroupThread) { - topLeft = 0; - topRight = 0; - } - - if (hasQuote(messageRecord)) { - topLeft = 0; - topRight = 0; - } - - if (hasLinkPreview(messageRecord) || hasExtraText(messageRecord)) { - bottomLeft = 0; - bottomRight = 0; - } - - mediaThumbnailStub.get().setCorners(topLeft, topRight, bottomRight, bottomLeft); - } - private void setLinkPreviewCorners(@NonNull MessageRecord current, @NonNull Optional previous, @NonNull Optional next, boolean isGroupThread, boolean bigImage) { - int defaultRadius = readDimen(R.dimen.message_corner_radius); - int collapseRadius = readDimen(R.dimen.message_corner_collapse_radius); - - if (bigImage) { - linkPreviewStub.get().setCorners(0, 0); - } else if (isStartOfMessageCluster(current, previous, isGroupThread) && !current.isOutgoing() && isGroupThread) { - linkPreviewStub.get().setCorners(0, 0); - } else if (isSingularMessage(current, previous, next, isGroupThread) || isStartOfMessageCluster(current, previous, isGroupThread)) { - linkPreviewStub.get().setCorners(defaultRadius, defaultRadius); - } else if (current.isOutgoing()) { - linkPreviewStub.get().setCorners(defaultRadius, collapseRadius); - } else { - linkPreviewStub.get().setCorners(collapseRadius, defaultRadius); - } - } - - private void setContactPhoto(@NonNull Recipient recipient) { - if (messageRecord == null) { return; } // TODO: Figure out how this happens - LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams)bodyBubble.getLayoutParams(); - int groupThreadMargin = (int)((12 * getResources().getDisplayMetrics().density) + getResources().getDimension(R.dimen.small_profile_picture_size)); - int defaultMargin = 0; - long threadID = messageRecord.getThreadId(); - Recipient r = DatabaseFactory.getThreadDatabase(context).getRecipientForThreadId(threadID); - String threadName = r != null ? r.getName() : ""; - boolean isRSSFeed = threadName != null && (threadName.equals("Loki News") || threadName.equals("Session Updates")); - layoutParams.setMarginStart((groupThread && !isRSSFeed) ? groupThreadMargin : defaultMargin); - bodyBubble.setLayoutParams(layoutParams); - if (profilePictureView == null) { return; } - String publicKey = recipient.getAddress().toString(); - profilePictureView.setPublicKey(publicKey); - String displayName = recipient.getName(); - profilePictureView.setDisplayName(displayName); - profilePictureView.setAdditionalPublicKey(null); - profilePictureView.setGlide(glideRequests); - profilePictureView.update(); - } - - private SpannableString linkifyMessageBody(SpannableString messageBody, boolean shouldLinkifyAllLinks) { - int linkPattern = Linkify.WEB_URLS | Linkify.EMAIL_ADDRESSES | Linkify.PHONE_NUMBERS; - boolean hasLinks = Linkify.addLinks(messageBody, shouldLinkifyAllLinks ? linkPattern : 0); - - if (hasLinks) { - Stream.of(messageBody.getSpans(0, messageBody.length(), URLSpan.class)) - .filterNot(url -> LinkPreviewUtil.isLegalUrl(url.getURL())) - .forEach(messageBody::removeSpan); - - URLSpan[] urlSpans = messageBody.getSpans(0, messageBody.length(), URLSpan.class); - - for (URLSpan urlSpan : urlSpans) { - int start = messageBody.getSpanStart(urlSpan); - int end = messageBody.getSpanEnd(urlSpan); - messageBody.setSpan(new LongClickCopySpan(urlSpan.getURL()), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - } - } - return messageBody; - } - - private void setStatusIcons(MessageRecord messageRecord) { - bodyText.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0); - - if (messageRecord.isFailed()) { - alertView.setFailed(); - } else { - alertView.setNone(); - } - } - - private void setQuote(@NonNull MessageRecord current, @NonNull Optional previous, @NonNull Optional next, boolean isGroupThread) { - if (current.isMms() && !current.isMmsNotification() && ((MediaMmsMessageRecord)current).getQuote() != null) { - Quote quote = ((MediaMmsMessageRecord)current).getQuote(); - //noinspection ConstantConditions - String quoteBody = MentionUtilities.highlightMentions(quote.getText(), current.getThreadId(), context); - quoteView.setQuote(glideRequests, quote.getId(), Recipient.from(context, quote.getAuthor(), true), quoteBody, quote.isOriginalMissing(), quote.getAttachment(), conversationRecipient); - quoteView.setVisibility(View.VISIBLE); - quoteView.getLayoutParams().width = ViewGroup.LayoutParams.WRAP_CONTENT; - - quoteView.setOnClickListener(view -> { - if (eventListener != null && batchSelected.isEmpty()) { - eventListener.onQuoteClicked((MmsMessageRecord) current); - } else { - passthroughClickListener.onClick(view); - } - }); - - quoteView.setOnLongClickListener(passthroughClickListener); - - if (isStartOfMessageCluster(current, previous, isGroupThread)) { - if (current.isOutgoing()) { - quoteView.setTopCornerSizes(true, true); - } else if (isGroupThread) { - quoteView.setTopCornerSizes(false, false); - } else { - quoteView.setTopCornerSizes(true, true); - } - } else if (!isSingularMessage(current, previous, next, isGroupThread)) { - if (current.isOutgoing()) { - quoteView.setTopCornerSizes(true, false); - } else { - quoteView.setTopCornerSizes(false, true); - } - } - - if (mediaThumbnailStub.resolved()) { - ViewUtil.setTopMargin(mediaThumbnailStub.get(), readDimen(R.dimen.message_bubble_top_padding)); - } - } else { - quoteView.dismiss(); - - if (mediaThumbnailStub.resolved()) { - ViewUtil.setTopMargin(mediaThumbnailStub.get(), 0); - } - } - } - - private void setGutterSizes(@NonNull MessageRecord current, boolean isGroupThread) { - if (isGroupThread && current.isOutgoing()) { - ViewUtil.setLeftMargin(container, readDimen(R.dimen.conversation_group_left_gutter)); - } else if (current.isOutgoing()) { - ViewUtil.setLeftMargin(container, readDimen(R.dimen.conversation_individual_left_gutter)); - } - } - - private void setFooter(@NonNull MessageRecord current, @NonNull Optional next, @NonNull Locale locale, boolean isGroupThread) { - ViewUtil.updateLayoutParams(footer, LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); - - footer.setVisibility(GONE); - stickerFooter.setVisibility(GONE); - if (mediaThumbnailStub.resolved()) mediaThumbnailStub.get().getFooter().setVisibility(GONE); - - boolean differentTimestamps = next.isPresent() && !DateUtils.isSameExtendedRelativeTimestamp(context, locale, next.get().getTimestamp(), current.getTimestamp()); - - if (current.getExpiresIn() > 0 || current.isPending() || - current.isFailed() || differentTimestamps || isEndOfMessageCluster(current, next, isGroupThread)) - { - ConversationItemFooter activeFooter = getActiveFooter(current); - activeFooter.setVisibility(VISIBLE); - activeFooter.setMessageRecord(current, locale); - } - } - - private ConversationItemFooter getActiveFooter(@NonNull MessageRecord messageRecord) { - if (hasOnlyThumbnail(messageRecord) && TextUtils.isEmpty(messageRecord.getDisplayBody(getContext()))) { - return mediaThumbnailStub.get().getFooter(); - } else { - return footer; - } - } - - private int readDimen(@DimenRes int dimenId) { - return context.getResources().getDimensionPixelOffset(dimenId); - } - - private boolean shouldInterceptClicks(MessageRecord messageRecord) { - return batchSelected.isEmpty() && (messageRecord.isFailed() && !messageRecord.isMmsNotification()); - } - - @SuppressLint("SetTextI18n") - private void setGroupMessageStatus(MessageRecord messageRecord, Recipient recipient) { - if (groupThread && !messageRecord.isOutgoing()) { - String sessionID = recipient.getAddress().serialize(); - Contact contact = DatabaseFactory.getSessionContactDatabase(context).getContactWithSessionID(sessionID); - String displayName; - if (contact != null) { - Contact.ContactContext context = (this.conversationRecipient.isOpenGroupRecipient()) ? Contact.ContactContext.OPEN_GROUP : Contact.ContactContext.REGULAR; - displayName = contact.displayName(context); - } else { - displayName = sessionID; - } - - this.groupSender.setText(displayName); - - if (recipient.getName() == null && !TextUtils.isEmpty(recipient.getProfileName())) { - this.groupSenderProfileName.setText("~" + recipient.getProfileName()); - this.groupSenderProfileName.setVisibility(View.VISIBLE); - } else { - this.groupSenderProfileName.setText(null); - this.groupSenderProfileName.setVisibility(View.GONE); - } - } - } - - private void setGroupAuthorColor(@NonNull MessageRecord messageRecord) { - groupSender.setTextColor(ThemeUtil.getThemedColor(context, R.attr.conversation_item_received_text_primary_color)); - groupSenderProfileName.setTextColor(ThemeUtil.getThemedColor(context, R.attr.conversation_item_received_text_primary_color)); - } - - private void setAuthor(@NonNull MessageRecord current, @NonNull Optional previous, @NonNull Optional next, boolean isGroupThread) { - Recipient recipient = DatabaseFactory.getThreadDatabase(context).getRecipientForThreadId(current.getThreadId()); - String threadName = null; - if (recipient != null) { - threadName = recipient.getName(); - } - boolean isRSSFeed = threadName != null && (threadName.equals("Loki News") || threadName.equals("Session Updates")); - if (isGroupThread && !isRSSFeed && !current.isOutgoing()) { - contactPhotoHolder.setVisibility(VISIBLE); - - if (!previous.isPresent() || previous.get().isUpdate() || !current.getRecipient().getAddress().equals(previous.get().getRecipient().getAddress()) || - !DateUtils.isSameDay(previous.get().getTimestamp(), current.getTimestamp())) - { - groupSenderHolder.setVisibility(VISIBLE); - } else { - groupSenderHolder.setVisibility(GONE); - } - - if (!previous.isPresent() || previous.get().isUpdate() || !current.getRecipient().getAddress().equals(previous.get().getRecipient().getAddress())) { - profilePictureView.setVisibility(VISIBLE); - int visibility = View.GONE; - - OpenGroupV2 openGroupV2 = DatabaseFactory.getLokiThreadDatabase(context).getOpenGroupChat(messageRecord.getThreadId()); - if (openGroupV2 != null) { - boolean isModerator = OpenGroupAPIV2.isUserModerator(current.getRecipient().getAddress().toString(), openGroupV2.getRoom(), openGroupV2.getServer()); - visibility = isModerator ? View.VISIBLE : View.GONE; - } - - moderatorIconImageView.setVisibility(visibility); - } else { - profilePictureView.setVisibility(GONE); - moderatorIconImageView.setVisibility(GONE); - - } - } else { - groupSenderHolder.setVisibility(GONE); - - if (contactPhotoHolder != null) { - contactPhotoHolder.setVisibility(GONE); - moderatorIconImageView.setVisibility(GONE); - } - } - } - - private void setMessageShape(@NonNull MessageRecord current, @NonNull Optional previous, @NonNull Optional next, boolean isGroupThread) { - int background; - if (isSingularMessage(current, previous, next, isGroupThread)) { - background = current.isOutgoing() ? R.drawable.message_bubble_background_sent_alone : R.drawable.message_bubble_background_received_alone; - } else if (isStartOfMessageCluster(current, previous, isGroupThread)) { - background = current.isOutgoing() ? R.drawable.message_bubble_background_sent_start : R.drawable.message_bubble_background_received_start; - } else if (isEndOfMessageCluster(current, next, isGroupThread)) { - background = current.isOutgoing() ? R.drawable.message_bubble_background_sent_end : R.drawable.message_bubble_background_received_end; - } else { - background = current.isOutgoing() ? R.drawable.message_bubble_background_sent_middle : R.drawable.message_bubble_background_received_middle; - } - - bodyBubble.setBackgroundResource(background); - } - - private boolean isStartOfMessageCluster(@NonNull MessageRecord current, @NonNull Optional previous, boolean isGroupThread) { - if (isGroupThread) { - return !previous.isPresent() || previous.get().isUpdate() || !DateUtils.isSameDay(current.getTimestamp(), previous.get().getTimestamp()) || - !current.getRecipient().getAddress().equals(previous.get().getRecipient().getAddress()); - } else { - return !previous.isPresent() || previous.get().isUpdate() || !DateUtils.isSameDay(current.getTimestamp(), previous.get().getTimestamp()) || - current.isOutgoing() != previous.get().isOutgoing(); - } - } - - private boolean isEndOfMessageCluster(@NonNull MessageRecord current, @NonNull Optional next, boolean isGroupThread) { - if (isGroupThread) { - return !next.isPresent() || next.get().isUpdate() || !DateUtils.isSameDay(current.getTimestamp(), next.get().getTimestamp()) || - !current.getRecipient().getAddress().equals(next.get().getRecipient().getAddress()); - } else { - return !next.isPresent() || next.get().isUpdate() || !DateUtils.isSameDay(current.getTimestamp(), next.get().getTimestamp()) || - current.isOutgoing() != next.get().isOutgoing(); - } - } - - private boolean isSingularMessage(@NonNull MessageRecord current, @NonNull Optional previous, @NonNull Optional next, boolean isGroupThread) { - return isStartOfMessageCluster(current, previous, isGroupThread) && isEndOfMessageCluster(current, next, isGroupThread); - } - - private void setMessageSpacing(@NonNull Context context, @NonNull MessageRecord current, @NonNull Optional previous, @NonNull Optional next, boolean isGroupThread) { - int spacingTop = readDimen(context, R.dimen.conversation_vertical_message_spacing_collapse); - int spacingBottom = spacingTop; - - if (isStartOfMessageCluster(current, previous, isGroupThread)) { - spacingTop = readDimen(context, R.dimen.conversation_vertical_message_spacing_default); - } - - if (isEndOfMessageCluster(current, next, isGroupThread)) { - spacingBottom = readDimen(context, R.dimen.conversation_vertical_message_spacing_default); - } - - ViewUtil.setPaddingTop(this, spacingTop); - ViewUtil.setPaddingBottom(this, spacingBottom); - } - - private int readDimen(@NonNull Context context, @DimenRes int dimenId) { - return context.getResources().getDimensionPixelOffset(dimenId); - } - - /// Event handlers - - private Spannable getLongMessageSpan(@NonNull MessageRecord messageRecord) { - String message; - Runnable action; - - if (messageRecord.isMms()) { - TextSlide slide = ((MmsMessageRecord) messageRecord).getSlideDeck().getTextSlide(); - - if (slide != null && slide.asAttachment().getTransferState() == AttachmentTransferProgress.TRANSFER_PROGRESS_DONE) { - message = getResources().getString(R.string.ConversationItem_read_more); - action = () -> eventListener.onMoreTextClicked(conversationRecipient.getAddress(), messageRecord.getId(), messageRecord.isMms()); - } else if (slide != null && slide.asAttachment().getTransferState() == AttachmentTransferProgress.TRANSFER_PROGRESS_STARTED) { - message = getResources().getString(R.string.ConversationItem_pending); - action = () -> {}; - } else if (slide != null) { - message = getResources().getString(R.string.ConversationItem_download_more); - action = () -> singleDownloadClickListener.onClick(bodyText, slide); - } else { - message = getResources().getString(R.string.ConversationItem_read_more); - action = () -> eventListener.onMoreTextClicked(conversationRecipient.getAddress(), messageRecord.getId(), messageRecord.isMms()); - } - } else { - message = getResources().getString(R.string.ConversationItem_read_more); - action = () -> eventListener.onMoreTextClicked(conversationRecipient.getAddress(), messageRecord.getId(), messageRecord.isMms()); - } - - SpannableStringBuilder span = new SpannableStringBuilder(message); - CharacterStyle style = new ClickableSpan() { - @Override - public void onClick(@NonNull View widget) { - if (eventListener != null && batchSelected.isEmpty()) { - action.run(); - } - } - - @Override - public void updateDrawState(@NonNull TextPaint ds) { - ds.setTypeface(Typeface.DEFAULT_BOLD); - } - }; - span.setSpan(style, 0, span.length(), Spanned.SPAN_INCLUSIVE_EXCLUSIVE); - return span; - } - - @Override - public void onModified(final Recipient modified) { - Util.runOnMain(() -> { - setBubbleState(messageRecord); - setContactPhoto(recipient); - setGroupMessageStatus(messageRecord, recipient); - setAudioViewTint(messageRecord, conversationRecipient); - }); - } - - private class LinkPreviewClickListener implements View.OnClickListener { - @Override - public void onClick(View view) { - if (eventListener != null && batchSelected.isEmpty() && messageRecord.isMms() && !((MmsMessageRecord) messageRecord).getLinkPreviews().isEmpty()) { - eventListener.onLinkPreviewClicked(((MmsMessageRecord) messageRecord).getLinkPreviews().get(0)); - } else { - passthroughClickListener.onClick(view); - } - } - } - - private class LinkPreviewThumbnailClickListener implements SlideClickListener { - public void onClick(final View v, final Slide slide) { - if (eventListener != null && batchSelected.isEmpty() && messageRecord.isMms() && !((MmsMessageRecord) messageRecord).getLinkPreviews().isEmpty()) { - eventListener.onLinkPreviewClicked(((MmsMessageRecord) messageRecord).getLinkPreviews().get(0)); - } else { - performClick(); - } - } - } - - private class AttachmentDownloadClickListener implements SlidesClickedListener { - @Override - public void onClick(View v, final List slides) { - Log.i(TAG, "onClick() for attachment download"); - Log.i(TAG, "Scheduling push attachment downloads for " + slides.size() + " items"); - - for (Slide slide : slides) { - JobQueue.getShared().add( - new AttachmentDownloadJob(messageRecord.getId(), - ((DatabaseAttachment)slide.asAttachment()).getAttachmentId().getRowId()) - ); - } - } - } - - private class SlideClickPassthroughListener implements SlideClickListener { - - private final SlidesClickedListener original; - - private SlideClickPassthroughListener(@NonNull SlidesClickedListener original) { - this.original = original; - } - - @Override - public void onClick(View v, Slide slide) { - original.onClick(v, Collections.singletonList(slide)); - } - } - - private class StickerClickListener implements SlideClickListener { - @Override - public void onClick(View v, Slide slide) { - if (shouldInterceptClicks(messageRecord) || !batchSelected.isEmpty()) { - performClick(); - } - } - } - - private class ThumbnailClickListener implements SlideClickListener { - public void onClick(final View v, final Slide slide) { - if (shouldInterceptClicks(messageRecord) || !batchSelected.isEmpty()) { - performClick(); - } else if (MediaPreviewActivity.isContentTypeSupported(slide.getContentType()) && slide.getUri() != null) { - Intent intent = new Intent(context, MediaPreviewActivity.class); - intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); - intent.setDataAndType(slide.getUri(), slide.getContentType()); - intent.putExtra(MediaPreviewActivity.ADDRESS_EXTRA, conversationRecipient.getAddress()); - intent.putExtra(MediaPreviewActivity.OUTGOING_EXTRA, messageRecord.isOutgoing()); - intent.putExtra(MediaPreviewActivity.DATE_EXTRA, messageRecord.getTimestamp()); - intent.putExtra(MediaPreviewActivity.SIZE_EXTRA, slide.asAttachment().getSize()); - intent.putExtra(MediaPreviewActivity.CAPTION_EXTRA, slide.getCaption().orNull()); - intent.putExtra(MediaPreviewActivity.LEFT_IS_RECENT_EXTRA, false); - - context.startActivity(intent); - } else if (slide.getUri() != null) { - Log.i(TAG, "Clicked: " + slide.getUri() + " , " + slide.getContentType()); - Uri publicUri = PartAuthority.getAttachmentPublicUri(slide.getUri()); - Log.i(TAG, "Public URI: " + publicUri); - Intent intent = new Intent(Intent.ACTION_VIEW); - intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); - intent.setDataAndType(PartAuthority.getAttachmentPublicUri(slide.getUri()), slide.getContentType()); - try { - context.startActivity(intent); - } catch (ActivityNotFoundException anfe) { - Log.w(TAG, "No activity existed to view the media."); - Toast.makeText(context, R.string.ConversationItem_unable_to_open_media, Toast.LENGTH_LONG).show(); - } - } - } - } - - private class PassthroughClickListener implements View.OnLongClickListener, View.OnClickListener { - - @Override - public boolean onLongClick(View v) { - if (bodyText.hasSelection()) { - return false; - } - performLongClick(); - return true; - } - - @Override - public void onClick(View v) { - performClick(); - } - } - - private class ClickListener implements View.OnClickListener { - private OnClickListener parent; - - ClickListener(@Nullable OnClickListener parent) { - this.parent = parent; - } - - public void onClick(View v) { - if (!shouldInterceptClicks(messageRecord) && parent != null) { - parent.onClick(v); - } else if (messageRecord.isFailed()) { - Intent intent = new Intent(context, MessageDetailsActivity.class); - intent.putExtra(MessageDetailsActivity.MESSAGE_ID_EXTRA, messageRecord.getId()); - intent.putExtra(MessageDetailsActivity.THREAD_ID_EXTRA, messageRecord.getThreadId()); - intent.putExtra(MessageDetailsActivity.TYPE_EXTRA, messageRecord.isMms() ? MmsSmsDatabase.MMS_TRANSPORT : MmsSmsDatabase.SMS_TRANSPORT); - intent.putExtra(MessageDetailsActivity.IS_PUSH_GROUP_EXTRA, groupThread); - intent.putExtra(MessageDetailsActivity.ADDRESS_EXTRA, conversationRecipient.getAddress()); - context.startActivity(intent); - } - } - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationPopupActivity.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationPopupActivity.java deleted file mode 100644 index ece1bb784..000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationPopupActivity.java +++ /dev/null @@ -1,120 +0,0 @@ -package org.thoughtcrime.securesms.conversation; - -import android.content.Intent; -import android.os.Build.VERSION; -import android.os.Build.VERSION_CODES; -import android.os.Bundle; -import androidx.core.app.ActivityOptionsCompat; -import android.view.Display; -import android.view.Gravity; -import android.view.Menu; -import android.view.MenuInflater; -import android.view.MenuItem; -import android.view.WindowManager; - -import org.session.libsignal.utilities.Log; -import org.session.libsignal.utilities.ListenableFuture; -import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2; - -import java.util.concurrent.ExecutionException; - -import network.loki.messenger.R; - -public class ConversationPopupActivity extends ConversationActivity { - - private static final String TAG = ConversationPopupActivity.class.getSimpleName(); - - @Override - protected void onPreCreate() { - super.onPreCreate(); - overridePendingTransition(R.anim.slide_from_top, R.anim.slide_to_top); - } - - @Override - protected void onCreate(Bundle bundle, boolean ready) { - getWindow().setFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND, - WindowManager.LayoutParams.FLAG_DIM_BEHIND); - - WindowManager.LayoutParams params = getWindow().getAttributes(); - params.alpha = 1.0f; - params.dimAmount = 0.1f; - params.gravity = Gravity.TOP; - getWindow().setAttributes(params); - - Display display = getWindowManager().getDefaultDisplay(); - int width = display.getWidth(); - int height = display.getHeight(); - - if (height > width) getWindow().setLayout((int) (width * .85), (int) (height * .5)); - else getWindow().setLayout((int) (width * .7), (int) (height * .75)); - - super.onCreate(bundle, ready); - } - - @Override - protected void onResume() { - super.onResume(); - composeText.requestFocus(); - quickAttachmentToggle.disable(); - } - - @Override - protected void onPause() { - super.onPause(); - if (isFinishing()) overridePendingTransition(R.anim.slide_from_top, R.anim.slide_to_top); - } - - @Override - public boolean onPrepareOptionsMenu(Menu menu) { - MenuInflater inflater = this.getMenuInflater(); - menu.clear(); - - inflater.inflate(R.menu.conversation_popup, menu); - return true; - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - case R.id.menu_expand: - saveDraft().addListener(new ListenableFuture.Listener() { - @Override - public void onSuccess(Long result) { - ActivityOptionsCompat transition = ActivityOptionsCompat.makeScaleUpAnimation(getWindow().getDecorView(), 0, 0, getWindow().getAttributes().width, getWindow().getAttributes().height); - Intent intent = new Intent(ConversationPopupActivity.this, ConversationActivityV2.class); - intent.putExtra(ConversationActivityV2.ADDRESS, getRecipient().getAddress()); - intent.putExtra(ConversationActivityV2.THREAD_ID, result); - - if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN) { - startActivity(intent, transition.toBundle()); - } else { - startActivity(intent); - overridePendingTransition(R.anim.fade_scale_in, R.anim.slide_to_right); - } - - finish(); - } - - @Override - public void onFailure(ExecutionException e) { - Log.w(TAG, e); - } - }); - return true; - } - - return false; - } - - @Override - protected void initializeActionBar() { - super.initializeActionBar(); - getSupportActionBar().setDisplayHomeAsUpEnabled(false); - } - - @Override - protected void sendComplete(long threadId) { - super.sendComplete(threadId); - finish(); - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationSearchViewModel.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationSearchViewModel.java deleted file mode 100644 index d24539982..000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationSearchViewModel.java +++ /dev/null @@ -1,146 +0,0 @@ -package org.thoughtcrime.securesms.conversation; - -import android.app.Application; -import androidx.lifecycle.AndroidViewModel; -import androidx.lifecycle.LiveData; -import android.content.Context; -import androidx.annotation.NonNull; - -import org.thoughtcrime.securesms.contacts.ContactAccessor; -import org.thoughtcrime.securesms.database.CursorList; -import org.thoughtcrime.securesms.database.DatabaseFactory; -import org.thoughtcrime.securesms.search.SearchRepository; -import org.thoughtcrime.securesms.search.model.MessageResult; -import org.thoughtcrime.securesms.util.CloseableLiveData; -import org.session.libsession.utilities.Debouncer; -import org.session.libsession.utilities.Util; -import org.session.libsession.utilities.concurrent.SignalExecutors; - -import java.io.Closeable; -import java.util.List; - -public class ConversationSearchViewModel extends AndroidViewModel { - - private final SearchRepository searchRepository; - private final CloseableLiveData result; - private final Debouncer debouncer; - - private boolean firstSearch; - private boolean searchOpen; - private String activeQuery; - private long activeThreadId; - - public ConversationSearchViewModel(@NonNull Application application) { - super(application); - Context context = application.getApplicationContext(); - result = new CloseableLiveData<>(); - debouncer = new Debouncer(500); - searchRepository = new SearchRepository(context, - DatabaseFactory.getSearchDatabase(context), - DatabaseFactory.getThreadDatabase(context), - ContactAccessor.getInstance(), - SignalExecutors.SERIAL); - } - - LiveData getSearchResults() { - return result; - } - - void onQueryUpdated(@NonNull String query, long threadId) { - if (firstSearch && query.length() < 2) { - result.postValue(new SearchResult(CursorList.emptyList(), 0)); - return; - } - - if (query.equals(activeQuery)) { - return; - } - - updateQuery(query, threadId); - } - - void onMissingResult() { - if (activeQuery != null) { - updateQuery(activeQuery, activeThreadId); - } - } - - void onMoveUp() { - debouncer.clear(); - - CursorList messages = (CursorList) result.getValue().getResults(); - int position = Math.min(result.getValue().getPosition() + 1, messages.size() - 1); - - result.setValue(new SearchResult(messages, position), false); - } - - void onMoveDown() { - debouncer.clear(); - - CursorList messages = (CursorList) result.getValue().getResults(); - int position = Math.max(result.getValue().getPosition() - 1, 0); - - result.setValue(new SearchResult(messages, position), false); - } - - - void onSearchOpened() { - searchOpen = true; - firstSearch = true; - } - - void onSearchClosed() { - searchOpen = false; - debouncer.clear(); - result.close(); - } - - @Override - protected void onCleared() { - super.onCleared(); - result.close(); - } - - private void updateQuery(@NonNull String query, long threadId) { - activeQuery = query; - activeThreadId = threadId; - - debouncer.publish(() -> { - firstSearch = false; - - searchRepository.query(query, threadId, messages -> { - Util.runOnMain(() -> { - if (searchOpen && query.equals(activeQuery)) { - result.setValue(new SearchResult(messages, 0)); - } else { - messages.close(); - } - }); - }); - }); - } - - static class SearchResult implements Closeable { - - private final CursorList results; - private final int position; - - SearchResult(CursorList results, int position) { - this.results = results; - this.position = position; - } - - public List getResults() { - return results; - } - - public int getPosition() { - return position; - } - - @Override - public void close() { - results.close(); - } - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationUpdateItem.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationUpdateItem.java deleted file mode 100644 index 6dfd2fa88..000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationUpdateItem.java +++ /dev/null @@ -1,198 +0,0 @@ -package org.thoughtcrime.securesms.conversation; - -import android.content.Context; -import android.graphics.PorterDuff; -import android.graphics.PorterDuffColorFilter; -import android.util.AttributeSet; -import android.view.View; -import android.widget.ImageView; -import android.widget.LinearLayout; -import android.widget.TextView; - -import androidx.annotation.ColorInt; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import org.session.libsession.messaging.sending_receiving.data_extraction.DataExtractionNotificationInfoMessage; -import org.session.libsession.utilities.ExpirationUtil; -import org.session.libsession.utilities.Util; -import org.session.libsession.utilities.recipients.Recipient; -import org.session.libsession.utilities.recipients.RecipientModifiedListener; -import org.session.libsignal.utilities.guava.Optional; -import org.thoughtcrime.securesms.BindableConversationItem; -import org.thoughtcrime.securesms.database.model.MessageRecord; -import org.thoughtcrime.securesms.loki.utilities.GeneralUtilitiesKt; -import org.thoughtcrime.securesms.mms.GlideRequests; -import org.thoughtcrime.securesms.util.DateUtils; - -import java.util.Locale; -import java.util.Set; - -import network.loki.messenger.R; - -//TODO Remove this class. -public class ConversationUpdateItem extends LinearLayout - implements RecipientModifiedListener, BindableConversationItem -{ - private static final String TAG = ConversationUpdateItem.class.getSimpleName(); - - private Set batchSelected; - - private ImageView icon; - private TextView title; - private TextView body; - private TextView date; - private Recipient sender; - private MessageRecord messageRecord; - private Locale locale; - - public ConversationUpdateItem(Context context) { - super(context); - } - - public ConversationUpdateItem(Context context, AttributeSet attrs) { - super(context, attrs); - } - - @Override - public void onFinishInflate() { - super.onFinishInflate(); - - this.icon = findViewById(R.id.conversation_update_icon); - this.title = findViewById(R.id.conversation_update_title); - this.body = findViewById(R.id.conversation_update_body); - this.date = findViewById(R.id.conversation_update_date); - - this.setOnClickListener(new InternalClickListener(null)); - } - - @Override - public void bind(@NonNull MessageRecord messageRecord, - @NonNull Optional previousMessageRecord, - @NonNull Optional nextMessageRecord, - @NonNull GlideRequests glideRequests, - @NonNull Locale locale, - @NonNull Set batchSelected, - @NonNull Recipient conversationRecipient, - @Nullable String searchQuery, - boolean pulseUpdate) - { - this.batchSelected = batchSelected; - - bind(messageRecord, locale); - } - - @Override - public void setEventListener(@Nullable EventListener listener) { - // No events to report yet - } - - @Override - public MessageRecord getMessageRecord() { - return messageRecord; - } - - private void bind(@NonNull MessageRecord messageRecord, @NonNull Locale locale) { - this.messageRecord = messageRecord; - this.sender = messageRecord.getIndividualRecipient(); - this.locale = locale; - - this.sender.addListener(this); - - if (messageRecord.isCallLog()) setCallRecord(messageRecord); - else if (messageRecord.isExpirationTimerUpdate()) setTimerRecord(messageRecord); - else if (messageRecord.isScreenshotNotification()) setDataExtractionRecord(messageRecord, DataExtractionNotificationInfoMessage.Kind.SCREENSHOT); - else if (messageRecord.isMediaSavedNotification()) setDataExtractionRecord(messageRecord, DataExtractionNotificationInfoMessage.Kind.MEDIA_SAVED); - else throw new AssertionError("Neither group nor log nor joined."); - - if (batchSelected.contains(messageRecord)) setSelected(true); - else setSelected(false); - } - - private void setCallRecord(MessageRecord messageRecord) { - if (messageRecord.isIncomingCall()) icon.setImageResource(R.drawable.ic_call_received_grey600_24dp); - else if (messageRecord.isOutgoingCall()) icon.setImageResource(R.drawable.ic_call_made_grey600_24dp); - else icon.setImageResource(R.drawable.ic_call_missed_grey600_24dp); - - body.setText(messageRecord.getDisplayBody(getContext())); - date.setText(DateUtils.getExtendedRelativeTimeSpanString(getContext(), locale, messageRecord.getDateReceived())); - - title.setVisibility(GONE); - body.setVisibility(VISIBLE); - date.setVisibility(View.VISIBLE); - } - - private void setTimerRecord(final MessageRecord messageRecord) { - @ColorInt int color = GeneralUtilitiesKt.getColorWithID(getResources(), R.color.text, getContext().getTheme()); - if (messageRecord.getExpiresIn() > 0) { - icon.setImageResource(R.drawable.ic_timer); - icon.setColorFilter(new PorterDuffColorFilter(color, PorterDuff.Mode.MULTIPLY)); - } else { - icon.setImageResource(R.drawable.ic_timer_disabled); - icon.setColorFilter(new PorterDuffColorFilter(color, PorterDuff.Mode.MULTIPLY)); - } - - title.setText(ExpirationUtil.getExpirationDisplayValue(getContext(), (int)(messageRecord.getExpiresIn() / 1000))); - body.setText(messageRecord.getDisplayBody(getContext())); - - title.setVisibility(VISIBLE); - body.setVisibility(VISIBLE); - date.setVisibility(GONE); - } - - private void setDataExtractionRecord(final MessageRecord messageRecord, DataExtractionNotificationInfoMessage.Kind kind) { - @ColorInt int color = GeneralUtilitiesKt.getColorWithID(getResources(), R.color.text, getContext().getTheme()); - if (kind == DataExtractionNotificationInfoMessage.Kind.SCREENSHOT) { - icon.setImageResource(R.drawable.quick_camera_dark); - } else if (kind == DataExtractionNotificationInfoMessage.Kind.MEDIA_SAVED) { - icon.setImageResource(R.drawable.ic_file_download_white_36dp); - } - icon.setColorFilter(new PorterDuffColorFilter(color, PorterDuff.Mode.MULTIPLY)); - - body.setText(messageRecord.getDisplayBody(getContext())); - - title.setVisibility(VISIBLE); - body.setVisibility(VISIBLE); - date.setVisibility(GONE); - } - - private void setTextMessageRecord(MessageRecord messageRecord) { - body.setText(messageRecord.getDisplayBody(getContext())); - - icon.setVisibility(GONE); - title.setVisibility(GONE); - body.setVisibility(VISIBLE); - date.setVisibility(GONE); - } - - @Override - public void onModified(Recipient recipient) { - Util.runOnMain(() -> bind(messageRecord, locale)); - } - - @Override - public void setOnClickListener(View.OnClickListener l) { - super.setOnClickListener(new InternalClickListener(l)); - } - - @Override - public void unbind() { - if (sender != null) { - sender.removeListener(this); - } - } - - private class InternalClickListener implements View.OnClickListener { - - @Nullable private final View.OnClickListener parent; - - InternalClickListener(@Nullable View.OnClickListener parent) { - this.parent = parent; - } - - @Override - public void onClick(View v) { - - } - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt index 075caf5f6..eeb9deaca 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt @@ -75,7 +75,7 @@ import org.thoughtcrime.securesms.ApplicationContext import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity import org.thoughtcrime.securesms.audio.AudioRecorder import org.thoughtcrime.securesms.contactshare.SimpleTextWatcher -import org.thoughtcrime.securesms.conversation.ConversationActivity + import org.thoughtcrime.securesms.conversation.v2.dialogs.* import org.thoughtcrime.securesms.conversation.v2.input_bar.InputBarButton import org.thoughtcrime.securesms.conversation.v2.input_bar.InputBarDelegate diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/activities/CreateClosedGroupActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/activities/CreateClosedGroupActivity.kt index df4f3ce61..9b40454a2 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/activities/CreateClosedGroupActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/activities/CreateClosedGroupActivity.kt @@ -17,7 +17,7 @@ import nl.komponents.kovenant.ui.successUi import org.session.libsession.messaging.sending_receiving.MessageSender import org.session.libsession.messaging.sending_receiving.groupSizeLimit import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity -import org.thoughtcrime.securesms.conversation.ConversationActivity + import org.session.libsession.utilities.Address import org.session.libsession.utilities.DistributionTypes import org.thoughtcrime.securesms.database.DatabaseFactory @@ -138,7 +138,6 @@ class CreateClosedGroupActivity : PassphraseRequiredActionBarActivity(), LoaderM private fun openConversationActivity(context: Context, threadId: Long, recipient: Recipient) { val intent = Intent(context, ConversationActivityV2::class.java) intent.putExtra(ConversationActivityV2.THREAD_ID, threadId) - intent.putExtra(ConversationActivity.DISTRIBUTION_TYPE_EXTRA, DistributionTypes.DEFAULT) intent.putExtra(ConversationActivityV2.ADDRESS, recipient.address) context.startActivity(intent) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/activities/CreatePrivateChatActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/activities/CreatePrivateChatActivity.kt index 7d429bf22..6eedd9701 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/activities/CreatePrivateChatActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/activities/CreatePrivateChatActivity.kt @@ -29,7 +29,7 @@ import org.session.libsession.utilities.TextSecurePreferences import org.session.libsession.utilities.recipients.Recipient import org.session.libsignal.utilities.PublicKeyValidation import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity -import org.thoughtcrime.securesms.conversation.ConversationActivity + import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2 import org.thoughtcrime.securesms.database.DatabaseFactory import org.thoughtcrime.securesms.loki.fragments.ScanQRCodeWrapperFragment @@ -114,11 +114,9 @@ class CreatePrivateChatActivity : PassphraseRequiredActionBarActivity(), ScanQRC val recipient = Recipient.from(this, Address.fromSerialized(hexEncodedPublicKey), false) val intent = Intent(this, ConversationActivityV2::class.java) intent.putExtra(ConversationActivityV2.ADDRESS, recipient.address) - intent.putExtra(ConversationActivity.TEXT_EXTRA, getIntent().getStringExtra(ConversationActivity.TEXT_EXTRA)) intent.setDataAndType(getIntent().data, getIntent().type) val existingThread = DatabaseFactory.getThreadDatabase(this).getThreadIdIfExistsFor(recipient) intent.putExtra(ConversationActivityV2.THREAD_ID, existingThread) - intent.putExtra(ConversationActivity.DISTRIBUTION_TYPE_EXTRA, DistributionTypes.DEFAULT) startActivity(intent) finish() } diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/activities/HomeActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/activities/HomeActivity.kt index 474d8036d..58eda3cc9 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/activities/HomeActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/activities/HomeActivity.kt @@ -37,7 +37,7 @@ import org.session.libsignal.utilities.toHexString import org.session.libsignal.utilities.ThreadUtils import org.thoughtcrime.securesms.ApplicationContext import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity -import org.thoughtcrime.securesms.conversation.ConversationActivity + import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2 import org.thoughtcrime.securesms.database.DatabaseFactory import org.thoughtcrime.securesms.database.model.ThreadRecord diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/activities/JoinPublicChatActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/activities/JoinPublicChatActivity.kt index dc038cc0c..4ce8bbde0 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/activities/JoinPublicChatActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/activities/JoinPublicChatActivity.kt @@ -32,7 +32,7 @@ import org.session.libsignal.utilities.Log import org.session.libsignal.utilities.PublicKeyValidation import org.thoughtcrime.securesms.BaseActionBarActivity import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity -import org.thoughtcrime.securesms.conversation.ConversationActivity + import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2 import org.thoughtcrime.securesms.groups.GroupManager import org.thoughtcrime.securesms.loki.api.OpenGroupManager @@ -130,7 +130,6 @@ class JoinPublicChatActivity : PassphraseRequiredActionBarActivity(), ScanQRCode private fun openConversationActivity(context: Context, threadId: Long, recipient: Recipient) { val intent = Intent(context, ConversationActivityV2::class.java) intent.putExtra(ConversationActivityV2.THREAD_ID, threadId) - intent.putExtra(ConversationActivity.DISTRIBUTION_TYPE_EXTRA, DistributionTypes.DEFAULT) intent.putExtra(ConversationActivityV2.ADDRESS, recipient.address) context.startActivity(intent) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/activities/LinkDeviceActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/activities/LinkDeviceActivity.kt index ec2a76e76..8cfa9d9e3 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/activities/LinkDeviceActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/activities/LinkDeviceActivity.kt @@ -14,7 +14,6 @@ import androidx.fragment.app.FragmentPagerAdapter import androidx.lifecycle.lifecycleScope import com.google.android.material.snackbar.Snackbar import kotlinx.android.synthetic.main.activity_link_device.* -import kotlinx.android.synthetic.main.conversation_activity.* import kotlinx.android.synthetic.main.fragment_recovery_phrase.* import kotlinx.coroutines.Job import kotlinx.coroutines.delay diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/activities/QRCodeActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/activities/QRCodeActivity.kt index 96d175ef9..2f5638d95 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/activities/QRCodeActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/activities/QRCodeActivity.kt @@ -14,7 +14,7 @@ import kotlinx.android.synthetic.main.activity_qr_code.* import kotlinx.android.synthetic.main.fragment_view_my_qr_code.* import network.loki.messenger.R import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity -import org.thoughtcrime.securesms.conversation.ConversationActivity + import org.session.libsession.utilities.Address import org.session.libsession.utilities.DistributionTypes import org.thoughtcrime.securesms.database.DatabaseFactory @@ -56,11 +56,9 @@ class QRCodeActivity : PassphraseRequiredActionBarActivity(), ScanQRCodeWrapperF val recipient = Recipient.from(this, Address.fromSerialized(hexEncodedPublicKey), false) val intent = Intent(this, ConversationActivityV2::class.java) intent.putExtra(ConversationActivityV2.ADDRESS, recipient.address) - intent.putExtra(ConversationActivity.TEXT_EXTRA, getIntent().getStringExtra(ConversationActivity.TEXT_EXTRA)) intent.setDataAndType(getIntent().data, getIntent().type) val existingThread = DatabaseFactory.getThreadDatabase(this).getThreadIdIfExistsFor(recipient) intent.putExtra(ConversationActivityV2.THREAD_ID, existingThread) - intent.putExtra(ConversationActivity.DISTRIBUTION_TYPE_EXTRA, DistributionTypes.DEFAULT) startActivity(intent) finish() } diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaSendFragment.java b/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaSendFragment.java index 0df65497c..ce1ccb2bb 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaSendFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaSendFragment.java @@ -82,7 +82,7 @@ public class MediaSendFragment extends Fragment implements ViewTreeObserver.OnGl private InputAwareLayout hud; private View captionAndRail; private ImageButton sendButton; - private ComposeText composeText; + private ComposeText composeText; private ViewGroup composeContainer; private EmojiEditText captionText; private EmojiToggle emojiToggle; diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/DefaultMessageNotifier.java b/app/src/main/java/org/thoughtcrime/securesms/notifications/DefaultMessageNotifier.java index a0c1046e0..c02663843 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/DefaultMessageNotifier.java +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/DefaultMessageNotifier.java @@ -48,7 +48,7 @@ import org.session.libsignal.utilities.Util; import org.session.libsignal.utilities.Log; import org.thoughtcrime.securesms.ApplicationContext; import org.thoughtcrime.securesms.contactshare.ContactUtil; -import org.thoughtcrime.securesms.conversation.ConversationActivity; + import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.MessagingDatabase.MarkedMessageInfo; diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/NotificationItem.java b/app/src/main/java/org/thoughtcrime/securesms/notifications/NotificationItem.java index c2041a84f..991989e8d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/NotificationItem.java +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/NotificationItem.java @@ -8,7 +8,7 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.core.app.TaskStackBuilder; -import org.thoughtcrime.securesms.conversation.ConversationActivity; + import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2; import org.thoughtcrime.securesms.mms.SlideDeck; import org.session.libsession.utilities.recipients.Recipient; diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/NotificationState.java b/app/src/main/java/org/thoughtcrime/securesms/notifications/NotificationState.java index 9330ec9e3..fe934e229 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/NotificationState.java +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/NotificationState.java @@ -7,11 +7,10 @@ import android.net.Uri; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import org.thoughtcrime.securesms.conversation.ConversationActivity; -import org.thoughtcrime.securesms.conversation.ConversationPopupActivity; import org.session.libsignal.utilities.Log; import org.session.libsession.utilities.recipients.Recipient; import org.session.libsession.utilities.recipients.Recipient.*; +import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2; import java.util.LinkedHashSet; import java.util.LinkedList; @@ -167,9 +166,9 @@ public class NotificationState { public PendingIntent getQuickReplyIntent(Context context, Recipient recipient) { if (threads.size() != 1) throw new AssertionError("We only support replies to single thread notifications! " + threads.size()); - Intent intent = new Intent(context, ConversationPopupActivity.class); - intent.putExtra(ConversationActivity.ADDRESS_EXTRA, recipient.getAddress()); - intent.putExtra(ConversationActivity.THREAD_ID_EXTRA, (long)threads.toArray()[0]); + Intent intent = new Intent(context, ConversationActivityV2.class); + intent.putExtra(ConversationActivityV2.ADDRESS, recipient.getAddress()); + intent.putExtra(ConversationActivityV2.THREAD_ID, (long)threads.toArray()[0]); intent.setData((Uri.parse("custom://"+System.currentTimeMillis()))); return PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/CommunicationActions.java b/app/src/main/java/org/thoughtcrime/securesms/util/CommunicationActions.java index e8dd7cd58..9bb71ad5c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/CommunicationActions.java +++ b/app/src/main/java/org/thoughtcrime/securesms/util/CommunicationActions.java @@ -11,7 +11,7 @@ import androidx.core.app.TaskStackBuilder; import android.text.TextUtils; import android.widget.Toast; -import org.thoughtcrime.securesms.conversation.ConversationActivity; + import network.loki.messenger.R; import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2; @@ -36,11 +36,6 @@ public class CommunicationActions { Intent intent = new Intent(context, ConversationActivityV2.class); intent.putExtra(ConversationActivityV2.ADDRESS, recipient.getAddress()); intent.putExtra(ConversationActivityV2.THREAD_ID, threadId); - intent.putExtra(ConversationActivity.TIMING_EXTRA, System.currentTimeMillis()); - - if (!TextUtils.isEmpty(text)) { - intent.putExtra(ConversationActivity.TEXT_EXTRA, text); - } if (backStack != null) { backStack.addNextIntent(intent); diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/Util.java b/app/src/main/java/org/thoughtcrime/securesms/util/Util.java index 2384fe482..41b18f024 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/Util.java +++ b/app/src/main/java/org/thoughtcrime/securesms/util/Util.java @@ -19,7 +19,6 @@ package org.thoughtcrime.securesms.util; import android.annotation.TargetApi; import android.app.ActivityManager; import android.content.Context; -import android.content.pm.PackageManager; import android.os.Build.VERSION; import android.os.Build.VERSION_CODES; import android.text.TextUtils; diff --git a/app/src/main/res/drawable-hdpi/ic_add_white_24dp.png b/app/src/main/res/drawable-hdpi/ic_add_white_24dp.png deleted file mode 100644 index b48ba111a..000000000 Binary files a/app/src/main/res/drawable-hdpi/ic_add_white_24dp.png and /dev/null differ diff --git a/app/src/main/res/drawable-hdpi/ic_call_made_grey600_24dp.png b/app/src/main/res/drawable-hdpi/ic_call_made_grey600_24dp.png deleted file mode 100644 index 7d0807b0b..000000000 Binary files a/app/src/main/res/drawable-hdpi/ic_call_made_grey600_24dp.png and /dev/null differ diff --git a/app/src/main/res/drawable-hdpi/ic_call_missed_grey600_24dp.png b/app/src/main/res/drawable-hdpi/ic_call_missed_grey600_24dp.png deleted file mode 100644 index 0241747c6..000000000 Binary files a/app/src/main/res/drawable-hdpi/ic_call_missed_grey600_24dp.png and /dev/null differ diff --git a/app/src/main/res/drawable-hdpi/ic_call_received_grey600_24dp.png b/app/src/main/res/drawable-hdpi/ic_call_received_grey600_24dp.png deleted file mode 100644 index 5c9a88d12..000000000 Binary files a/app/src/main/res/drawable-hdpi/ic_call_received_grey600_24dp.png and /dev/null differ diff --git a/app/src/main/res/drawable-hdpi/ic_check_white_24dp.png b/app/src/main/res/drawable-hdpi/ic_check_white_24dp.png deleted file mode 100644 index 468ea5acd..000000000 Binary files a/app/src/main/res/drawable-hdpi/ic_check_white_24dp.png and /dev/null differ diff --git a/app/src/main/res/drawable-hdpi/ic_favorite_grey600_24dp.png b/app/src/main/res/drawable-hdpi/ic_favorite_grey600_24dp.png deleted file mode 100644 index 2bdad7d3f..000000000 Binary files a/app/src/main/res/drawable-hdpi/ic_favorite_grey600_24dp.png and /dev/null differ diff --git a/app/src/main/res/drawable-hdpi/ic_group_grey600_24dp.png b/app/src/main/res/drawable-hdpi/ic_group_grey600_24dp.png deleted file mode 100644 index 816fe2053..000000000 Binary files a/app/src/main/res/drawable-hdpi/ic_group_grey600_24dp.png and /dev/null differ diff --git a/app/src/main/res/drawable-hdpi/ic_menu_login.png b/app/src/main/res/drawable-hdpi/ic_menu_login.png deleted file mode 100644 index 163f0aa85..000000000 Binary files a/app/src/main/res/drawable-hdpi/ic_menu_login.png and /dev/null differ diff --git a/app/src/main/res/drawable-hdpi/ic_refresh_white_24dp.png b/app/src/main/res/drawable-hdpi/ic_refresh_white_24dp.png deleted file mode 100644 index 38cd52d9d..000000000 Binary files a/app/src/main/res/drawable-hdpi/ic_refresh_white_24dp.png and /dev/null differ diff --git a/app/src/main/res/drawable-hdpi/ic_security_white_24dp.png b/app/src/main/res/drawable-hdpi/ic_security_white_24dp.png deleted file mode 100644 index 262800a4d..000000000 Binary files a/app/src/main/res/drawable-hdpi/ic_security_white_24dp.png and /dev/null differ diff --git a/app/src/main/res/drawable-hdpi/ic_timer_disabled.png b/app/src/main/res/drawable-hdpi/ic_timer_disabled.png deleted file mode 100644 index 9e6127d54..000000000 Binary files a/app/src/main/res/drawable-hdpi/ic_timer_disabled.png and /dev/null differ diff --git a/app/src/main/res/drawable-hdpi/quick_camera_dark.png b/app/src/main/res/drawable-hdpi/quick_camera_dark.png deleted file mode 100644 index aa3a3e571..000000000 Binary files a/app/src/main/res/drawable-hdpi/quick_camera_dark.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/ic_add_white_24dp.png b/app/src/main/res/drawable-mdpi/ic_add_white_24dp.png deleted file mode 100644 index eeba9f669..000000000 Binary files a/app/src/main/res/drawable-mdpi/ic_add_white_24dp.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/ic_call_made_grey600_24dp.png b/app/src/main/res/drawable-mdpi/ic_call_made_grey600_24dp.png deleted file mode 100644 index f275722ff..000000000 Binary files a/app/src/main/res/drawable-mdpi/ic_call_made_grey600_24dp.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/ic_call_missed_grey600_24dp.png b/app/src/main/res/drawable-mdpi/ic_call_missed_grey600_24dp.png deleted file mode 100644 index 609ef5261..000000000 Binary files a/app/src/main/res/drawable-mdpi/ic_call_missed_grey600_24dp.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/ic_call_received_grey600_24dp.png b/app/src/main/res/drawable-mdpi/ic_call_received_grey600_24dp.png deleted file mode 100644 index 685982d8e..000000000 Binary files a/app/src/main/res/drawable-mdpi/ic_call_received_grey600_24dp.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/ic_check_white_24dp.png b/app/src/main/res/drawable-mdpi/ic_check_white_24dp.png deleted file mode 100644 index 6a93d6919..000000000 Binary files a/app/src/main/res/drawable-mdpi/ic_check_white_24dp.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/ic_favorite_grey600_24dp.png b/app/src/main/res/drawable-mdpi/ic_favorite_grey600_24dp.png deleted file mode 100644 index 63f13a33b..000000000 Binary files a/app/src/main/res/drawable-mdpi/ic_favorite_grey600_24dp.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/ic_group_grey600_24dp.png b/app/src/main/res/drawable-mdpi/ic_group_grey600_24dp.png deleted file mode 100644 index 2c69b7cc1..000000000 Binary files a/app/src/main/res/drawable-mdpi/ic_group_grey600_24dp.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/ic_menu_login.png b/app/src/main/res/drawable-mdpi/ic_menu_login.png deleted file mode 100644 index 07dd6a510..000000000 Binary files a/app/src/main/res/drawable-mdpi/ic_menu_login.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/ic_refresh_white_24dp.png b/app/src/main/res/drawable-mdpi/ic_refresh_white_24dp.png deleted file mode 100644 index 48e37b75c..000000000 Binary files a/app/src/main/res/drawable-mdpi/ic_refresh_white_24dp.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/ic_security_white_24dp.png b/app/src/main/res/drawable-mdpi/ic_security_white_24dp.png deleted file mode 100644 index 44ee7346e..000000000 Binary files a/app/src/main/res/drawable-mdpi/ic_security_white_24dp.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/ic_timer_disabled.png b/app/src/main/res/drawable-mdpi/ic_timer_disabled.png deleted file mode 100644 index b2cd951a9..000000000 Binary files a/app/src/main/res/drawable-mdpi/ic_timer_disabled.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/quick_camera_dark.png b/app/src/main/res/drawable-mdpi/quick_camera_dark.png deleted file mode 100644 index 4dbf6c77b..000000000 Binary files a/app/src/main/res/drawable-mdpi/quick_camera_dark.png and /dev/null differ diff --git a/app/src/main/res/drawable-xhdpi/ic_add_white_24dp.png b/app/src/main/res/drawable-xhdpi/ic_add_white_24dp.png deleted file mode 100644 index 67bb598e5..000000000 Binary files a/app/src/main/res/drawable-xhdpi/ic_add_white_24dp.png and /dev/null differ diff --git a/app/src/main/res/drawable-xhdpi/ic_call_made_grey600_24dp.png b/app/src/main/res/drawable-xhdpi/ic_call_made_grey600_24dp.png deleted file mode 100644 index 1e609bb5e..000000000 Binary files a/app/src/main/res/drawable-xhdpi/ic_call_made_grey600_24dp.png and /dev/null differ diff --git a/app/src/main/res/drawable-xhdpi/ic_call_missed_grey600_24dp.png b/app/src/main/res/drawable-xhdpi/ic_call_missed_grey600_24dp.png deleted file mode 100644 index fb5e2794f..000000000 Binary files a/app/src/main/res/drawable-xhdpi/ic_call_missed_grey600_24dp.png and /dev/null differ diff --git a/app/src/main/res/drawable-xhdpi/ic_call_received_grey600_24dp.png b/app/src/main/res/drawable-xhdpi/ic_call_received_grey600_24dp.png deleted file mode 100644 index 91b5587a8..000000000 Binary files a/app/src/main/res/drawable-xhdpi/ic_call_received_grey600_24dp.png and /dev/null differ diff --git a/app/src/main/res/drawable-xhdpi/ic_check_white_24dp.png b/app/src/main/res/drawable-xhdpi/ic_check_white_24dp.png deleted file mode 100644 index 9868d19a4..000000000 Binary files a/app/src/main/res/drawable-xhdpi/ic_check_white_24dp.png and /dev/null differ diff --git a/app/src/main/res/drawable-xhdpi/ic_favorite_grey600_24dp.png b/app/src/main/res/drawable-xhdpi/ic_favorite_grey600_24dp.png deleted file mode 100644 index c4ad1cb30..000000000 Binary files a/app/src/main/res/drawable-xhdpi/ic_favorite_grey600_24dp.png and /dev/null differ diff --git a/app/src/main/res/drawable-xhdpi/ic_group_grey600_24dp.png b/app/src/main/res/drawable-xhdpi/ic_group_grey600_24dp.png deleted file mode 100644 index 1865da694..000000000 Binary files a/app/src/main/res/drawable-xhdpi/ic_group_grey600_24dp.png and /dev/null differ diff --git a/app/src/main/res/drawable-xhdpi/ic_menu_login.png b/app/src/main/res/drawable-xhdpi/ic_menu_login.png deleted file mode 100644 index 17c2e5c39..000000000 Binary files a/app/src/main/res/drawable-xhdpi/ic_menu_login.png and /dev/null differ diff --git a/app/src/main/res/drawable-xhdpi/ic_refresh_white_24dp.png b/app/src/main/res/drawable-xhdpi/ic_refresh_white_24dp.png deleted file mode 100644 index 7e5c6ef19..000000000 Binary files a/app/src/main/res/drawable-xhdpi/ic_refresh_white_24dp.png and /dev/null differ diff --git a/app/src/main/res/drawable-xhdpi/ic_security_white_24dp.png b/app/src/main/res/drawable-xhdpi/ic_security_white_24dp.png deleted file mode 100644 index 7e306c303..000000000 Binary files a/app/src/main/res/drawable-xhdpi/ic_security_white_24dp.png and /dev/null differ diff --git a/app/src/main/res/drawable-xhdpi/ic_timer_disabled.png b/app/src/main/res/drawable-xhdpi/ic_timer_disabled.png deleted file mode 100644 index 45165abec..000000000 Binary files a/app/src/main/res/drawable-xhdpi/ic_timer_disabled.png and /dev/null differ diff --git a/app/src/main/res/drawable-xhdpi/quick_camera_dark.png b/app/src/main/res/drawable-xhdpi/quick_camera_dark.png deleted file mode 100644 index 753805275..000000000 Binary files a/app/src/main/res/drawable-xhdpi/quick_camera_dark.png and /dev/null differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_add_white_24dp.png b/app/src/main/res/drawable-xxhdpi/ic_add_white_24dp.png deleted file mode 100644 index 0fdced8fc..000000000 Binary files a/app/src/main/res/drawable-xxhdpi/ic_add_white_24dp.png and /dev/null differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_call_made_grey600_24dp.png b/app/src/main/res/drawable-xxhdpi/ic_call_made_grey600_24dp.png deleted file mode 100644 index 66a9ff46e..000000000 Binary files a/app/src/main/res/drawable-xxhdpi/ic_call_made_grey600_24dp.png and /dev/null differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_call_missed_grey600_24dp.png b/app/src/main/res/drawable-xxhdpi/ic_call_missed_grey600_24dp.png deleted file mode 100644 index 84c13861c..000000000 Binary files a/app/src/main/res/drawable-xxhdpi/ic_call_missed_grey600_24dp.png and /dev/null differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_call_received_grey600_24dp.png b/app/src/main/res/drawable-xxhdpi/ic_call_received_grey600_24dp.png deleted file mode 100644 index f4be04c67..000000000 Binary files a/app/src/main/res/drawable-xxhdpi/ic_call_received_grey600_24dp.png and /dev/null differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_check_white_24dp.png b/app/src/main/res/drawable-xxhdpi/ic_check_white_24dp.png deleted file mode 100644 index 2a7c32de6..000000000 Binary files a/app/src/main/res/drawable-xxhdpi/ic_check_white_24dp.png and /dev/null differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_favorite_grey600_24dp.png b/app/src/main/res/drawable-xxhdpi/ic_favorite_grey600_24dp.png deleted file mode 100644 index 11f108dad..000000000 Binary files a/app/src/main/res/drawable-xxhdpi/ic_favorite_grey600_24dp.png and /dev/null differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_group_grey600_24dp.png b/app/src/main/res/drawable-xxhdpi/ic_group_grey600_24dp.png deleted file mode 100644 index 2aa030e4c..000000000 Binary files a/app/src/main/res/drawable-xxhdpi/ic_group_grey600_24dp.png and /dev/null differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_refresh_white_24dp.png b/app/src/main/res/drawable-xxhdpi/ic_refresh_white_24dp.png deleted file mode 100644 index 72128fe69..000000000 Binary files a/app/src/main/res/drawable-xxhdpi/ic_refresh_white_24dp.png and /dev/null differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_security_white_24dp.png b/app/src/main/res/drawable-xxhdpi/ic_security_white_24dp.png deleted file mode 100644 index 7bcb2fd01..000000000 Binary files a/app/src/main/res/drawable-xxhdpi/ic_security_white_24dp.png and /dev/null differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_timer_disabled.png b/app/src/main/res/drawable-xxhdpi/ic_timer_disabled.png deleted file mode 100644 index fabf9ffdb..000000000 Binary files a/app/src/main/res/drawable-xxhdpi/ic_timer_disabled.png and /dev/null differ diff --git a/app/src/main/res/drawable-xxhdpi/quick_camera_dark.png b/app/src/main/res/drawable-xxhdpi/quick_camera_dark.png deleted file mode 100644 index c1a3549bf..000000000 Binary files a/app/src/main/res/drawable-xxhdpi/quick_camera_dark.png and /dev/null differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_check_white_24dp.png b/app/src/main/res/drawable-xxxhdpi/ic_check_white_24dp.png deleted file mode 100644 index d601ca823..000000000 Binary files a/app/src/main/res/drawable-xxxhdpi/ic_check_white_24dp.png and /dev/null differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_favorite_grey600_24dp.png b/app/src/main/res/drawable-xxxhdpi/ic_favorite_grey600_24dp.png deleted file mode 100644 index e8ee60e7e..000000000 Binary files a/app/src/main/res/drawable-xxxhdpi/ic_favorite_grey600_24dp.png and /dev/null differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_security_white_24dp.png b/app/src/main/res/drawable-xxxhdpi/ic_security_white_24dp.png deleted file mode 100644 index b1eddbd6c..000000000 Binary files a/app/src/main/res/drawable-xxxhdpi/ic_security_white_24dp.png and /dev/null differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_timer_disabled.png b/app/src/main/res/drawable-xxxhdpi/ic_timer_disabled.png deleted file mode 100644 index 6301f7bad..000000000 Binary files a/app/src/main/res/drawable-xxxhdpi/ic_timer_disabled.png and /dev/null differ diff --git a/app/src/main/res/drawable/compose_divider_background.xml b/app/src/main/res/drawable/compose_divider_background.xml deleted file mode 100644 index 0046fe4e4..000000000 --- a/app/src/main/res/drawable/compose_divider_background.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - diff --git a/app/src/main/res/drawable/contact_photo_background.xml b/app/src/main/res/drawable/contact_photo_background.xml deleted file mode 100644 index ff1153bf2..000000000 --- a/app/src/main/res/drawable/contact_photo_background.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/conversation_back_button_background.xml b/app/src/main/res/drawable/conversation_back_button_background.xml deleted file mode 100644 index f259e83e7..000000000 --- a/app/src/main/res/drawable/conversation_back_button_background.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/conversation_home_touch_highlight.xml b/app/src/main/res/drawable/conversation_home_touch_highlight.xml deleted file mode 100644 index 5fbf5ba7d..000000000 --- a/app/src/main/res/drawable/conversation_home_touch_highlight.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/conversation_item_background.xml b/app/src/main/res/drawable/conversation_item_background.xml deleted file mode 100644 index dfae25e4f..000000000 --- a/app/src/main/res/drawable/conversation_item_background.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/app/src/main/res/drawable/conversation_item_background_animated.xml b/app/src/main/res/drawable/conversation_item_background_animated.xml deleted file mode 100644 index 4206e6a57..000000000 --- a/app/src/main/res/drawable/conversation_item_background_animated.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/app/src/main/res/drawable/error_round.xml b/app/src/main/res/drawable/error_round.xml deleted file mode 100644 index 56cc75291..000000000 --- a/app/src/main/res/drawable/error_round.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_baseline_arrow_back_compact_24.xml b/app/src/main/res/drawable/ic_baseline_arrow_back_compact_24.xml deleted file mode 100644 index f63216015..000000000 --- a/app/src/main/res/drawable/ic_baseline_arrow_back_compact_24.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/ic_baseline_key_24.xml b/app/src/main/res/drawable/ic_baseline_key_24.xml deleted file mode 100644 index 2316df207..000000000 --- a/app/src/main/res/drawable/ic_baseline_key_24.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/ic_baseline_refresh_24.xml b/app/src/main/res/drawable/ic_baseline_refresh_24.xml deleted file mode 100644 index f2be45bab..000000000 --- a/app/src/main/res/drawable/ic_baseline_refresh_24.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/ic_circle_plus.xml b/app/src/main/res/drawable/ic_circle_plus.xml deleted file mode 100644 index 5e243e529..000000000 --- a/app/src/main/res/drawable/ic_circle_plus.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - - - diff --git a/app/src/main/res/drawable/ic_shield.xml b/app/src/main/res/drawable/ic_shield.xml deleted file mode 100644 index 67a9845da..000000000 --- a/app/src/main/res/drawable/ic_shield.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/info_round.xml b/app/src/main/res/drawable/info_round.xml deleted file mode 100644 index 668eaf3be..000000000 --- a/app/src/main/res/drawable/info_round.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/message_bubble_background.xml b/app/src/main/res/drawable/message_bubble_background.xml deleted file mode 100644 index 9c4347ae6..000000000 --- a/app/src/main/res/drawable/message_bubble_background.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - diff --git a/app/src/main/res/drawable/profile_picture_view_large_foreground.xml b/app/src/main/res/drawable/profile_picture_view_large_foreground.xml deleted file mode 100644 index e73b68d16..000000000 --- a/app/src/main/res/drawable/profile_picture_view_large_foreground.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/profile_picture_view_medium_foreground.xml b/app/src/main/res/drawable/profile_picture_view_medium_foreground.xml deleted file mode 100644 index 808be8d0e..000000000 --- a/app/src/main/res/drawable/profile_picture_view_medium_foreground.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/profile_picture_view_rss_medium_background.xml b/app/src/main/res/drawable/profile_picture_view_rss_medium_background.xml deleted file mode 100644 index a12f21fce..000000000 --- a/app/src/main/res/drawable/profile_picture_view_rss_medium_background.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/profile_picture_view_small_foreground.xml b/app/src/main/res/drawable/profile_picture_view_small_foreground.xml deleted file mode 100644 index 6a46584bf..000000000 --- a/app/src/main/res/drawable/profile_picture_view_small_foreground.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/prominent_dialog_button_background.xml b/app/src/main/res/drawable/prominent_dialog_button_background.xml deleted file mode 100644 index de3409d48..000000000 --- a/app/src/main/res/drawable/prominent_dialog_button_background.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/scroll_to_bottom_button_background.xml b/app/src/main/res/drawable/scroll_to_bottom_button_background.xml deleted file mode 100644 index 387c75be9..000000000 --- a/app/src/main/res/drawable/scroll_to_bottom_button_background.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/session_logo_white.xml b/app/src/main/res/drawable/session_logo_white.xml deleted file mode 100644 index 9927e8822..000000000 --- a/app/src/main/res/drawable/session_logo_white.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - diff --git a/app/src/main/res/layout/activity_backup_restore.xml b/app/src/main/res/layout/activity_backup_restore.xml deleted file mode 100644 index af86967c7..000000000 --- a/app/src/main/res/layout/activity_backup_restore.xml +++ /dev/null @@ -1,125 +0,0 @@ - - - - - - - - - - - - - - - - - - - - -