Merge pull request #626 from oxen-io/cleanup
Remove Unused Code & Resources
@ -206,18 +206,6 @@
|
|||||||
android:resource="@mipmap/ic_launcher" />
|
android:resource="@mipmap/ic_launcher" />
|
||||||
</activity-alias>
|
</activity-alias>
|
||||||
|
|
||||||
<activity
|
|
||||||
android:name="org.thoughtcrime.securesms.conversation.ConversationActivity"
|
|
||||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
|
|
||||||
android:launchMode="singleTask"
|
|
||||||
android:screenOrientation="portrait"
|
|
||||||
android:theme="@style/Theme.TextSecure.DayNight.NoActionBar"
|
|
||||||
android:parentActivityName="org.thoughtcrime.securesms.loki.activities.HomeActivity"
|
|
||||||
android:windowSoftInputMode="stateUnchanged">
|
|
||||||
<meta-data
|
|
||||||
android:name="android.support.PARENT_ACTIVITY"
|
|
||||||
android:value="org.thoughtcrime.securesms.loki.activities.HomeActivity" />
|
|
||||||
</activity>
|
|
||||||
<activity
|
<activity
|
||||||
android:name="org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2"
|
android:name="org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2"
|
||||||
android:screenOrientation="portrait"
|
android:screenOrientation="portrait"
|
||||||
@ -235,21 +223,6 @@
|
|||||||
android:name="org.thoughtcrime.securesms.longmessage.LongMessageActivity"
|
android:name="org.thoughtcrime.securesms.longmessage.LongMessageActivity"
|
||||||
android:screenOrientation="portrait"
|
android:screenOrientation="portrait"
|
||||||
android:theme="@style/Theme.TextSecure.DayNight" />
|
android:theme="@style/Theme.TextSecure.DayNight" />
|
||||||
<activity
|
|
||||||
android:name="org.thoughtcrime.securesms.conversation.ConversationPopupActivity"
|
|
||||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
|
|
||||||
android:excludeFromRecents="true"
|
|
||||||
android:launchMode="singleTask"
|
|
||||||
android:taskAffinity=""
|
|
||||||
android:windowSoftInputMode="stateVisible" />
|
|
||||||
<activity
|
|
||||||
android:name="org.thoughtcrime.securesms.MessageDetailsActivity"
|
|
||||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
|
|
||||||
android:label="Message Details"
|
|
||||||
android:screenOrientation="portrait"
|
|
||||||
android:theme="@style/Theme.TextSecure.DayNight"
|
|
||||||
android:launchMode="singleTask"
|
|
||||||
android:windowSoftInputMode="stateHidden" />
|
|
||||||
<activity
|
<activity
|
||||||
android:name="org.thoughtcrime.securesms.DatabaseUpgradeActivity"
|
android:name="org.thoughtcrime.securesms.DatabaseUpgradeActivity"
|
||||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
|
||||||
|
@ -1,476 +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 <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
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<Cursor>, 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<RecipientDeliveryStatus> 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<Cursor> 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<Cursor> 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<Cursor> 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<Void,Void,List<RecipientDeliveryStatus>> {
|
|
||||||
|
|
||||||
private final WeakReference<Context> 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<RecipientDeliveryStatus> doInBackground(Void... voids) {
|
|
||||||
Context context = getContext();
|
|
||||||
|
|
||||||
if (context == null) {
|
|
||||||
Log.w(TAG, "associated context is destroyed, finishing early");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
List<RecipientDeliveryStatus> recipients = new LinkedList<>();
|
|
||||||
|
|
||||||
if (!messageRecord.getRecipient().isGroupRecipient()) {
|
|
||||||
recipients.add(new RecipientDeliveryStatus(messageRecord.getRecipient(), getStatusFor(messageRecord.getDeliveryReceiptCount(), messageRecord.getReadReceiptCount(), messageRecord.isPending()), true, -1));
|
|
||||||
} else {
|
|
||||||
List<GroupReceiptInfo> receiptInfoList = DatabaseFactory.getGroupReceiptDatabase(context).getGroupReceiptInfo(messageRecord.getId());
|
|
||||||
|
|
||||||
if (receiptInfoList.isEmpty()) {
|
|
||||||
List<Recipient> 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<RecipientDeliveryStatus> 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -37,7 +37,7 @@ import androidx.appcompat.widget.Toolbar;
|
|||||||
|
|
||||||
import org.session.libsession.utilities.DistributionTypes;
|
import org.session.libsession.utilities.DistributionTypes;
|
||||||
import org.thoughtcrime.securesms.components.SearchToolbar;
|
import org.thoughtcrime.securesms.components.SearchToolbar;
|
||||||
import org.thoughtcrime.securesms.conversation.ConversationActivity;
|
|
||||||
import org.session.libsession.utilities.Address;
|
import org.session.libsession.utilities.Address;
|
||||||
import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2;
|
import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2;
|
||||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||||
@ -219,7 +219,6 @@ public class ShareActivity extends PassphraseRequiredActionBarActivity
|
|||||||
final Intent intent = getBaseShareIntent(ConversationActivityV2.class);
|
final Intent intent = getBaseShareIntent(ConversationActivityV2.class);
|
||||||
intent.putExtra(ConversationActivityV2.ADDRESS, address);
|
intent.putExtra(ConversationActivityV2.ADDRESS, address);
|
||||||
intent.putExtra(ConversationActivityV2.THREAD_ID, threadId);
|
intent.putExtra(ConversationActivityV2.THREAD_ID, threadId);
|
||||||
intent.putExtra(ConversationActivity.DISTRIBUTION_TYPE_EXTRA, distributionType);
|
|
||||||
|
|
||||||
isPassingAlongMedia = true;
|
isPassingAlongMedia = true;
|
||||||
startActivity(intent);
|
startActivity(intent);
|
||||||
@ -227,11 +226,6 @@ public class ShareActivity extends PassphraseRequiredActionBarActivity
|
|||||||
|
|
||||||
private Intent getBaseShareIntent(final @NonNull Class<?> target) {
|
private Intent getBaseShareIntent(final @NonNull Class<?> target) {
|
||||||
final Intent intent = new Intent(this, target);
|
final Intent intent = new Intent(this, target);
|
||||||
final String textExtra = getIntent().getStringExtra(Intent.EXTRA_TEXT);
|
|
||||||
final ArrayList<Media> 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);
|
if (resolvedExtra != null) intent.setDataAndType(resolvedExtra, mimeType);
|
||||||
|
|
||||||
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
@ -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<Recipient> 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();
|
|
||||||
}
|
|
||||||
}
|
|
@ -4,73 +4,11 @@ import android.annotation.TargetApi;
|
|||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import androidx.annotation.DimenRes;
|
|
||||||
import androidx.annotation.MainThread;
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import androidx.core.view.ViewCompat;
|
|
||||||
import android.text.format.DateUtils;
|
|
||||||
import android.util.AttributeSet;
|
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.LinearLayout;
|
||||||
import android.widget.TextView;
|
|
||||||
import android.widget.Toast;
|
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.components.emoji.EmojiKeyboardProvider;
|
public class InputPanel extends LinearLayout {
|
||||||
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;
|
|
||||||
|
|
||||||
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) {
|
public InputPanel(Context context) {
|
||||||
super(context);
|
super(context);
|
||||||
@ -85,361 +23,6 @@ public class InputPanel extends LinearLayout
|
|||||||
super(context, attrs, 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
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<QuoteModel> 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<LinkPreview> preview) {
|
|
||||||
if (preview.isPresent()) {
|
|
||||||
this.linkPreview.setVisibility(View.VISIBLE);
|
|
||||||
this.linkPreview.setLinkPreview(glideRequests, preview.get(), true);
|
|
||||||
} else {
|
|
||||||
this.linkPreview.setVisibility(View.GONE);
|
|
||||||
}
|
|
||||||
|
|
||||||
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<Void> future = slideToCancel.hide();
|
|
||||||
long elapsedTime = recordTime.hide();
|
|
||||||
|
|
||||||
future.addListener(new AssertedSuccessListener<Void>() {
|
|
||||||
@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;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void display() {
|
|
||||||
ViewUtil.fadeIn(this.slideToCancelView, FADE_TIME);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ListenableFuture<Void> hide() {
|
|
||||||
final SettableFuture<Void> 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 {
|
public interface MediaListener {
|
||||||
void onMediaSelected(@NonNull Uri uri, String contentType);
|
void onMediaSelected(@NonNull Uri uri, String contentType);
|
||||||
}
|
}
|
||||||
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
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 <V extends View & BindableConversationItem>
|
|
||||||
extends FastCursorRecyclerViewAdapter<ConversationAdapter.ViewHolder, MessageRecord>
|
|
||||||
implements StickyHeaderDecoration.StickyHeaderAdapter<HeaderViewHolder>
|
|
||||||
{
|
|
||||||
|
|
||||||
private static final int MAX_CACHE_SIZE = 1000;
|
|
||||||
private static final String TAG = ConversationAdapter.class.getSimpleName();
|
|
||||||
private final Map<String,SoftReference<MessageRecord>> messageRecordCache =
|
|
||||||
Collections.synchronizedMap(new LRUCache<String, SoftReference<MessageRecord>>(MAX_CACHE_SIZE));
|
|
||||||
private final SparseArray<String> 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<MessageRecord> batchSelected = Collections.synchronizedSet(new HashSet<MessageRecord>());
|
|
||||||
|
|
||||||
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 <V extends View & BindableConversationItem> ViewHolder(final @NonNull V itemView) {
|
|
||||||
super(itemView);
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
public <V extends View & BindableConversationItem> 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<MessageRecord> 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<MessageRecord> 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<DatabaseAttachment> attachments = DatabaseFactory.getAttachmentDatabase(getContext()).getAttachment(cursor);
|
|
||||||
List<DatabaseAttachment> 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<MessageRecord> 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<count;i++) {
|
|
||||||
MessageRecord messageRecord = getRecordForPositionOrThrow(i);
|
|
||||||
|
|
||||||
if (messageRecord.isOutgoing() || messageRecord.getDateReceived() <= lastSeen) {
|
|
||||||
return i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void toggleSelection(MessageRecord messageRecord) {
|
|
||||||
if (!batchSelected.remove(messageRecord)) {
|
|
||||||
batchSelected.add(messageRecord);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void clearSelection() {
|
|
||||||
batchSelected.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Set<MessageRecord> 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -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<Long>() {
|
|
||||||
@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();
|
|
||||||
}
|
|
||||||
}
|
|
@ -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<SearchResult> 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<SearchResult> 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<MessageResult> messages = (CursorList<MessageResult>) 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<MessageResult> messages = (CursorList<MessageResult>) 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<MessageResult> results;
|
|
||||||
private final int position;
|
|
||||||
|
|
||||||
SearchResult(CursorList<MessageResult> results, int position) {
|
|
||||||
this.results = results;
|
|
||||||
this.position = position;
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<MessageResult> getResults() {
|
|
||||||
return results;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getPosition() {
|
|
||||||
return position;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void close() {
|
|
||||||
results.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -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<MessageRecord> 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<MessageRecord> previousMessageRecord,
|
|
||||||
@NonNull Optional<MessageRecord> nextMessageRecord,
|
|
||||||
@NonNull GlideRequests glideRequests,
|
|
||||||
@NonNull Locale locale,
|
|
||||||
@NonNull Set<MessageRecord> 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) {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -75,7 +75,7 @@ import org.thoughtcrime.securesms.ApplicationContext
|
|||||||
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity
|
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity
|
||||||
import org.thoughtcrime.securesms.audio.AudioRecorder
|
import org.thoughtcrime.securesms.audio.AudioRecorder
|
||||||
import org.thoughtcrime.securesms.contactshare.SimpleTextWatcher
|
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.dialogs.*
|
||||||
import org.thoughtcrime.securesms.conversation.v2.input_bar.InputBarButton
|
import org.thoughtcrime.securesms.conversation.v2.input_bar.InputBarButton
|
||||||
import org.thoughtcrime.securesms.conversation.v2.input_bar.InputBarDelegate
|
import org.thoughtcrime.securesms.conversation.v2.input_bar.InputBarDelegate
|
||||||
|
@ -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.MessageSender
|
||||||
import org.session.libsession.messaging.sending_receiving.groupSizeLimit
|
import org.session.libsession.messaging.sending_receiving.groupSizeLimit
|
||||||
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity
|
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity
|
||||||
import org.thoughtcrime.securesms.conversation.ConversationActivity
|
|
||||||
import org.session.libsession.utilities.Address
|
import org.session.libsession.utilities.Address
|
||||||
import org.session.libsession.utilities.DistributionTypes
|
import org.session.libsession.utilities.DistributionTypes
|
||||||
import org.thoughtcrime.securesms.database.DatabaseFactory
|
import org.thoughtcrime.securesms.database.DatabaseFactory
|
||||||
@ -138,7 +138,6 @@ class CreateClosedGroupActivity : PassphraseRequiredActionBarActivity(), LoaderM
|
|||||||
private fun openConversationActivity(context: Context, threadId: Long, recipient: Recipient) {
|
private fun openConversationActivity(context: Context, threadId: Long, recipient: Recipient) {
|
||||||
val intent = Intent(context, ConversationActivityV2::class.java)
|
val intent = Intent(context, ConversationActivityV2::class.java)
|
||||||
intent.putExtra(ConversationActivityV2.THREAD_ID, threadId)
|
intent.putExtra(ConversationActivityV2.THREAD_ID, threadId)
|
||||||
intent.putExtra(ConversationActivity.DISTRIBUTION_TYPE_EXTRA, DistributionTypes.DEFAULT)
|
|
||||||
intent.putExtra(ConversationActivityV2.ADDRESS, recipient.address)
|
intent.putExtra(ConversationActivityV2.ADDRESS, recipient.address)
|
||||||
context.startActivity(intent)
|
context.startActivity(intent)
|
||||||
}
|
}
|
||||||
|
@ -29,7 +29,7 @@ import org.session.libsession.utilities.TextSecurePreferences
|
|||||||
import org.session.libsession.utilities.recipients.Recipient
|
import org.session.libsession.utilities.recipients.Recipient
|
||||||
import org.session.libsignal.utilities.PublicKeyValidation
|
import org.session.libsignal.utilities.PublicKeyValidation
|
||||||
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity
|
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity
|
||||||
import org.thoughtcrime.securesms.conversation.ConversationActivity
|
|
||||||
import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2
|
import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2
|
||||||
import org.thoughtcrime.securesms.database.DatabaseFactory
|
import org.thoughtcrime.securesms.database.DatabaseFactory
|
||||||
import org.thoughtcrime.securesms.loki.fragments.ScanQRCodeWrapperFragment
|
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 recipient = Recipient.from(this, Address.fromSerialized(hexEncodedPublicKey), false)
|
||||||
val intent = Intent(this, ConversationActivityV2::class.java)
|
val intent = Intent(this, ConversationActivityV2::class.java)
|
||||||
intent.putExtra(ConversationActivityV2.ADDRESS, recipient.address)
|
intent.putExtra(ConversationActivityV2.ADDRESS, recipient.address)
|
||||||
intent.putExtra(ConversationActivity.TEXT_EXTRA, getIntent().getStringExtra(ConversationActivity.TEXT_EXTRA))
|
|
||||||
intent.setDataAndType(getIntent().data, getIntent().type)
|
intent.setDataAndType(getIntent().data, getIntent().type)
|
||||||
val existingThread = DatabaseFactory.getThreadDatabase(this).getThreadIdIfExistsFor(recipient)
|
val existingThread = DatabaseFactory.getThreadDatabase(this).getThreadIdIfExistsFor(recipient)
|
||||||
intent.putExtra(ConversationActivityV2.THREAD_ID, existingThread)
|
intent.putExtra(ConversationActivityV2.THREAD_ID, existingThread)
|
||||||
intent.putExtra(ConversationActivity.DISTRIBUTION_TYPE_EXTRA, DistributionTypes.DEFAULT)
|
|
||||||
startActivity(intent)
|
startActivity(intent)
|
||||||
finish()
|
finish()
|
||||||
}
|
}
|
||||||
|
@ -37,7 +37,7 @@ import org.session.libsignal.utilities.toHexString
|
|||||||
import org.session.libsignal.utilities.ThreadUtils
|
import org.session.libsignal.utilities.ThreadUtils
|
||||||
import org.thoughtcrime.securesms.ApplicationContext
|
import org.thoughtcrime.securesms.ApplicationContext
|
||||||
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity
|
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity
|
||||||
import org.thoughtcrime.securesms.conversation.ConversationActivity
|
|
||||||
import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2
|
import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2
|
||||||
import org.thoughtcrime.securesms.database.DatabaseFactory
|
import org.thoughtcrime.securesms.database.DatabaseFactory
|
||||||
import org.thoughtcrime.securesms.database.model.ThreadRecord
|
import org.thoughtcrime.securesms.database.model.ThreadRecord
|
||||||
|
@ -32,7 +32,7 @@ import org.session.libsignal.utilities.Log
|
|||||||
import org.session.libsignal.utilities.PublicKeyValidation
|
import org.session.libsignal.utilities.PublicKeyValidation
|
||||||
import org.thoughtcrime.securesms.BaseActionBarActivity
|
import org.thoughtcrime.securesms.BaseActionBarActivity
|
||||||
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity
|
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity
|
||||||
import org.thoughtcrime.securesms.conversation.ConversationActivity
|
|
||||||
import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2
|
import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2
|
||||||
import org.thoughtcrime.securesms.groups.GroupManager
|
import org.thoughtcrime.securesms.groups.GroupManager
|
||||||
import org.thoughtcrime.securesms.loki.api.OpenGroupManager
|
import org.thoughtcrime.securesms.loki.api.OpenGroupManager
|
||||||
@ -130,7 +130,6 @@ class JoinPublicChatActivity : PassphraseRequiredActionBarActivity(), ScanQRCode
|
|||||||
private fun openConversationActivity(context: Context, threadId: Long, recipient: Recipient) {
|
private fun openConversationActivity(context: Context, threadId: Long, recipient: Recipient) {
|
||||||
val intent = Intent(context, ConversationActivityV2::class.java)
|
val intent = Intent(context, ConversationActivityV2::class.java)
|
||||||
intent.putExtra(ConversationActivityV2.THREAD_ID, threadId)
|
intent.putExtra(ConversationActivityV2.THREAD_ID, threadId)
|
||||||
intent.putExtra(ConversationActivity.DISTRIBUTION_TYPE_EXTRA, DistributionTypes.DEFAULT)
|
|
||||||
intent.putExtra(ConversationActivityV2.ADDRESS, recipient.address)
|
intent.putExtra(ConversationActivityV2.ADDRESS, recipient.address)
|
||||||
context.startActivity(intent)
|
context.startActivity(intent)
|
||||||
}
|
}
|
||||||
|
@ -14,7 +14,6 @@ import androidx.fragment.app.FragmentPagerAdapter
|
|||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import com.google.android.material.snackbar.Snackbar
|
import com.google.android.material.snackbar.Snackbar
|
||||||
import kotlinx.android.synthetic.main.activity_link_device.*
|
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.android.synthetic.main.fragment_recovery_phrase.*
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
|
@ -14,7 +14,7 @@ import kotlinx.android.synthetic.main.activity_qr_code.*
|
|||||||
import kotlinx.android.synthetic.main.fragment_view_my_qr_code.*
|
import kotlinx.android.synthetic.main.fragment_view_my_qr_code.*
|
||||||
import network.loki.messenger.R
|
import network.loki.messenger.R
|
||||||
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity
|
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity
|
||||||
import org.thoughtcrime.securesms.conversation.ConversationActivity
|
|
||||||
import org.session.libsession.utilities.Address
|
import org.session.libsession.utilities.Address
|
||||||
import org.session.libsession.utilities.DistributionTypes
|
import org.session.libsession.utilities.DistributionTypes
|
||||||
import org.thoughtcrime.securesms.database.DatabaseFactory
|
import org.thoughtcrime.securesms.database.DatabaseFactory
|
||||||
@ -56,11 +56,9 @@ class QRCodeActivity : PassphraseRequiredActionBarActivity(), ScanQRCodeWrapperF
|
|||||||
val recipient = Recipient.from(this, Address.fromSerialized(hexEncodedPublicKey), false)
|
val recipient = Recipient.from(this, Address.fromSerialized(hexEncodedPublicKey), false)
|
||||||
val intent = Intent(this, ConversationActivityV2::class.java)
|
val intent = Intent(this, ConversationActivityV2::class.java)
|
||||||
intent.putExtra(ConversationActivityV2.ADDRESS, recipient.address)
|
intent.putExtra(ConversationActivityV2.ADDRESS, recipient.address)
|
||||||
intent.putExtra(ConversationActivity.TEXT_EXTRA, getIntent().getStringExtra(ConversationActivity.TEXT_EXTRA))
|
|
||||||
intent.setDataAndType(getIntent().data, getIntent().type)
|
intent.setDataAndType(getIntent().data, getIntent().type)
|
||||||
val existingThread = DatabaseFactory.getThreadDatabase(this).getThreadIdIfExistsFor(recipient)
|
val existingThread = DatabaseFactory.getThreadDatabase(this).getThreadIdIfExistsFor(recipient)
|
||||||
intent.putExtra(ConversationActivityV2.THREAD_ID, existingThread)
|
intent.putExtra(ConversationActivityV2.THREAD_ID, existingThread)
|
||||||
intent.putExtra(ConversationActivity.DISTRIBUTION_TYPE_EXTRA, DistributionTypes.DEFAULT)
|
|
||||||
startActivity(intent)
|
startActivity(intent)
|
||||||
finish()
|
finish()
|
||||||
}
|
}
|
||||||
|
@ -48,7 +48,7 @@ import org.session.libsignal.utilities.Util;
|
|||||||
import org.session.libsignal.utilities.Log;
|
import org.session.libsignal.utilities.Log;
|
||||||
import org.thoughtcrime.securesms.ApplicationContext;
|
import org.thoughtcrime.securesms.ApplicationContext;
|
||||||
import org.thoughtcrime.securesms.contactshare.ContactUtil;
|
import org.thoughtcrime.securesms.contactshare.ContactUtil;
|
||||||
import org.thoughtcrime.securesms.conversation.ConversationActivity;
|
|
||||||
import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2;
|
import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2;
|
||||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||||
import org.thoughtcrime.securesms.database.MessagingDatabase.MarkedMessageInfo;
|
import org.thoughtcrime.securesms.database.MessagingDatabase.MarkedMessageInfo;
|
||||||
|
@ -8,7 +8,7 @@ import androidx.annotation.NonNull;
|
|||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.core.app.TaskStackBuilder;
|
import androidx.core.app.TaskStackBuilder;
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.conversation.ConversationActivity;
|
|
||||||
import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2;
|
import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2;
|
||||||
import org.thoughtcrime.securesms.mms.SlideDeck;
|
import org.thoughtcrime.securesms.mms.SlideDeck;
|
||||||
import org.session.libsession.utilities.recipients.Recipient;
|
import org.session.libsession.utilities.recipients.Recipient;
|
||||||
|
@ -7,11 +7,10 @@ import android.net.Uri;
|
|||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
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.libsignal.utilities.Log;
|
||||||
import org.session.libsession.utilities.recipients.Recipient;
|
import org.session.libsession.utilities.recipients.Recipient;
|
||||||
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.LinkedHashSet;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
@ -167,9 +166,9 @@ public class NotificationState {
|
|||||||
public PendingIntent getQuickReplyIntent(Context context, Recipient recipient) {
|
public PendingIntent getQuickReplyIntent(Context context, Recipient recipient) {
|
||||||
if (threads.size() != 1) throw new AssertionError("We only support replies to single thread notifications! " + threads.size());
|
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 intent = new Intent(context, ConversationActivityV2.class);
|
||||||
intent.putExtra(ConversationActivity.ADDRESS_EXTRA, recipient.getAddress());
|
intent.putExtra(ConversationActivityV2.ADDRESS, recipient.getAddress());
|
||||||
intent.putExtra(ConversationActivity.THREAD_ID_EXTRA, (long)threads.toArray()[0]);
|
intent.putExtra(ConversationActivityV2.THREAD_ID, (long)threads.toArray()[0]);
|
||||||
intent.setData((Uri.parse("custom://"+System.currentTimeMillis())));
|
intent.setData((Uri.parse("custom://"+System.currentTimeMillis())));
|
||||||
|
|
||||||
return PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
|
return PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
|
||||||
|
@ -11,7 +11,7 @@ import androidx.core.app.TaskStackBuilder;
|
|||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.conversation.ConversationActivity;
|
|
||||||
import network.loki.messenger.R;
|
import network.loki.messenger.R;
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2;
|
import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2;
|
||||||
@ -36,11 +36,6 @@ public class CommunicationActions {
|
|||||||
Intent intent = new Intent(context, ConversationActivityV2.class);
|
Intent intent = new Intent(context, ConversationActivityV2.class);
|
||||||
intent.putExtra(ConversationActivityV2.ADDRESS, recipient.getAddress());
|
intent.putExtra(ConversationActivityV2.ADDRESS, recipient.getAddress());
|
||||||
intent.putExtra(ConversationActivityV2.THREAD_ID, threadId);
|
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) {
|
if (backStack != null) {
|
||||||
backStack.addNextIntent(intent);
|
backStack.addNextIntent(intent);
|
||||||
|
@ -19,7 +19,6 @@ package org.thoughtcrime.securesms.util;
|
|||||||
import android.annotation.TargetApi;
|
import android.annotation.TargetApi;
|
||||||
import android.app.ActivityManager;
|
import android.app.ActivityManager;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.pm.PackageManager;
|
|
||||||
import android.os.Build.VERSION;
|
import android.os.Build.VERSION;
|
||||||
import android.os.Build.VERSION_CODES;
|
import android.os.Build.VERSION_CODES;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
|
Before Width: | Height: | Size: 129 B |
Before Width: | Height: | Size: 284 B |
Before Width: | Height: | Size: 311 B |
Before Width: | Height: | Size: 309 B |
Before Width: | Height: | Size: 561 B |
Before Width: | Height: | Size: 483 B |
Before Width: | Height: | Size: 297 B |
Before Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 571 B |
Before Width: | Height: | Size: 428 B |
Before Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 457 B |
Before Width: | Height: | Size: 92 B |
Before Width: | Height: | Size: 227 B |
Before Width: | Height: | Size: 249 B |
Before Width: | Height: | Size: 230 B |
Before Width: | Height: | Size: 351 B |
Before Width: | Height: | Size: 344 B |
Before Width: | Height: | Size: 211 B |
Before Width: | Height: | Size: 777 B |
Before Width: | Height: | Size: 488 B |
Before Width: | Height: | Size: 288 B |
Before Width: | Height: | Size: 746 B |
Before Width: | Height: | Size: 326 B |
Before Width: | Height: | Size: 97 B |
Before Width: | Height: | Size: 308 B |
Before Width: | Height: | Size: 359 B |
Before Width: | Height: | Size: 322 B |
Before Width: | Height: | Size: 576 B |
Before Width: | Height: | Size: 575 B |
Before Width: | Height: | Size: 364 B |
Before Width: | Height: | Size: 1.7 KiB |
Before Width: | Height: | Size: 872 B |
Before Width: | Height: | Size: 507 B |
Before Width: | Height: | Size: 1.6 KiB |
Before Width: | Height: | Size: 534 B |
Before Width: | Height: | Size: 97 B |
Before Width: | Height: | Size: 386 B |
Before Width: | Height: | Size: 449 B |
Before Width: | Height: | Size: 384 B |
Before Width: | Height: | Size: 990 B |
Before Width: | Height: | Size: 834 B |
Before Width: | Height: | Size: 474 B |
Before Width: | Height: | Size: 875 B |
Before Width: | Height: | Size: 702 B |
Before Width: | Height: | Size: 2.4 KiB |
Before Width: | Height: | Size: 781 B |
Before Width: | Height: | Size: 946 B |
Before Width: | Height: | Size: 1.0 KiB |
Before Width: | Height: | Size: 913 B |
Before Width: | Height: | Size: 3.4 KiB |
@ -1,8 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
<gradient
|
|
||||||
android:angle="90"
|
|
||||||
android:dither="true"
|
|
||||||
android:endColor="@android:color/transparent"
|
|
||||||
android:startColor="@color/conversation_compose_divider" />
|
|
||||||
</shape>
|
|
@ -1,10 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
<item android:state_pressed="true">
|
|
||||||
<shape android:shape="oval">
|
|
||||||
<solid android:color="@color/touch_highlight" />
|
|
||||||
</shape>
|
|
||||||
</item>
|
|
||||||
|
|
||||||
<item android:drawable="@android:color/transparent" />
|
|
||||||
</selector>
|
|
@ -1,15 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<ripple
|
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:color="?attr/colorControlHighlight">
|
|
||||||
<item
|
|
||||||
android:id="@android:id/mask"
|
|
||||||
android:right="24dp">
|
|
||||||
<shape>
|
|
||||||
<corners
|
|
||||||
android:bottomLeftRadius="46dp"
|
|
||||||
android:topLeftRadius="46dp" />
|
|
||||||
<solid android:color="@android:color/white" />
|
|
||||||
</shape>
|
|
||||||
</item>
|
|
||||||
</ripple>
|
|
@ -1,15 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:color="?attr/colorControlHighlight">
|
|
||||||
<!-- Add half of the medium_profile_picture_size padding on the right to better work with the group icons. -->
|
|
||||||
<item
|
|
||||||
android:id="@android:id/mask"
|
|
||||||
android:right="24dp">
|
|
||||||
<shape>
|
|
||||||
<corners
|
|
||||||
android:bottomLeftRadius="@dimen/medium_profile_picture_size"
|
|
||||||
android:topLeftRadius="@dimen/medium_profile_picture_size" />
|
|
||||||
<solid android:color="@android:color/white" />
|
|
||||||
</shape>
|
|
||||||
</item>
|
|
||||||
</ripple>
|
|
@ -1,5 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
<item android:drawable="@color/accent_alpha50" android:state_selected="true" />
|
|
||||||
<item android:drawable="@color/signal_primary_alpha_focus" android:state_focused="true" />
|
|
||||||
</selector>
|
|
@ -1,5 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<selector xmlns:android="http://schemas.android.com/apk/res/android" android:exitFadeDuration="1000">
|
|
||||||
<item android:drawable="@color/accent_alpha50" android:state_selected="true" />
|
|
||||||
<item android:drawable="@color/signal_primary_alpha_focus" android:state_focused="true" />
|
|
||||||
</selector>
|
|
@ -1,16 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
<item android:state_pressed="true">
|
|
||||||
<shape>
|
|
||||||
<corners android:radius="2dp" />
|
|
||||||
<solid android:color="#FFD32F2F" />
|
|
||||||
</shape>
|
|
||||||
</item>
|
|
||||||
|
|
||||||
<item>
|
|
||||||
<shape>
|
|
||||||
<corners android:radius="2dp" />
|
|
||||||
<solid android:color="#FFF44336" />
|
|
||||||
</shape>
|
|
||||||
</item>
|
|
||||||
</selector>
|
|
@ -1,10 +0,0 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="24dp"
|
|
||||||
android:height="24dp"
|
|
||||||
android:viewportWidth="24"
|
|
||||||
android:viewportHeight="24"
|
|
||||||
android:tint="?attr/colorControlNormal">
|
|
||||||
<path
|
|
||||||
android:fillColor="@android:color/white"
|
|
||||||
android:pathData="M11.67,3.87L9.9,2.1 0,12l9.9,9.9 1.77,-1.77L3.54,12z"/>
|
|
||||||
</vector>
|
|
@ -1,10 +0,0 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="24dp"
|
|
||||||
android:height="24dp"
|
|
||||||
android:viewportWidth="24"
|
|
||||||
android:viewportHeight="24"
|
|
||||||
android:tint="?attr/colorControlNormal">
|
|
||||||
<path
|
|
||||||
android:fillColor="@android:color/white"
|
|
||||||
android:pathData="M12.65,10C11.83,7.67 9.61,6 7,6c-3.31,0 -6,2.69 -6,6s2.69,6 6,6c2.61,0 4.83,-1.67 5.65,-4H17v4h4v-4h2v-4H12.65zM7,14c-1.1,0 -2,-0.9 -2,-2s0.9,-2 2,-2 2,0.9 2,2 -0.9,2 -2,2z"/>
|
|
||||||
</vector>
|
|
@ -1,10 +0,0 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="24dp"
|
|
||||||
android:height="24dp"
|
|
||||||
android:viewportWidth="24"
|
|
||||||
android:viewportHeight="24"
|
|
||||||
android:tint="?attr/colorControlNormal">
|
|
||||||
<path
|
|
||||||
android:fillColor="@android:color/white"
|
|
||||||
android:pathData="M17.65,6.35C16.2,4.9 14.21,4 12,4c-4.42,0 -7.99,3.58 -7.99,8s3.57,8 7.99,8c3.73,0 6.84,-2.55 7.73,-6h-2.08c-0.82,2.33 -3.04,4 -5.65,4 -3.31,0 -6,-2.69 -6,-6s2.69,-6 6,-6c1.66,0 3.14,0.69 4.22,1.78L13,11h7V4l-2.35,2.35z"/>
|
|
||||||
</vector>
|
|
@ -1,21 +0,0 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="24dp"
|
|
||||||
android:height="24dp"
|
|
||||||
android:viewportWidth="24"
|
|
||||||
android:viewportHeight="24"
|
|
||||||
android:tint="?android:colorControlNormal">
|
|
||||||
<path
|
|
||||||
android:pathData="M12,12m-9.4734,0a9.4734,9.4734 0,1 1,18.9468 0a9.4734,9.4734 0,1 1,-18.9468 0"
|
|
||||||
android:strokeWidth="1.5"
|
|
||||||
android:strokeColor="@android:color/white"/>
|
|
||||||
<path
|
|
||||||
android:pathData="m11.9996,8.4641l0,7.0708"
|
|
||||||
android:strokeWidth="1.2991"
|
|
||||||
android:strokeColor="@android:color/white"
|
|
||||||
android:strokeLineCap="round"/>
|
|
||||||
<path
|
|
||||||
android:pathData="m15.535,11.9997l-7.0708,0"
|
|
||||||
android:strokeWidth="1.2991"
|
|
||||||
android:strokeColor="@android:color/white"
|
|
||||||
android:strokeLineCap="round"/>
|
|
||||||
</vector>
|
|
@ -1,9 +0,0 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="512dp"
|
|
||||||
android:height="512dp"
|
|
||||||
android:viewportWidth="512"
|
|
||||||
android:viewportHeight="512">
|
|
||||||
<path
|
|
||||||
android:pathData="M466.5,83.7l-192,-80a48.15,48.15 0,0 0,-36.9 0l-192,80C27.7,91.1 16,108.6 16,128c0,198.5 114.5,335.7 221.5,380.3 11.8,4.9 25.1,4.9 36.9,0C360.1,472.6 496,349.3 496,128c0,-19.4 -11.7,-36.9 -29.5,-44.3zM256.1,446.3l-0.1,-381 175.9,73.3c-3.3,151.4 -82.1,261.1 -175.8,307.7z"
|
|
||||||
android:fillColor="#000000"/>
|
|
||||||
</vector>
|
|
@ -1,16 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
<item android:state_pressed="true">
|
|
||||||
<shape>
|
|
||||||
<corners android:radius="2dp" />
|
|
||||||
<solid android:color="#ff145c95" />
|
|
||||||
</shape>
|
|
||||||
</item>
|
|
||||||
|
|
||||||
<item>
|
|
||||||
<shape>
|
|
||||||
<corners android:radius="2dp" />
|
|
||||||
<solid android:color="#ff2090ea" />
|
|
||||||
</shape>
|
|
||||||
</item>
|
|
||||||
</selector>
|
|
@ -1,12 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<layer-list
|
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
<item
|
|
||||||
android:top="2px"
|
|
||||||
android:bottom="2px">
|
|
||||||
<shape android:shape="rectangle">
|
|
||||||
<corners android:radius="@dimen/message_corner_radius"/>
|
|
||||||
<solid android:color="@color/white" />
|
|
||||||
</shape>
|
|
||||||
</item>
|
|
||||||
</layer-list>
|
|
@ -1,9 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<shape
|
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:shape="rectangle">
|
|
||||||
|
|
||||||
<solid android:color="@color/transparent" />
|
|
||||||
|
|
||||||
<corners android:radius="38dp" />
|
|
||||||
</shape>
|
|
@ -1,9 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<shape
|
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:shape="rectangle">
|
|
||||||
|
|
||||||
<solid android:color="@color/transparent" />
|
|
||||||
|
|
||||||
<corners android:radius="23dp" />
|
|
||||||
</shape>
|
|
@ -1,9 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<shape
|
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:shape="rectangle">
|
|
||||||
|
|
||||||
<solid android:color="@color/profile_picture_background" />
|
|
||||||
|
|
||||||
<corners android:radius="23dp" />
|
|
||||||
</shape>
|
|
@ -1,11 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<shape
|
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:shape="rectangle">
|
|
||||||
|
|
||||||
<solid android:color="@color/transparent" />
|
|
||||||
|
|
||||||
<corners android:radius="18dp" />
|
|
||||||
|
|
||||||
<stroke android:width="@dimen/profile_picture_border_thickness" android:color="@color/profile_picture_border" />
|
|
||||||
</shape>
|
|
@ -1,11 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<shape
|
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:shape="rectangle">
|
|
||||||
|
|
||||||
<solid android:color="@color/accent" />
|
|
||||||
|
|
||||||
<corners android:radius="@dimen/dialog_button_corner_radius" />
|
|
||||||
|
|
||||||
<stroke android:width="@dimen/border_thickness" android:color="@color/accent" />
|
|
||||||
</shape>
|
|
@ -1,8 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<shape
|
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:shape="oval">
|
|
||||||
|
|
||||||
<solid android:color="@color/compose_view_background" />
|
|
||||||
|
|
||||||
</shape>
|
|
@ -1,9 +0,0 @@
|
|||||||
<vector android:height="24dp" android:viewportHeight="448.40668"
|
|
||||||
android:viewportWidth="404.08533" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
<group>
|
|
||||||
<clip-path android:pathData="M0,0L404.085,0L404.085,448.407L0,448.407Z M 0,0"/>
|
|
||||||
<path android:fillAlpha="1" android:fillColor="#ffffff"
|
|
||||||
android:fillType="nonZero"
|
|
||||||
android:pathData="m288.607,420.376l-196.335,-0c-33.576,-0 -62.508,-25.748 -64.164,-59.281 -1.771,-35.847 26.883,-65.576 62.353,-65.576l113.072,-0c6.919,-0 12.527,-5.608 12.527,-12.525l0,-92.305L327.307,252.333C356.723,268.633 375.241,299.335 376.027,332.848 377.161,380.975 336.746,420.376 288.607,420.376m-211.829,-224.303c-29.416,-16.3 -47.933,-47.001 -48.721,-80.515 -1.132,-48.127 39.283,-87.528 87.42,-87.528L311.811,28.031c33.576,-0 62.508,25.748 64.165,59.283 1.771,35.845 -26.883,65.575 -62.352,65.575 0,-0 -81.316,0.013 -113.077,0.019 -6.915,0.001 -12.499,5.608 -12.501,12.523l-0.021,92.289zM340.891,227.816 L256.254,180.919l57.371,-0c49.877,-0 90.46,-40.579 90.46,-90.457 0,-49.877 -40.583,-90.461 -90.46,-90.461l-200.299,-0c-62.485,-0 -113.327,50.841 -113.327,113.327 0,44.567 24.216,85.664 63.195,107.265l84.636,46.896l-57.368,-0c-49.88,-0 -90.463,40.58 -90.463,90.457 0,49.877 40.583,90.461 90.463,90.461L290.758,448.407c62.488,-0 113.327,-50.84 113.327,-113.327 0,-44.567 -24.216,-85.664 -63.193,-107.264" android:strokeColor="#00000000"/>
|
|
||||||
</group>
|
|
||||||
</vector>
|
|
@ -1,125 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
|
|
||||||
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
|
||||||
xmlns:tools="http://schemas.android.com/tools">
|
|
||||||
<data>
|
|
||||||
<import type="org.thoughtcrime.securesms.loki.activities.BackupRestoreViewModel"/>
|
|
||||||
<import type="org.thoughtcrime.securesms.util.BackupUtil"/>
|
|
||||||
<import type="android.view.View"/>
|
|
||||||
<variable
|
|
||||||
name="viewModel"
|
|
||||||
type="org.thoughtcrime.securesms.loki.activities.BackupRestoreViewModel" />
|
|
||||||
</data>
|
|
||||||
|
|
||||||
<FrameLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent">
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:orientation="vertical">
|
|
||||||
|
|
||||||
<View
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="0dp"
|
|
||||||
android:layout_weight="1" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginLeft="@dimen/very_large_spacing"
|
|
||||||
android:layout_marginRight="@dimen/very_large_spacing"
|
|
||||||
android:text="@string/activity_backup_restore_title"
|
|
||||||
android:textColor="@color/text"
|
|
||||||
android:textSize="@dimen/large_font_size"
|
|
||||||
android:textStyle="bold" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginLeft="@dimen/very_large_spacing"
|
|
||||||
android:layout_marginTop="4dp"
|
|
||||||
android:layout_marginRight="@dimen/very_large_spacing"
|
|
||||||
android:text="@string/activity_backup_restore_explanation_1"
|
|
||||||
android:textColor="@color/text"
|
|
||||||
android:textSize="@dimen/small_font_size" />
|
|
||||||
|
|
||||||
<Button
|
|
||||||
android:id="@+id/buttonSelectFile"
|
|
||||||
style="@style/Button.Primary"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginLeft="@dimen/very_large_spacing"
|
|
||||||
android:layout_marginTop="4dp"
|
|
||||||
android:layout_marginRight="@dimen/very_large_spacing"
|
|
||||||
android:textColor="@color/black"
|
|
||||||
android:text="@{viewModel.backupFile != null ? BackupRestoreViewModel.uriToFileName(buttonSelectFile, viewModel.backupFile) : @string/activity_backup_restore_select_file}"
|
|
||||||
tools:text="Select a file"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<EditText
|
|
||||||
android:id="@+id/backupCode"
|
|
||||||
style="@style/SmallSessionEditText"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginLeft="@dimen/very_large_spacing"
|
|
||||||
android:layout_marginTop="10dp"
|
|
||||||
android:layout_marginRight="@dimen/very_large_spacing"
|
|
||||||
android:hint="@string/activity_backup_restore_passphrase"
|
|
||||||
android:inputType="numberDecimal|textNoSuggestions"
|
|
||||||
android:digits="0123456789"
|
|
||||||
android:maxLength="@{BackupUtil.BACKUP_PASSPHRASE_LENGTH}"
|
|
||||||
android:text="@={viewModel.backupPassphrase}"
|
|
||||||
android:visibility="@{viewModel.backupFile != null ? View.VISIBLE : View.INVISIBLE}"/>
|
|
||||||
|
|
||||||
<View
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="0dp"
|
|
||||||
android:layout_weight="1" />
|
|
||||||
|
|
||||||
<Button
|
|
||||||
android:id="@+id/restoreButton"
|
|
||||||
style="@style/Widget.Session.Button.Common.ProminentFilled"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="@dimen/medium_button_height"
|
|
||||||
android:layout_marginLeft="@dimen/massive_spacing"
|
|
||||||
android:layout_marginRight="@dimen/massive_spacing"
|
|
||||||
android:text="@string/continue_2"
|
|
||||||
android:visibility="@{BackupRestoreViewModel.validateData(viewModel.backupFile, viewModel.backupPassphrase) ? View.VISIBLE : View.INVISIBLE}"/>
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/termsTextView"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="@dimen/onboarding_button_bottom_offset"
|
|
||||||
android:layout_marginLeft="@dimen/massive_spacing"
|
|
||||||
android:layout_marginRight="@dimen/massive_spacing"
|
|
||||||
android:gravity="center"
|
|
||||||
android:text="By using this service, you agree to our Terms of Service and Privacy Policy"
|
|
||||||
android:textColor="@color/text"
|
|
||||||
android:textColorLink="@color/text"
|
|
||||||
android:textSize="@dimen/very_small_font_size"
|
|
||||||
tools:ignore="HardcodedText" /> <!-- Intentionally not yet translated -->
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<FrameLayout
|
|
||||||
android:id="@+id/busyIndicator"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:background="#A4000000"
|
|
||||||
android:visibility="@{viewModel.processingBackupFile == true ? View.VISIBLE : View.GONE}"
|
|
||||||
tools:visibility="visible">
|
|
||||||
|
|
||||||
<ProgressBar
|
|
||||||
android:layout_width="64dp"
|
|
||||||
android:layout_height="64dp"
|
|
||||||
android:indeterminate="true"
|
|
||||||
android:layout_gravity="center"/>
|
|
||||||
|
|
||||||
</FrameLayout>
|
|
||||||
|
|
||||||
</FrameLayout>
|
|
||||||
|
|
||||||
</layout>
|
|
@ -1,235 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<FrameLayout
|
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
android:id="@+id/rootView"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent">
|
|
||||||
|
|
||||||
<androidx.appcompat.widget.Toolbar
|
|
||||||
android:id="@+id/toolbar"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="?attr/actionBarSize"
|
|
||||||
android:clipChildren="false"
|
|
||||||
app:contentInsetStart="4dp">
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:gravity="center_vertical"
|
|
||||||
android:orientation="horizontal"
|
|
||||||
android:clipChildren="false">
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:id="@+id/homeButtonContainer"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:gravity="center_vertical"
|
|
||||||
android:orientation="horizontal"
|
|
||||||
android:background="@drawable/conversation_home_touch_highlight"
|
|
||||||
android:clickable="true"
|
|
||||||
android:focusable="true">
|
|
||||||
|
|
||||||
<ImageView
|
|
||||||
android:layout_width="18dp"
|
|
||||||
android:layout_height="18dp"
|
|
||||||
android:src="@drawable/ic_baseline_arrow_back_compact_24"
|
|
||||||
android:layout_marginRight="-2dp"
|
|
||||||
android:layout_marginLeft="8dp" />
|
|
||||||
|
|
||||||
<org.thoughtcrime.securesms.loki.views.ProfilePictureView
|
|
||||||
android:id="@+id/profilePictureView"
|
|
||||||
android:layout_width="@dimen/medium_profile_picture_size"
|
|
||||||
android:layout_height="@dimen/medium_profile_picture_size" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginStart="@dimen/medium_spacing"
|
|
||||||
android:orientation="vertical">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/titleTextView"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:background="@null"
|
|
||||||
android:maxLines="1"
|
|
||||||
android:ellipsize="end"
|
|
||||||
tools:text="Conversation"
|
|
||||||
android:textColor="@color/text"
|
|
||||||
android:textSize="@dimen/large_font_size"
|
|
||||||
android:fontFamily="sans-serif-medium" />
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:orientation="horizontal">
|
|
||||||
|
|
||||||
<ImageView
|
|
||||||
android:id="@+id/muteIndicatorImageView"
|
|
||||||
android:layout_width="18dp"
|
|
||||||
android:layout_height="18dp"
|
|
||||||
android:layout_gravity="center"
|
|
||||||
android:src="@drawable/ic_outline_notifications_off_24"
|
|
||||||
android:layout_marginRight="6dp"
|
|
||||||
android:visibility="gone"
|
|
||||||
tools:visibility="visible"/>
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/subtitleTextView"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:maxLines="1"
|
|
||||||
android:ellipsize="end"
|
|
||||||
tools:text="26 members"
|
|
||||||
android:textColor="@color/text"
|
|
||||||
android:textSize="@dimen/small_font_size" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<View
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:layout_weight="1" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
</androidx.appcompat.widget.Toolbar>
|
|
||||||
|
|
||||||
<org.thoughtcrime.securesms.components.InputAwareLayout
|
|
||||||
android:id="@+id/layout_container"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent">
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:id="@+id/conversation_container"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:clipChildren="false"
|
|
||||||
android:clipToPadding="false"
|
|
||||||
android:gravity="bottom"
|
|
||||||
android:orientation="vertical"
|
|
||||||
android:paddingTop="?attr/actionBarSize">
|
|
||||||
|
|
||||||
<ProgressBar
|
|
||||||
android:id="@+id/messageStatusProgressBar"
|
|
||||||
style="?android:attr/progressBarStyleHorizontal"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="@dimen/accent_line_thickness"
|
|
||||||
android:paddingLeft="-2dp"
|
|
||||||
android:paddingRight="-2dp"
|
|
||||||
android:progressTint="@color/accent"
|
|
||||||
android:progressBackgroundTint="@color/transparent"
|
|
||||||
android:alpha="0"
|
|
||||||
android:indeterminate="false"
|
|
||||||
android:progress="0" />
|
|
||||||
|
|
||||||
<View
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="1px"
|
|
||||||
android:background="?android:dividerHorizontal"
|
|
||||||
android:elevation="1dp" />
|
|
||||||
|
|
||||||
<org.thoughtcrime.securesms.conversation.v2.components.OpenGroupGuidelinesView
|
|
||||||
android:id="@+id/open_group_guidelines_view"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:visibility="gone" />
|
|
||||||
|
|
||||||
<FrameLayout
|
|
||||||
android:id="@+id/fragment_content"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="0dp"
|
|
||||||
android:layout_weight="1" />
|
|
||||||
|
|
||||||
<ViewStub
|
|
||||||
android:id="@+id/attachment_editor_stub"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:inflatedId="@+id/attachment_editor"
|
|
||||||
android:layout="@layout/conversation_activity_attachment_editor_stub" />
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:orientation="vertical"
|
|
||||||
android:clipChildren="false"
|
|
||||||
android:clipToPadding="false">
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:id="@+id/mentionCandidateSelectionViewContainer"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:orientation="vertical"
|
|
||||||
android:visibility="gone">
|
|
||||||
|
|
||||||
<View
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="1px"
|
|
||||||
android:background="?android:dividerHorizontal" />
|
|
||||||
|
|
||||||
<include layout="@layout/view_mention_candidate_selection" />
|
|
||||||
|
|
||||||
<View
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="1px"
|
|
||||||
android:background="?android:dividerHorizontal" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<FrameLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:clipChildren="false"
|
|
||||||
android:clipToPadding="false">
|
|
||||||
|
|
||||||
<include layout="@layout/conversation_input_panel" />
|
|
||||||
|
|
||||||
<include layout="@layout/conversation_search_nav" />
|
|
||||||
|
|
||||||
</FrameLayout>
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<Button
|
|
||||||
android:id="@+id/unblock_button"
|
|
||||||
android:layout_width="fill_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:padding="20dp"
|
|
||||||
android:text="@string/ConversationActivity_unblock"
|
|
||||||
android:visibility="gone" />
|
|
||||||
|
|
||||||
<Button
|
|
||||||
android:id="@+id/make_default_sms_button"
|
|
||||||
android:layout_width="fill_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:padding="20dp"
|
|
||||||
android:text="@string/conversation_activity__enable_signal_for_sms"
|
|
||||||
android:visibility="gone" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/space_left"
|
|
||||||
android:layout_width="fill_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:background="?android:attr/windowBackground"
|
|
||||||
android:paddingStart="5dip"
|
|
||||||
tools:text="160/160 (1)"
|
|
||||||
android:visibility="gone" />
|
|
||||||
|
|
||||||
<ViewStub
|
|
||||||
android:id="@+id/emoji_drawer_stub"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:inflatedId="@+id/emoji_drawer"
|
|
||||||
android:layout="@layout/conversation_activity_emojidrawer_stub" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
</org.thoughtcrime.securesms.components.InputAwareLayout>
|
|
||||||
|
|
||||||
</FrameLayout>
|
|