mirror of
https://github.com/oxen-io/session-android.git
synced 2025-02-19 19:38:45 +00:00
commit
20f303b2a2
51
res/layout/cell_user_selection_view.xml
Normal file
51
res/layout/cell_user_selection_view.xml
Normal file
@ -0,0 +1,51 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<org.thoughtcrime.securesms.loki.UserSelectionViewCell
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="52dp"
|
||||
android:orientation="horizontal"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingEnd="16dp"
|
||||
android:gravity="center_vertical"
|
||||
android:background="?attr/conversation_list_item_background">
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="36dp"
|
||||
android:layout_height="38dp"
|
||||
android:layout_marginTop="1dp">
|
||||
|
||||
<!-- The frame layout below is a workaround for an avatar image view bug -->
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/profilePictureImageViewContainer"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content" >
|
||||
|
||||
<org.thoughtcrime.securesms.components.AvatarImageView
|
||||
android:id="@+id/profilePictureImageView"
|
||||
android:layout_width="36dp"
|
||||
android:layout_height="36dp"
|
||||
android:layout_marginBottom="2dp" />
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/moderatorIconImageView"
|
||||
android:layout_width="16dp"
|
||||
android:layout_height="16dp"
|
||||
android:layout_marginEnd="1.5dp"
|
||||
android:src="@drawable/icon_crown"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_alignParentBottom="true" />
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/displayNameTextView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
style="@style/Signal.Text.Body"
|
||||
android:ellipsize="end" />
|
||||
|
||||
</org.thoughtcrime.securesms.loki.UserSelectionViewCell>
|
@ -71,17 +71,26 @@
|
||||
android:inflatedId="@+id/attachment_editor"
|
||||
android:layout="@layout/conversation_activity_attachment_editor_stub" />
|
||||
|
||||
<FrameLayout
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:clipChildren="false"
|
||||
android:clipToPadding="false">
|
||||
android:orientation="vertical">
|
||||
|
||||
<include layout="@layout/conversation_input_panel" />
|
||||
<include layout="@layout/view_user_selection" />
|
||||
|
||||
<include layout="@layout/conversation_search_nav" />
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:clipChildren="false"
|
||||
android:clipToPadding="false">
|
||||
|
||||
</FrameLayout>
|
||||
<include layout="@layout/conversation_input_panel" />
|
||||
|
||||
<include layout="@layout/conversation_search_nav" />
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<Button
|
||||
android:id="@+id/register_button"
|
||||
|
@ -101,6 +101,7 @@
|
||||
tools:visibility="invisible"
|
||||
tools:hint="Send TextSecure message" >
|
||||
<requestFocus />
|
||||
|
||||
</org.thoughtcrime.securesms.components.ComposeText>
|
||||
|
||||
<FrameLayout
|
||||
|
8
res/layout/view_user_selection.xml
Normal file
8
res/layout/view_user_selection.xml
Normal file
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<org.thoughtcrime.securesms.loki.UserSelectionView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/userSelectionView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:paddingTop="6dp"
|
||||
android:listSelector="@color/transparent" />
|
@ -2,6 +2,7 @@
|
||||
<resources>
|
||||
<!-- Loki -->
|
||||
<color name="loki_green">#78be20</color>
|
||||
<color name="loki_dark_green">#419B41</color>
|
||||
<color name="loki_darkest_gray">#0a0a0a</color>
|
||||
<!-- Loki -->
|
||||
|
||||
|
@ -19,6 +19,7 @@ import android.view.ViewOutlineProvider;
|
||||
import org.thoughtcrime.securesms.color.MaterialColor;
|
||||
import org.thoughtcrime.securesms.contacts.avatars.ContactColors;
|
||||
import org.thoughtcrime.securesms.contacts.avatars.GeneratedContactPhoto;
|
||||
import org.thoughtcrime.securesms.database.Address;
|
||||
import org.thoughtcrime.securesms.loki.JazzIdenticonDrawable;
|
||||
import org.thoughtcrime.securesms.mms.GlideRequests;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
@ -123,6 +124,10 @@ public class AvatarImageView extends AppCompatImageView {
|
||||
setImageDrawable(image);
|
||||
}
|
||||
|
||||
public void update(String hexEncodedPublicKey) {
|
||||
this.recipient = Recipient.from(getContext(), Address.fromSerialized(hexEncodedPublicKey), false);
|
||||
}
|
||||
|
||||
public void setAvatar(@NonNull GlideRequests requestManager, @Nullable Recipient recipient, boolean quickContactEnabled) {
|
||||
this.recipient = recipient;
|
||||
/*
|
||||
|
@ -156,7 +156,10 @@ import org.thoughtcrime.securesms.linkpreview.LinkPreviewUtil;
|
||||
import org.thoughtcrime.securesms.linkpreview.LinkPreviewViewModel;
|
||||
import org.thoughtcrime.securesms.logging.Log;
|
||||
import org.thoughtcrime.securesms.loki.FriendRequestViewDelegate;
|
||||
import org.thoughtcrime.securesms.loki.LokiAPIUtilities;
|
||||
import org.thoughtcrime.securesms.loki.LokiThreadDatabaseDelegate;
|
||||
import org.thoughtcrime.securesms.loki.LokiUserDatabase;
|
||||
import org.thoughtcrime.securesms.loki.UserSelectionView;
|
||||
import org.thoughtcrime.securesms.mediasend.Media;
|
||||
import org.thoughtcrime.securesms.mediasend.MediaSendActivity;
|
||||
import org.thoughtcrime.securesms.mms.AttachmentManager;
|
||||
@ -224,6 +227,7 @@ import org.whispersystems.libsignal.util.guava.Optional;
|
||||
import org.whispersystems.signalservice.api.SignalServiceMessageSender;
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage;
|
||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
||||
import org.whispersystems.signalservice.loki.api.LokiAPI;
|
||||
import org.whispersystems.signalservice.loki.messaging.LokiMessageFriendRequestStatus;
|
||||
import org.whispersystems.signalservice.loki.messaging.LokiThreadFriendRequestStatus;
|
||||
import org.whispersystems.signalservice.loki.utilities.Analytics;
|
||||
@ -239,7 +243,9 @@ import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import kotlin.Unit;
|
||||
import network.loki.messenger.R;
|
||||
import nl.komponents.kovenant.combine.Tuple2;
|
||||
|
||||
import static org.thoughtcrime.securesms.TransportOption.Type;
|
||||
import static org.thoughtcrime.securesms.database.GroupDatabase.GroupRecord;
|
||||
@ -292,23 +298,24 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
private static final int SMS_DEFAULT = 11;
|
||||
private static final int MEDIA_SENDER = 12;
|
||||
|
||||
private GlideRequests glideRequests;
|
||||
protected ComposeText composeText;
|
||||
private AnimatingToggle buttonToggle;
|
||||
private SendButton sendButton;
|
||||
private ImageButton attachButton;
|
||||
protected ConversationTitleView titleView;
|
||||
private TextView charactersLeft;
|
||||
private ConversationFragment fragment;
|
||||
private Button unblockButton;
|
||||
private Button makeDefaultSmsButton;
|
||||
private Button registerButton;
|
||||
private InputAwareLayout container;
|
||||
private View composePanel;
|
||||
protected Stub<ReminderView> reminderView;
|
||||
private Stub<UnverifiedBannerView> unverifiedBannerView;
|
||||
private GlideRequests glideRequests;
|
||||
protected ComposeText composeText;
|
||||
private AnimatingToggle buttonToggle;
|
||||
private SendButton sendButton;
|
||||
private ImageButton attachButton;
|
||||
protected ConversationTitleView titleView;
|
||||
private TextView charactersLeft;
|
||||
private ConversationFragment fragment;
|
||||
private Button unblockButton;
|
||||
private Button makeDefaultSmsButton;
|
||||
private Button registerButton;
|
||||
private InputAwareLayout container;
|
||||
private View composePanel;
|
||||
protected Stub<ReminderView> reminderView;
|
||||
private Stub<UnverifiedBannerView> unverifiedBannerView;
|
||||
private Stub<GroupShareProfileView> groupShareProfileView;
|
||||
private TypingStatusTextWatcher typingTextWatcher;
|
||||
private MentionTextWatcher mentionTextWatcher;
|
||||
private ConversationSearchBottomBar searchNav;
|
||||
private MenuItem searchViewItem;
|
||||
|
||||
@ -338,6 +345,8 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
private final DynamicNoActionBarTheme dynamicTheme = new DynamicNoActionBarTheme();
|
||||
private final DynamicLanguage dynamicLanguage = new DynamicLanguage();
|
||||
|
||||
private UserSelectionView userSelectionView;
|
||||
private int mentionStartIndex = -1;
|
||||
|
||||
@Override
|
||||
protected void onPreCreate() {
|
||||
@ -389,11 +398,21 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
composeText.addTextChangedListener(typingTextWatcher);
|
||||
}
|
||||
composeText.setSelection(composeText.length(), composeText.length());
|
||||
composeText.addTextChangedListener(mentionTextWatcher);
|
||||
userSelectionView.setOnUserSelected(tuple -> {
|
||||
String oldText = composeText.getText().toString();
|
||||
String newText = oldText.substring(0, mentionStartIndex) + tuple.getFirst();
|
||||
composeText.setText(newText);
|
||||
composeText.setSelection(newText.length());
|
||||
return Unit.INSTANCE;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
LokiAPIUtilities.INSTANCE.populateUserIDCacheIfNeeded(threadId, this);
|
||||
|
||||
if (this.recipient.isGroupRecipient()) {
|
||||
if (this.recipient.getName().equals("Loki Public Chat")) {
|
||||
Analytics.Companion.getShared().track("Loki Public Chat Opened");
|
||||
@ -1552,6 +1571,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
inlineAttachmentToggle = ViewUtil.findById(this, R.id.inline_attachment_container);
|
||||
inputPanel = ViewUtil.findById(this, R.id.bottom_panel);
|
||||
searchNav = ViewUtil.findById(this, R.id.conversation_search_nav);
|
||||
userSelectionView = ViewUtil.findById(this, R.id.userSelectionView);
|
||||
|
||||
ImageButton quickCameraToggle = ViewUtil.findById(this, R.id.quick_camera_toggle);
|
||||
ImageButton inlineAttachmentButton = ViewUtil.findById(this, R.id.inline_attachment_button);
|
||||
@ -1564,6 +1584,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
attachmentManager = new AttachmentManager(this, this);
|
||||
audioRecorder = new AudioRecorder(this);
|
||||
typingTextWatcher = new TypingStatusTextWatcher();
|
||||
mentionTextWatcher = new MentionTextWatcher();
|
||||
|
||||
SendButtonListener sendButtonListener = new SendButtonListener();
|
||||
ComposeKeyPressedListener composeKeyPressedListener = new ComposeKeyPressedListener();
|
||||
@ -2591,7 +2612,9 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
|
||||
private void silentlySetComposeText(String text) {
|
||||
typingTextWatcher.setEnabled(false);
|
||||
mentionTextWatcher.setEnabled(false);
|
||||
composeText.setText(text);
|
||||
mentionTextWatcher.setEnabled(true);
|
||||
typingTextWatcher.setEnabled(true);
|
||||
}
|
||||
|
||||
@ -2706,7 +2729,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
}
|
||||
|
||||
private class TypingStatusTextWatcher extends SimpleTextWatcher {
|
||||
|
||||
private boolean enabled = true;
|
||||
|
||||
@Override
|
||||
@ -2721,6 +2743,36 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
}
|
||||
}
|
||||
|
||||
private class MentionTextWatcher extends SimpleTextWatcher {
|
||||
private boolean enabled = true;
|
||||
|
||||
@Override
|
||||
public void onTextChanged(String text) {
|
||||
if (!enabled | text.length() == 0) { return; }
|
||||
int currentEndIndex = text.length() - 1;
|
||||
char lastCharacter = text.charAt(currentEndIndex);
|
||||
LokiUserDatabase userDatabase = DatabaseFactory.getLokiUserDatabase(ConversationActivity.this);
|
||||
if (lastCharacter == '@') {
|
||||
List<Tuple2<String, String>> users = LokiAPI.Companion.getUsers("", threadId, userDatabase);
|
||||
mentionStartIndex = currentEndIndex + 1;
|
||||
userSelectionView.show(users, threadId);
|
||||
} else if (Character.getType(lastCharacter) != Character.UPPERCASE_LETTER && Character.getType(lastCharacter) != Character.LOWERCASE_LETTER) {
|
||||
mentionStartIndex = -1;
|
||||
userSelectionView.hide();
|
||||
} else {
|
||||
if (mentionStartIndex != -1) {
|
||||
String query = text.substring(mentionStartIndex);
|
||||
List<Tuple2<String, String>> users = LokiAPI.Companion.getUsers(query, threadId, userDatabase);
|
||||
userSelectionView.show(users, threadId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void setEnabled(boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setThreadId(long threadId) {
|
||||
this.threadId = threadId;
|
||||
@ -2747,11 +2799,11 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
}
|
||||
|
||||
inputPanel.setQuote(GlideApp.with(this),
|
||||
messageRecord.getDateSent(),
|
||||
author,
|
||||
body,
|
||||
slideDeck,
|
||||
recipient);
|
||||
messageRecord.getDateSent(),
|
||||
author,
|
||||
body,
|
||||
slideDeck,
|
||||
recipient);
|
||||
|
||||
} else if (messageRecord.isMms() && !((MmsMessageRecord) messageRecord).getLinkPreviews().isEmpty()) {
|
||||
LinkPreview linkPreview = ((MmsMessageRecord) messageRecord).getLinkPreviews().get(0);
|
||||
@ -2762,18 +2814,18 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
}
|
||||
|
||||
inputPanel.setQuote(GlideApp.with(this),
|
||||
messageRecord.getDateSent(),
|
||||
author,
|
||||
messageRecord.getBody(),
|
||||
slideDeck,
|
||||
recipient);
|
||||
messageRecord.getDateSent(),
|
||||
author,
|
||||
messageRecord.getBody(),
|
||||
slideDeck,
|
||||
recipient);
|
||||
} else {
|
||||
inputPanel.setQuote(GlideApp.with(this),
|
||||
messageRecord.getDateSent(),
|
||||
author,
|
||||
messageRecord.getBody(),
|
||||
messageRecord.isMms() ? ((MmsMessageRecord) messageRecord).getSlideDeck() : new SlideDeck(),
|
||||
recipient);
|
||||
messageRecord.getDateSent(),
|
||||
author,
|
||||
messageRecord.getBody(),
|
||||
messageRecord.isMms() ? ((MmsMessageRecord) messageRecord).getSlideDeck() : new SlideDeck(),
|
||||
recipient);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -42,6 +42,7 @@ import android.text.style.ForegroundColorSpan;
|
||||
import android.text.style.URLSpan;
|
||||
import android.text.util.Linkify;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Range;
|
||||
import android.util.TypedValue;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
@ -114,11 +115,14 @@ import org.thoughtcrime.securesms.util.views.Stub;
|
||||
import org.whispersystems.libsignal.util.guava.Optional;
|
||||
import org.whispersystems.signalservice.loki.api.LokiGroupChatAPI;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import network.loki.messenger.R;
|
||||
|
||||
@ -210,7 +214,7 @@ public class ConversationItem extends LinearLayout
|
||||
this.groupSenderProfileName = findViewById(R.id.group_message_sender_profile);
|
||||
this.alertView = findViewById(R.id.indicators_parent);
|
||||
this.contactPhoto = findViewById(R.id.contact_photo);
|
||||
this.moderatorIconImageView = findViewById(R.id.moderator_icon_image_view);
|
||||
this.moderatorIconImageView = findViewById(R.id.moderator_icon_image_view);
|
||||
this.contactPhotoHolder = findViewById(R.id.contact_photo_container);
|
||||
this.bodyBubble = findViewById(R.id.body_bubble);
|
||||
this.mediaThumbnailStub = new Stub<>(findViewById(R.id.image_view_stub));
|
||||
@ -258,7 +262,7 @@ public class ConversationItem extends LinearLayout
|
||||
setMessageShape(messageRecord, previousMessageRecord, nextMessageRecord, groupThread);
|
||||
setMediaAttributes(messageRecord, previousMessageRecord, nextMessageRecord, conversationRecipient, groupThread);
|
||||
setInteractionState(messageRecord, pulseHighlight);
|
||||
setBodyText(messageRecord, searchQuery);
|
||||
setBodyText(messageRecord, searchQuery, groupThread);
|
||||
setBubbleState(messageRecord);
|
||||
setStatusIcons(messageRecord);
|
||||
setContactPhoto(recipient);
|
||||
@ -464,7 +468,7 @@ public class ConversationItem extends LinearLayout
|
||||
!StickerUrl.isValidShareLink(linkPreview.getUrl());
|
||||
}
|
||||
|
||||
private void setBodyText(MessageRecord messageRecord, @Nullable String searchQuery) {
|
||||
private void setBodyText(MessageRecord messageRecord, @Nullable String searchQuery, boolean isGroupThread) {
|
||||
bodyText.setClickable(false);
|
||||
bodyText.setFocusable(false);
|
||||
bodyText.setTextSize(TypedValue.COMPLEX_UNIT_SP, TextSecurePreferences.getMessageBodyTextSize(context));
|
||||
@ -472,9 +476,9 @@ public class ConversationItem extends LinearLayout
|
||||
if (isCaptionlessMms(messageRecord)) {
|
||||
bodyText.setVisibility(View.GONE);
|
||||
} else {
|
||||
Spannable styledText = linkifyMessageBody(messageRecord.getDisplayBody(getContext()), batchSelected.isEmpty());
|
||||
styledText = SearchUtil.getHighlightedSpan(locale, () -> new BackgroundColorSpan(Color.YELLOW), styledText, searchQuery);
|
||||
styledText = SearchUtil.getHighlightedSpan(locale, () -> new ForegroundColorSpan(Color.BLACK), styledText, searchQuery);
|
||||
Spannable text = linkifyMessageBody(highlightMentions(messageRecord.getDisplayBody(context), isGroupThread), batchSelected.isEmpty());
|
||||
text = SearchUtil.getHighlightedSpan(locale, () -> new BackgroundColorSpan(Color.YELLOW), text, searchQuery);
|
||||
text = SearchUtil.getHighlightedSpan(locale, () -> new ForegroundColorSpan(Color.BLACK), text, searchQuery);
|
||||
|
||||
if (hasExtraText(messageRecord)) {
|
||||
bodyText.setOverflowText(getLongMessageSpan(messageRecord));
|
||||
@ -482,7 +486,7 @@ public class ConversationItem extends LinearLayout
|
||||
bodyText.setOverflowText(null);
|
||||
}
|
||||
|
||||
bodyText.setText(styledText);
|
||||
bodyText.setText(text);
|
||||
bodyText.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
@ -772,6 +776,40 @@ public class ConversationItem extends LinearLayout
|
||||
return messageBody;
|
||||
}
|
||||
|
||||
private SpannableString highlightMentions(CharSequence text, boolean isGroupThread) {
|
||||
Pattern pattern = Pattern.compile("@\\w*");
|
||||
Matcher matcher = pattern.matcher(text);
|
||||
ArrayList<Range<Integer>> mentions = new ArrayList<>();
|
||||
if (matcher.find() && isGroupThread) {
|
||||
while (true) {
|
||||
CharSequence userID = text.subSequence(matcher.start() + 1, matcher.end()); // +1 to get rid of the @
|
||||
Integer matchEnd;
|
||||
String userDisplayName;
|
||||
if (userID.equals(TextSecurePreferences.getLocalNumber(context))) {
|
||||
userDisplayName = TextSecurePreferences.getProfileName(context);
|
||||
} else {
|
||||
String publicChatID = LokiGroupChatAPI.getPublicChatServer() + "." + LokiGroupChatAPI.getPublicChatServerID();
|
||||
userDisplayName = DatabaseFactory.getLokiUserDatabase(context).getServerDisplayName(publicChatID, userID.toString());
|
||||
}
|
||||
if (userDisplayName != null) {
|
||||
text = text.subSequence(0, matcher.start()) + "@" + userDisplayName + text.subSequence(matcher.end(), Math.max(text.length(), matcher.end()));
|
||||
matchEnd = matcher.start() + 1 + userDisplayName.length();
|
||||
mentions.add(Range.create(matcher.start(), matchEnd));
|
||||
} else {
|
||||
matchEnd = matcher.end();
|
||||
}
|
||||
matcher = pattern.matcher(text.subSequence(matchEnd, Math.max(text.length(), matchEnd)));
|
||||
if (!matcher.find()) { break; }
|
||||
}
|
||||
}
|
||||
SpannableString result = new SpannableString(text);
|
||||
for (Range<Integer> range : mentions) {
|
||||
int highlightColor = (messageRecord.isOutgoing()) ? getResources().getColor(R.color.loki_dark_green) : getResources().getColor(R.color.loki_green);
|
||||
result.setSpan(new BackgroundColorSpan(highlightColor), range.getLower(), range.getUpper(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private void setStatusIcons(MessageRecord messageRecord) {
|
||||
bodyText.setCompoundDrawablesWithIntrinsicBounds(0, 0, messageRecord.isKeyExchange() ? R.drawable.ic_menu_login : 0, 0);
|
||||
|
||||
|
@ -67,6 +67,7 @@ import org.thoughtcrime.securesms.linkpreview.LinkPreview;
|
||||
import org.thoughtcrime.securesms.linkpreview.LinkPreviewRepository;
|
||||
import org.thoughtcrime.securesms.linkpreview.LinkPreviewUtil;
|
||||
import org.thoughtcrime.securesms.logging.Log;
|
||||
import org.thoughtcrime.securesms.loki.LokiAPIUtilities;
|
||||
import org.thoughtcrime.securesms.loki.LokiMessageDatabase;
|
||||
import org.thoughtcrime.securesms.loki.LokiPreKeyBundleDatabase;
|
||||
import org.thoughtcrime.securesms.loki.LokiPreKeyRecordDatabase;
|
||||
@ -123,6 +124,7 @@ import org.whispersystems.signalservice.api.messages.multidevice.VerifiedMessage
|
||||
import org.whispersystems.signalservice.api.messages.shared.SharedContact;
|
||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
||||
import org.whispersystems.signalservice.loki.api.DeviceLinkingSession;
|
||||
import org.whispersystems.signalservice.loki.api.LokiAPI;
|
||||
import org.whispersystems.signalservice.loki.api.LokiStorageAPI;
|
||||
import org.whispersystems.signalservice.loki.api.PairingAuthorisation;
|
||||
import org.whispersystems.signalservice.loki.crypto.LokiServiceCipher;
|
||||
@ -1021,6 +1023,12 @@ public class PushDecryptJob extends BaseJob implements InjectableType {
|
||||
|
||||
if (smsMessageId.isPresent()) database.deleteMessage(smsMessageId.get());
|
||||
|
||||
// Loki - Cache the user hex encoded public key (for mentions)
|
||||
if (threadId != null) {
|
||||
LokiAPIUtilities.INSTANCE.populateUserIDCacheIfNeeded(threadId, context);
|
||||
LokiAPI.Companion.cache(textMessage.getSender().serialize(), threadId);
|
||||
}
|
||||
|
||||
// Loki - Store message server ID
|
||||
updateGroupChatMessageServerID(messageServerIDOrNull, insertResult);
|
||||
|
||||
|
29
src/org/thoughtcrime/securesms/loki/LokiAPIUtilities.kt
Normal file
29
src/org/thoughtcrime/securesms/loki/LokiAPIUtilities.kt
Normal file
@ -0,0 +1,29 @@
|
||||
package org.thoughtcrime.securesms.loki
|
||||
|
||||
import android.content.Context
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory
|
||||
import org.thoughtcrime.securesms.database.model.MessageRecord
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences
|
||||
import org.whispersystems.signalservice.loki.api.LokiAPI
|
||||
|
||||
object LokiAPIUtilities {
|
||||
|
||||
fun populateUserIDCacheIfNeeded(threadID: Long, context: Context) {
|
||||
if (LokiAPI.userIDCache[threadID] != null) { return }
|
||||
val result = mutableSetOf<String>()
|
||||
val messageDatabase = DatabaseFactory.getMmsSmsDatabase(context)
|
||||
val reader = messageDatabase.readerFor(messageDatabase.getConversation(threadID))
|
||||
var record: MessageRecord? = reader.next
|
||||
while (record != null) {
|
||||
result.add(record.individualRecipient.address.serialize())
|
||||
try {
|
||||
record = reader.next
|
||||
} catch (exception: Exception) {
|
||||
record = null
|
||||
}
|
||||
}
|
||||
reader.close()
|
||||
result.add(TextSecurePreferences.getLocalNumber(context))
|
||||
LokiAPI.userIDCache[threadID] = result
|
||||
}
|
||||
}
|
@ -46,7 +46,7 @@ class LokiUserDatabase(context: Context, helper: SQLCipherOpenHelper) : Database
|
||||
Recipient.from(context, Address.fromSerialized(hexEncodedPublicKey), false).notifyListeners()
|
||||
}
|
||||
|
||||
fun getServerDisplayName(serverID: String, hexEncodedPublicKey: String): String? {
|
||||
override fun getServerDisplayName(serverID: String, hexEncodedPublicKey: String): String? {
|
||||
val database = databaseHelper.readableDatabase
|
||||
return database.get(serverDisplayNameTable, "${Companion.hexEncodedPublicKey} = ? AND ${Companion.serverID} = ?", arrayOf( hexEncodedPublicKey, serverID )) { cursor ->
|
||||
cursor.getString(cursor.getColumnIndexOrThrow(displayName))
|
||||
|
@ -9,9 +9,6 @@ import android.view.View
|
||||
import android.view.inputmethod.InputMethodManager
|
||||
import android.widget.Toast
|
||||
import kotlinx.android.synthetic.main.activity_seed.*
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import network.loki.messenger.R
|
||||
import org.thoughtcrime.securesms.ApplicationContext
|
||||
import org.thoughtcrime.securesms.BaseActionBarActivity
|
||||
@ -180,13 +177,12 @@ class SeedActivity : BaseActionBarActivity(), DeviceLinkingDialogDelegate {
|
||||
IdentityKeyUtil.generateIdentityKeyPair(this, seed)
|
||||
}
|
||||
val keyPair = IdentityKeyUtil.getIdentityKeyPair(this)
|
||||
val publicKey = keyPair.publicKey
|
||||
val hexEncodedPublicKey = keyPair.hexEncodedPublicKey
|
||||
val userHexEncodedPublicKey = keyPair.hexEncodedPublicKey
|
||||
val registrationID = KeyHelper.generateRegistrationId(false)
|
||||
TextSecurePreferences.setLocalRegistrationId(this, registrationID)
|
||||
DatabaseFactory.getIdentityDatabase(this).saveIdentity(Address.fromSerialized(hexEncodedPublicKey), publicKey,
|
||||
DatabaseFactory.getIdentityDatabase(this).saveIdentity(Address.fromSerialized(userHexEncodedPublicKey), keyPair.publicKey,
|
||||
IdentityDatabase.VerifiedStatus.VERIFIED, true, System.currentTimeMillis(), true)
|
||||
TextSecurePreferences.setLocalNumber(this, hexEncodedPublicKey)
|
||||
TextSecurePreferences.setLocalNumber(this, userHexEncodedPublicKey)
|
||||
when (mode) {
|
||||
Mode.Register -> Analytics.shared.track("Seed Created")
|
||||
Mode.Restore -> Analytics.shared.track("Seed Restored")
|
||||
@ -195,22 +191,20 @@ class SeedActivity : BaseActionBarActivity(), DeviceLinkingDialogDelegate {
|
||||
if (mode == Mode.Link) {
|
||||
TextSecurePreferences.setHasSeenWelcomeScreen(this, true)
|
||||
TextSecurePreferences.setPromptedPushRegistration(this, true)
|
||||
val primaryDevicePublicKey = publicKeyEditText.text.trim().toString()
|
||||
val authorisation = PairingAuthorisation(primaryDevicePublicKey, hexEncodedPublicKey).sign(PairingAuthorisation.Type.REQUEST, keyPair.privateKey.serialize())
|
||||
val masterHexEncodedPublicKey = publicKeyEditText.text.trim().toString()
|
||||
val authorisation = PairingAuthorisation(masterHexEncodedPublicKey, userHexEncodedPublicKey).sign(PairingAuthorisation.Type.REQUEST, keyPair.privateKey.serialize())
|
||||
if (authorisation == null) {
|
||||
Log.d("Loki", "Failed to sign outgoing pairing request.")
|
||||
Log.d("Loki", "Failed to sign pairing request.")
|
||||
resetForRegistration()
|
||||
return Toast.makeText(application, "Failed to link device.", Toast.LENGTH_SHORT).show()
|
||||
return Toast.makeText(application, "Couldn't start device linking process.", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
val application = ApplicationContext.getInstance(this)
|
||||
application.startLongPollingIfNeeded()
|
||||
application.setUpP2PAPI()
|
||||
application.setUpStorageAPIIfNeeded()
|
||||
DeviceLinkingDialog.show(this, DeviceLinkingView.Mode.Slave, this)
|
||||
CoroutineScope(Dispatchers.Main).launch {
|
||||
retryIfNeeded(8) {
|
||||
sendPairingAuthorisationMessage(this@SeedActivity, authorisation.primaryDevicePublicKey, authorisation).get()
|
||||
}
|
||||
retryIfNeeded(8) {
|
||||
sendPairingAuthorisationMessage(this@SeedActivity, authorisation.primaryDevicePublicKey, authorisation).get()
|
||||
}
|
||||
} else {
|
||||
startActivity(Intent(this, DisplayNameActivity::class.java))
|
||||
|
71
src/org/thoughtcrime/securesms/loki/UserSelectionView.kt
Normal file
71
src/org/thoughtcrime/securesms/loki/UserSelectionView.kt
Normal file
@ -0,0 +1,71 @@
|
||||
package org.thoughtcrime.securesms.loki
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.BaseAdapter
|
||||
import android.widget.ListView
|
||||
import nl.komponents.kovenant.combine.Tuple2
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory
|
||||
|
||||
class UserSelectionView(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : ListView(context, attrs, defStyleAttr) {
|
||||
private var users = listOf<Tuple2<String, String>>()
|
||||
set(newValue) { field = newValue; userSelectionViewAdapter.users = newValue }
|
||||
private var hasGroupContext = false
|
||||
var onUserSelected: ((Tuple2<String, String>) -> Unit)? = null
|
||||
|
||||
private val userSelectionViewAdapter by lazy { Adapter(context) }
|
||||
|
||||
private class Adapter(private val context: Context) : BaseAdapter() {
|
||||
var users = listOf<Tuple2<String, String>>()
|
||||
set(newValue) { field = newValue; notifyDataSetChanged() }
|
||||
var hasGroupContext = false
|
||||
|
||||
override fun getCount(): Int {
|
||||
return users.count()
|
||||
}
|
||||
|
||||
override fun getItemId(position: Int): Long {
|
||||
return position.toLong()
|
||||
}
|
||||
|
||||
override fun getItem(position: Int): Tuple2<String, String> {
|
||||
return users[position]
|
||||
}
|
||||
|
||||
override fun getView(position: Int, cellToBeReused: View?, parent: ViewGroup): View {
|
||||
val cell = cellToBeReused as UserSelectionViewCell? ?: UserSelectionViewCell.inflate(LayoutInflater.from(context), parent)
|
||||
val user = getItem(position)
|
||||
cell.user = user
|
||||
cell.hasGroupContext = hasGroupContext
|
||||
return cell
|
||||
}
|
||||
}
|
||||
|
||||
constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0)
|
||||
constructor(context: Context) : this(context, null)
|
||||
|
||||
init {
|
||||
adapter = userSelectionViewAdapter
|
||||
userSelectionViewAdapter.users = users
|
||||
setOnItemClickListener { _, _, position, _ ->
|
||||
onUserSelected?.invoke(users[position])
|
||||
}
|
||||
}
|
||||
|
||||
fun show(users: List<Tuple2<String, String>>, threadID: Long) {
|
||||
hasGroupContext = DatabaseFactory.getThreadDatabase(context).getRecipientForThreadId(threadID)!!.isGroupRecipient
|
||||
this.users = users
|
||||
val layoutParams = this.layoutParams as ViewGroup.LayoutParams
|
||||
layoutParams.height = toPx(6 + Math.min(users.count(), 4) * 52, resources)
|
||||
this.layoutParams = layoutParams
|
||||
}
|
||||
|
||||
fun hide() {
|
||||
val layoutParams = this.layoutParams as ViewGroup.LayoutParams
|
||||
layoutParams.height = 0
|
||||
this.layoutParams = layoutParams
|
||||
}
|
||||
}
|
48
src/org/thoughtcrime/securesms/loki/UserSelectionViewCell.kt
Normal file
48
src/org/thoughtcrime/securesms/loki/UserSelectionViewCell.kt
Normal file
@ -0,0 +1,48 @@
|
||||
package org.thoughtcrime.securesms.loki
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Outline
|
||||
import android.util.AttributeSet
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.ViewOutlineProvider
|
||||
import android.widget.LinearLayout
|
||||
import kotlinx.android.synthetic.main.cell_user_selection_view.view.*
|
||||
import network.loki.messenger.R
|
||||
import nl.komponents.kovenant.combine.Tuple2
|
||||
import org.whispersystems.signalservice.loki.api.LokiGroupChatAPI
|
||||
|
||||
class UserSelectionViewCell(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : LinearLayout(context, attrs, defStyleAttr) {
|
||||
var user = Tuple2("", "")
|
||||
set(newValue) { field = newValue; update() }
|
||||
var hasGroupContext = false
|
||||
|
||||
constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0)
|
||||
constructor(context: Context) : this(context, null)
|
||||
|
||||
companion object {
|
||||
|
||||
fun inflate(layoutInflater: LayoutInflater, parent: ViewGroup): UserSelectionViewCell {
|
||||
return layoutInflater.inflate(R.layout.cell_user_selection_view, parent, false) as UserSelectionViewCell
|
||||
}
|
||||
}
|
||||
|
||||
override fun onFinishInflate() {
|
||||
super.onFinishInflate()
|
||||
profilePictureImageViewContainer.outlineProvider = object : ViewOutlineProvider() {
|
||||
|
||||
override fun getOutline(view: View, outline: Outline) {
|
||||
outline.setOval(0, 0, view.width, view.height)
|
||||
}
|
||||
}
|
||||
profilePictureImageViewContainer.clipToOutline = true
|
||||
}
|
||||
|
||||
private fun update() {
|
||||
displayNameTextView.text = user.second
|
||||
profilePictureImageView.update(user.first)
|
||||
val isUserModerator = LokiGroupChatAPI.isUserModerator(user.first, LokiGroupChatAPI.publicChatServerID, LokiGroupChatAPI.publicChatServer)
|
||||
moderatorIconImageView.visibility = if (isUserModerator && hasGroupContext) View.VISIBLE else View.GONE
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user