mirror of
https://github.com/oxen-io/session-android.git
synced 2024-12-25 09:17:44 +00:00
Merge branch 'dev' into custom-server
# Conflicts: # src/org/thoughtcrime/securesms/loki/DisplayNameActivity.kt
This commit is contained in:
commit
2f18c5bad2
@ -2,10 +2,8 @@
|
|||||||
<ScrollView
|
<ScrollView
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent">
|
||||||
tools:context=".loki.AccountDetailsActivity">
|
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
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,6 +71,13 @@
|
|||||||
android:inflatedId="@+id/attachment_editor"
|
android:inflatedId="@+id/attachment_editor"
|
||||||
android:layout="@layout/conversation_activity_attachment_editor_stub" />
|
android:layout="@layout/conversation_activity_attachment_editor_stub" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<include layout="@layout/view_user_selection" />
|
||||||
|
|
||||||
<FrameLayout
|
<FrameLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
@ -83,6 +90,8 @@
|
|||||||
|
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
android:id="@+id/register_button"
|
android:id="@+id/register_button"
|
||||||
android:layout_width="fill_parent"
|
android:layout_width="fill_parent"
|
||||||
|
@ -101,6 +101,7 @@
|
|||||||
tools:visibility="invisible"
|
tools:visibility="invisible"
|
||||||
tools:hint="Send TextSecure message" >
|
tools:hint="Send TextSecure message" >
|
||||||
<requestFocus />
|
<requestFocus />
|
||||||
|
|
||||||
</org.thoughtcrime.securesms.components.ComposeText>
|
</org.thoughtcrime.securesms.components.ComposeText>
|
||||||
|
|
||||||
<FrameLayout
|
<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>
|
<resources>
|
||||||
<!-- Loki -->
|
<!-- Loki -->
|
||||||
<color name="loki_green">#78be20</color>
|
<color name="loki_green">#78be20</color>
|
||||||
|
<color name="loki_dark_green">#419B41</color>
|
||||||
<color name="loki_darkest_gray">#0a0a0a</color>
|
<color name="loki_darkest_gray">#0a0a0a</color>
|
||||||
<!-- Loki -->
|
<!-- Loki -->
|
||||||
|
|
||||||
|
@ -1552,7 +1552,7 @@
|
|||||||
<!-- Display name activity -->
|
<!-- Display name activity -->
|
||||||
<string name="activity_display_name_title">Create Your Loki Messenger Account</string>
|
<string name="activity_display_name_title">Create Your Loki Messenger Account</string>
|
||||||
<string name="activity_display_name_subtitle">Enter a name to be shown to your contacts</string>
|
<string name="activity_display_name_subtitle">Enter a name to be shown to your contacts</string>
|
||||||
<string name="activity_display_name_name_edit_text_label">Display Name (Optional)</string>
|
<string name="activity_display_name_name_edit_text_label">Display Name</string>
|
||||||
<string name="activity_display_name_button_title">Next</string>
|
<string name="activity_display_name_button_title">Next</string>
|
||||||
<!-- Key pair activity -->
|
<!-- Key pair activity -->
|
||||||
<string name="activity_key_pair_title">Create Your Loki Messenger Account</string>
|
<string name="activity_key_pair_title">Create Your Loki Messenger Account</string>
|
||||||
|
@ -160,7 +160,7 @@ public class ApplicationPreferencesActivity extends PassphraseRequiredActionBarA
|
|||||||
super.onCreate(icicle);
|
super.onCreate(icicle);
|
||||||
|
|
||||||
String masterHexEncodedPublicKey = TextSecurePreferences.getMasterHexEncodedPublicKey(getContext());
|
String masterHexEncodedPublicKey = TextSecurePreferences.getMasterHexEncodedPublicKey(getContext());
|
||||||
boolean isMasterDevice = (masterHexEncodedPublicKey != null);
|
boolean isMasterDevice = (masterHexEncodedPublicKey == null);
|
||||||
|
|
||||||
Preference profilePreference = this.findPreference(PREFERENCE_CATEGORY_PROFILE);
|
Preference profilePreference = this.findPreference(PREFERENCE_CATEGORY_PROFILE);
|
||||||
// Hide if this is a slave device
|
// Hide if this is a slave device
|
||||||
|
@ -203,7 +203,7 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit
|
|||||||
int height = profilePictureImageView.getHeight();
|
int height = profilePictureImageView.getHeight();
|
||||||
if (width == 0 || height == 0) return true;
|
if (width == 0 || height == 0) return true;
|
||||||
profilePictureImageView.getViewTreeObserver().removeOnPreDrawListener(this);
|
profilePictureImageView.getViewTreeObserver().removeOnPreDrawListener(this);
|
||||||
JazzIdenticonDrawable identicon = new JazzIdenticonDrawable(width, height, recipient.getAddress().serialize());
|
JazzIdenticonDrawable identicon = new JazzIdenticonDrawable(width, height, recipient.getAddress().serialize().toLowerCase());
|
||||||
profilePictureImageView.setImageDrawable(identicon);
|
profilePictureImageView.setImageDrawable(identicon);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -37,6 +37,8 @@ import org.thoughtcrime.securesms.components.FromTextView;
|
|||||||
import org.thoughtcrime.securesms.components.ThumbnailView;
|
import org.thoughtcrime.securesms.components.ThumbnailView;
|
||||||
import org.thoughtcrime.securesms.components.TypingIndicatorView;
|
import org.thoughtcrime.securesms.components.TypingIndicatorView;
|
||||||
import org.thoughtcrime.securesms.database.model.ThreadRecord;
|
import org.thoughtcrime.securesms.database.model.ThreadRecord;
|
||||||
|
import org.thoughtcrime.securesms.loki.LokiAPIUtilities;
|
||||||
|
import org.thoughtcrime.securesms.loki.MentionUtilities;
|
||||||
import org.thoughtcrime.securesms.mms.GlideRequests;
|
import org.thoughtcrime.securesms.mms.GlideRequests;
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||||
import org.thoughtcrime.securesms.recipients.RecipientModifiedListener;
|
import org.thoughtcrime.securesms.recipients.RecipientModifiedListener;
|
||||||
@ -270,8 +272,9 @@ public class ConversationListItem extends RelativeLayout
|
|||||||
}
|
}
|
||||||
|
|
||||||
private @NonNull CharSequence getTrimmedSnippet(@NonNull CharSequence snippet) {
|
private @NonNull CharSequence getTrimmedSnippet(@NonNull CharSequence snippet) {
|
||||||
return snippet.length() <= MAX_SNIPPET_LENGTH ? snippet
|
LokiAPIUtilities.INSTANCE.populateUserIDCacheIfNeeded(threadId, getContext()); // TODO: Terrible place to do this, but okay for now
|
||||||
: snippet.subSequence(0, MAX_SNIPPET_LENGTH);
|
snippet = MentionUtilities.highlightMentions(snippet, this.recipient.isGroupRecipient(), getContext());
|
||||||
|
return snippet.length() <= MAX_SNIPPET_LENGTH ? snippet : snippet.subSequence(0, MAX_SNIPPET_LENGTH);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setThumbnailSnippet(ThreadRecord thread) {
|
private void setThumbnailSnippet(ThreadRecord thread) {
|
||||||
|
@ -232,7 +232,10 @@ public class CreateProfileActivity extends BaseActionBarActivity implements Inje
|
|||||||
public void onTextChanged(CharSequence s, int start, int before, int count) {}
|
public void onTextChanged(CharSequence s, int start, int before, int count) {}
|
||||||
@Override
|
@Override
|
||||||
public void afterTextChanged(Editable s) {
|
public void afterTextChanged(Editable s) {
|
||||||
if (s.toString().getBytes().length > ProfileCipher.NAME_PADDED_LENGTH) {
|
if (s.toString().isEmpty()) {
|
||||||
|
name.getInput().setError("Invalid");
|
||||||
|
finishButton.setEnabled(false);
|
||||||
|
} else if (s.toString().getBytes().length > ProfileCipher.NAME_PADDED_LENGTH) {
|
||||||
name.getInput().setError(getString(R.string.CreateProfileActivity_too_long));
|
name.getInput().setError(getString(R.string.CreateProfileActivity_too_long));
|
||||||
finishButton.setEnabled(false);
|
finishButton.setEnabled(false);
|
||||||
} else if (name.getInput().getError() != null || !finishButton.isEnabled()) {
|
} else if (name.getInput().getError() != null || !finishButton.isEnabled()) {
|
||||||
|
@ -19,6 +19,7 @@ import android.view.ViewOutlineProvider;
|
|||||||
import org.thoughtcrime.securesms.color.MaterialColor;
|
import org.thoughtcrime.securesms.color.MaterialColor;
|
||||||
import org.thoughtcrime.securesms.contacts.avatars.ContactColors;
|
import org.thoughtcrime.securesms.contacts.avatars.ContactColors;
|
||||||
import org.thoughtcrime.securesms.contacts.avatars.GeneratedContactPhoto;
|
import org.thoughtcrime.securesms.contacts.avatars.GeneratedContactPhoto;
|
||||||
|
import org.thoughtcrime.securesms.database.Address;
|
||||||
import org.thoughtcrime.securesms.loki.JazzIdenticonDrawable;
|
import org.thoughtcrime.securesms.loki.JazzIdenticonDrawable;
|
||||||
import org.thoughtcrime.securesms.mms.GlideRequests;
|
import org.thoughtcrime.securesms.mms.GlideRequests;
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||||
@ -118,11 +119,15 @@ public class AvatarImageView extends AppCompatImageView {
|
|||||||
|
|
||||||
image = new GeneratedContactPhoto(name, R.drawable.ic_profile_default).asDrawable(context, fallbackColor.toAvatarColor(context));
|
image = new GeneratedContactPhoto(name, R.drawable.ic_profile_default).asDrawable(context, fallbackColor.toAvatarColor(context));
|
||||||
} else {
|
} else {
|
||||||
image = new JazzIdenticonDrawable(w, h, recipient.getAddress().serialize());
|
image = new JazzIdenticonDrawable(w, h, recipient.getAddress().serialize().toLowerCase());
|
||||||
}
|
}
|
||||||
setImageDrawable(image);
|
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) {
|
public void setAvatar(@NonNull GlideRequests requestManager, @Nullable Recipient recipient, boolean quickContactEnabled) {
|
||||||
this.recipient = recipient;
|
this.recipient = recipient;
|
||||||
/*
|
/*
|
||||||
|
@ -156,7 +156,11 @@ import org.thoughtcrime.securesms.linkpreview.LinkPreviewUtil;
|
|||||||
import org.thoughtcrime.securesms.linkpreview.LinkPreviewViewModel;
|
import org.thoughtcrime.securesms.linkpreview.LinkPreviewViewModel;
|
||||||
import org.thoughtcrime.securesms.logging.Log;
|
import org.thoughtcrime.securesms.logging.Log;
|
||||||
import org.thoughtcrime.securesms.loki.FriendRequestViewDelegate;
|
import org.thoughtcrime.securesms.loki.FriendRequestViewDelegate;
|
||||||
|
import org.thoughtcrime.securesms.loki.LokiAPIUtilities;
|
||||||
import org.thoughtcrime.securesms.loki.LokiThreadDatabaseDelegate;
|
import org.thoughtcrime.securesms.loki.LokiThreadDatabaseDelegate;
|
||||||
|
import org.thoughtcrime.securesms.loki.LokiUserDatabase;
|
||||||
|
import org.thoughtcrime.securesms.loki.Mention;
|
||||||
|
import org.thoughtcrime.securesms.loki.UserSelectionView;
|
||||||
import org.thoughtcrime.securesms.mediasend.Media;
|
import org.thoughtcrime.securesms.mediasend.Media;
|
||||||
import org.thoughtcrime.securesms.mediasend.MediaSendActivity;
|
import org.thoughtcrime.securesms.mediasend.MediaSendActivity;
|
||||||
import org.thoughtcrime.securesms.mms.AttachmentManager;
|
import org.thoughtcrime.securesms.mms.AttachmentManager;
|
||||||
@ -224,12 +228,14 @@ import org.whispersystems.libsignal.util.guava.Optional;
|
|||||||
import org.whispersystems.signalservice.api.SignalServiceMessageSender;
|
import org.whispersystems.signalservice.api.SignalServiceMessageSender;
|
||||||
import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage;
|
import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage;
|
||||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
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.LokiMessageFriendRequestStatus;
|
||||||
import org.whispersystems.signalservice.loki.messaging.LokiThreadFriendRequestStatus;
|
import org.whispersystems.signalservice.loki.messaging.LokiThreadFriendRequestStatus;
|
||||||
import org.whispersystems.signalservice.loki.utilities.Analytics;
|
import org.whispersystems.signalservice.loki.utilities.Analytics;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
@ -239,7 +245,9 @@ import java.util.concurrent.ExecutionException;
|
|||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
|
import kotlin.Unit;
|
||||||
import network.loki.messenger.R;
|
import network.loki.messenger.R;
|
||||||
|
import nl.komponents.kovenant.combine.Tuple2;
|
||||||
|
|
||||||
import static org.thoughtcrime.securesms.TransportOption.Type;
|
import static org.thoughtcrime.securesms.TransportOption.Type;
|
||||||
import static org.thoughtcrime.securesms.database.GroupDatabase.GroupRecord;
|
import static org.thoughtcrime.securesms.database.GroupDatabase.GroupRecord;
|
||||||
@ -309,6 +317,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
|||||||
private Stub<UnverifiedBannerView> unverifiedBannerView;
|
private Stub<UnverifiedBannerView> unverifiedBannerView;
|
||||||
private Stub<GroupShareProfileView> groupShareProfileView;
|
private Stub<GroupShareProfileView> groupShareProfileView;
|
||||||
private TypingStatusTextWatcher typingTextWatcher;
|
private TypingStatusTextWatcher typingTextWatcher;
|
||||||
|
private MentionTextWatcher mentionTextWatcher;
|
||||||
private ConversationSearchBottomBar searchNav;
|
private ConversationSearchBottomBar searchNav;
|
||||||
private MenuItem searchViewItem;
|
private MenuItem searchViewItem;
|
||||||
|
|
||||||
@ -338,6 +347,12 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
|||||||
private final DynamicNoActionBarTheme dynamicTheme = new DynamicNoActionBarTheme();
|
private final DynamicNoActionBarTheme dynamicTheme = new DynamicNoActionBarTheme();
|
||||||
private final DynamicLanguage dynamicLanguage = new DynamicLanguage();
|
private final DynamicLanguage dynamicLanguage = new DynamicLanguage();
|
||||||
|
|
||||||
|
// Mentions
|
||||||
|
private UserSelectionView userSelectionView;
|
||||||
|
private int currentMentionStartIndex = -1;
|
||||||
|
private ArrayList<Mention> mentions = new ArrayList<>();
|
||||||
|
private String oldText = "";
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onPreCreate() {
|
protected void onPreCreate() {
|
||||||
@ -389,11 +404,25 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
|||||||
composeText.addTextChangedListener(typingTextWatcher);
|
composeText.addTextChangedListener(typingTextWatcher);
|
||||||
}
|
}
|
||||||
composeText.setSelection(composeText.length(), composeText.length());
|
composeText.setSelection(composeText.length(), composeText.length());
|
||||||
|
composeText.addTextChangedListener(mentionTextWatcher);
|
||||||
|
userSelectionView.setOnUserSelected(tuple -> {
|
||||||
|
Mention mention = new Mention(currentMentionStartIndex, tuple.getFirst(), tuple.getSecond());
|
||||||
|
mentions.add(mention);
|
||||||
|
String oldText = composeText.getText().toString();
|
||||||
|
String newText = oldText.substring(0, currentMentionStartIndex) + "@" + tuple.getSecond();
|
||||||
|
composeText.setText(newText);
|
||||||
|
composeText.setSelection(newText.length());
|
||||||
|
userSelectionView.hide();
|
||||||
|
currentMentionStartIndex = -1;
|
||||||
|
return Unit.INSTANCE;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
LokiAPIUtilities.INSTANCE.populateUserIDCacheIfNeeded(threadId, this);
|
||||||
|
|
||||||
if (this.recipient.isGroupRecipient()) {
|
if (this.recipient.isGroupRecipient()) {
|
||||||
if (this.recipient.getName().equals("Loki Public Chat")) {
|
if (this.recipient.getName().equals("Loki Public Chat")) {
|
||||||
Analytics.Companion.getShared().track("Loki Public Chat Opened");
|
Analytics.Companion.getShared().track("Loki Public Chat Opened");
|
||||||
@ -1552,6 +1581,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
|||||||
inlineAttachmentToggle = ViewUtil.findById(this, R.id.inline_attachment_container);
|
inlineAttachmentToggle = ViewUtil.findById(this, R.id.inline_attachment_container);
|
||||||
inputPanel = ViewUtil.findById(this, R.id.bottom_panel);
|
inputPanel = ViewUtil.findById(this, R.id.bottom_panel);
|
||||||
searchNav = ViewUtil.findById(this, R.id.conversation_search_nav);
|
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 quickCameraToggle = ViewUtil.findById(this, R.id.quick_camera_toggle);
|
||||||
ImageButton inlineAttachmentButton = ViewUtil.findById(this, R.id.inline_attachment_button);
|
ImageButton inlineAttachmentButton = ViewUtil.findById(this, R.id.inline_attachment_button);
|
||||||
@ -1564,6 +1594,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
|||||||
attachmentManager = new AttachmentManager(this, this);
|
attachmentManager = new AttachmentManager(this, this);
|
||||||
audioRecorder = new AudioRecorder(this);
|
audioRecorder = new AudioRecorder(this);
|
||||||
typingTextWatcher = new TypingStatusTextWatcher();
|
typingTextWatcher = new TypingStatusTextWatcher();
|
||||||
|
mentionTextWatcher = new MentionTextWatcher();
|
||||||
|
|
||||||
SendButtonListener sendButtonListener = new SendButtonListener();
|
SendButtonListener sendButtonListener = new SendButtonListener();
|
||||||
ComposeKeyPressedListener composeKeyPressedListener = new ComposeKeyPressedListener();
|
ComposeKeyPressedListener composeKeyPressedListener = new ComposeKeyPressedListener();
|
||||||
@ -2060,12 +2091,20 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
|||||||
}
|
}
|
||||||
|
|
||||||
private String getMessage() throws InvalidMessageException {
|
private String getMessage() throws InvalidMessageException {
|
||||||
String rawText = composeText.getTextTrimmed();
|
String result = composeText.getTextTrimmed();
|
||||||
|
if (result.length() < 1 && !attachmentManager.isAttachmentPresent()) throw new InvalidMessageException();
|
||||||
if (rawText.length() < 1 && !attachmentManager.isAttachmentPresent())
|
int shift = 0;
|
||||||
throw new InvalidMessageException(getString(R.string.ConversationActivity_message_is_empty_exclamation));
|
for (Mention mention : mentions) {
|
||||||
|
try {
|
||||||
return rawText;
|
int startIndex = mention.getLocationInString() + shift;
|
||||||
|
int endIndex = startIndex + mention.getDisplayName().length() + 1; // + 1 to include the @
|
||||||
|
shift = shift + mention.getHexEncodedPublicKey().length() - mention.getDisplayName().length();
|
||||||
|
result = result.substring(0, startIndex) + "@" + mention.getHexEncodedPublicKey() + result.substring(endIndex);
|
||||||
|
} catch (Exception exception) {
|
||||||
|
// Do nothing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Pair<String, Optional<Slide>> getSplitMessage(String rawText, int maxPrimaryMessageSize) {
|
private Pair<String, Optional<Slide>> getSplitMessage(String rawText, int maxPrimaryMessageSize) {
|
||||||
@ -2592,6 +2631,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
|||||||
private void silentlySetComposeText(String text) {
|
private void silentlySetComposeText(String text) {
|
||||||
typingTextWatcher.setEnabled(false);
|
typingTextWatcher.setEnabled(false);
|
||||||
composeText.setText(text);
|
composeText.setText(text);
|
||||||
|
if (text.isEmpty()) clearMentions();
|
||||||
typingTextWatcher.setEnabled(true);
|
typingTextWatcher.setEnabled(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2706,7 +2746,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
|||||||
}
|
}
|
||||||
|
|
||||||
private class TypingStatusTextWatcher extends SimpleTextWatcher {
|
private class TypingStatusTextWatcher extends SimpleTextWatcher {
|
||||||
|
|
||||||
private boolean enabled = true;
|
private boolean enabled = true;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -2721,6 +2760,55 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class MentionTextWatcher extends SimpleTextWatcher {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onTextChanged(String text) {
|
||||||
|
boolean isBackspace = text.length() < oldText.length();
|
||||||
|
if (isBackspace) {
|
||||||
|
currentMentionStartIndex = -1;
|
||||||
|
for (Mention mention : mentions) {
|
||||||
|
boolean isValid;
|
||||||
|
if (mention.getLocationInString() > (text.length() - 1)) {
|
||||||
|
isValid = false;
|
||||||
|
} else {
|
||||||
|
isValid = text.substring(mention.getLocationInString()).startsWith("@" + mention.getDisplayName());
|
||||||
|
}
|
||||||
|
if (!isValid) {
|
||||||
|
mentions.remove(mention);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (text.length() > 0) {
|
||||||
|
if (currentMentionStartIndex > text.length()) {
|
||||||
|
clearMentions(); // Should never occur
|
||||||
|
}
|
||||||
|
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);
|
||||||
|
currentMentionStartIndex = currentEndIndex;
|
||||||
|
userSelectionView.show(users, threadId);
|
||||||
|
} else if (Character.isWhitespace(lastCharacter)) {
|
||||||
|
currentMentionStartIndex = -1;
|
||||||
|
userSelectionView.hide();
|
||||||
|
} else {
|
||||||
|
if (currentMentionStartIndex != -1) {
|
||||||
|
String query = text.substring(currentMentionStartIndex + 1); // + 1 to get rid of the @
|
||||||
|
List<Tuple2<String, String>> users = LokiAPI.Companion.getUsers(query, threadId, userDatabase);
|
||||||
|
userSelectionView.show(users, threadId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void clearMentions() {
|
||||||
|
oldText = "";
|
||||||
|
currentMentionStartIndex = -1;
|
||||||
|
mentions.clear();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setThreadId(long threadId) {
|
public void setThreadId(long threadId) {
|
||||||
this.threadId = threadId;
|
this.threadId = threadId;
|
||||||
|
@ -91,6 +91,7 @@ import org.thoughtcrime.securesms.logging.Log;
|
|||||||
import org.thoughtcrime.securesms.loki.FriendRequestView;
|
import org.thoughtcrime.securesms.loki.FriendRequestView;
|
||||||
import org.thoughtcrime.securesms.loki.FriendRequestViewDelegate;
|
import org.thoughtcrime.securesms.loki.FriendRequestViewDelegate;
|
||||||
import org.thoughtcrime.securesms.loki.LokiMessageDatabase;
|
import org.thoughtcrime.securesms.loki.LokiMessageDatabase;
|
||||||
|
import org.thoughtcrime.securesms.loki.MentionUtilities;
|
||||||
import org.thoughtcrime.securesms.mms.GlideRequests;
|
import org.thoughtcrime.securesms.mms.GlideRequests;
|
||||||
import org.thoughtcrime.securesms.mms.ImageSlide;
|
import org.thoughtcrime.securesms.mms.ImageSlide;
|
||||||
import org.thoughtcrime.securesms.mms.PartAuthority;
|
import org.thoughtcrime.securesms.mms.PartAuthority;
|
||||||
@ -260,7 +261,7 @@ public class ConversationItem extends LinearLayout
|
|||||||
setMessageShape(messageRecord, previousMessageRecord, nextMessageRecord, groupThread);
|
setMessageShape(messageRecord, previousMessageRecord, nextMessageRecord, groupThread);
|
||||||
setMediaAttributes(messageRecord, previousMessageRecord, nextMessageRecord, conversationRecipient, groupThread);
|
setMediaAttributes(messageRecord, previousMessageRecord, nextMessageRecord, conversationRecipient, groupThread);
|
||||||
setInteractionState(messageRecord, pulseHighlight);
|
setInteractionState(messageRecord, pulseHighlight);
|
||||||
setBodyText(messageRecord, searchQuery);
|
setBodyText(messageRecord, searchQuery, groupThread);
|
||||||
setBubbleState(messageRecord);
|
setBubbleState(messageRecord);
|
||||||
setStatusIcons(messageRecord);
|
setStatusIcons(messageRecord);
|
||||||
setContactPhoto(recipient);
|
setContactPhoto(recipient);
|
||||||
@ -466,17 +467,17 @@ public class ConversationItem extends LinearLayout
|
|||||||
!StickerUrl.isValidShareLink(linkPreview.getUrl());
|
!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.setClickable(false);
|
||||||
bodyText.setFocusable(false);
|
bodyText.setFocusable(false);
|
||||||
bodyText.setTextSize(TypedValue.COMPLEX_UNIT_SP, TextSecurePreferences.getMessageBodyTextSize(context));
|
bodyText.setTextSize(TypedValue.COMPLEX_UNIT_SP, TextSecurePreferences.getMessageBodyTextSize(context));
|
||||||
|
|
||||||
if (isCaptionlessMms(messageRecord)) {
|
if (isCaptionlessMms(messageRecord)) {
|
||||||
bodyText.setVisibility(View.GONE);
|
bodyText.setVisibility(View.GONE);
|
||||||
} else {
|
} else { ;
|
||||||
Spannable styledText = linkifyMessageBody(messageRecord.getDisplayBody(getContext()), batchSelected.isEmpty());
|
Spannable text = MentionUtilities.highlightMentions(linkifyMessageBody(messageRecord.getDisplayBody(context), batchSelected.isEmpty()), messageRecord.isOutgoing(), isGroupThread, context);
|
||||||
styledText = SearchUtil.getHighlightedSpan(locale, () -> new BackgroundColorSpan(Color.YELLOW), styledText, searchQuery);
|
text = SearchUtil.getHighlightedSpan(locale, () -> new BackgroundColorSpan(Color.YELLOW), text, searchQuery);
|
||||||
styledText = SearchUtil.getHighlightedSpan(locale, () -> new ForegroundColorSpan(Color.BLACK), styledText, searchQuery);
|
text = SearchUtil.getHighlightedSpan(locale, () -> new ForegroundColorSpan(Color.BLACK), text, searchQuery);
|
||||||
|
|
||||||
if (hasExtraText(messageRecord)) {
|
if (hasExtraText(messageRecord)) {
|
||||||
bodyText.setOverflowText(getLongMessageSpan(messageRecord));
|
bodyText.setOverflowText(getLongMessageSpan(messageRecord));
|
||||||
@ -484,7 +485,7 @@ public class ConversationItem extends LinearLayout
|
|||||||
bodyText.setOverflowText(null);
|
bodyText.setOverflowText(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
bodyText.setText(styledText);
|
bodyText.setText(text);
|
||||||
bodyText.setVisibility(View.VISIBLE);
|
bodyText.setVisibility(View.VISIBLE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -790,7 +791,8 @@ public class ConversationItem extends LinearLayout
|
|||||||
if (current.isMms() && !current.isMmsNotification() && ((MediaMmsMessageRecord)current).getQuote() != null) {
|
if (current.isMms() && !current.isMmsNotification() && ((MediaMmsMessageRecord)current).getQuote() != null) {
|
||||||
Quote quote = ((MediaMmsMessageRecord)current).getQuote();
|
Quote quote = ((MediaMmsMessageRecord)current).getQuote();
|
||||||
//noinspection ConstantConditions
|
//noinspection ConstantConditions
|
||||||
quoteView.setQuote(glideRequests, quote.getId(), Recipient.from(context, quote.getAuthor(), true), quote.getText(), quote.isOriginalMissing(), quote.getAttachment(), conversationRecipient);
|
String quoteBody = MentionUtilities.highlightMentions(quote.getText(), isGroupThread, context);
|
||||||
|
quoteView.setQuote(glideRequests, quote.getId(), Recipient.from(context, quote.getAuthor(), true), quoteBody, quote.isOriginalMissing(), quote.getAttachment(), conversationRecipient);
|
||||||
quoteView.setVisibility(View.VISIBLE);
|
quoteView.setVisibility(View.VISIBLE);
|
||||||
quoteView.getLayoutParams().width = ViewGroup.LayoutParams.WRAP_CONTENT;
|
quoteView.getLayoutParams().width = ViewGroup.LayoutParams.WRAP_CONTENT;
|
||||||
|
|
||||||
|
@ -67,6 +67,7 @@ import org.thoughtcrime.securesms.linkpreview.LinkPreview;
|
|||||||
import org.thoughtcrime.securesms.linkpreview.LinkPreviewRepository;
|
import org.thoughtcrime.securesms.linkpreview.LinkPreviewRepository;
|
||||||
import org.thoughtcrime.securesms.linkpreview.LinkPreviewUtil;
|
import org.thoughtcrime.securesms.linkpreview.LinkPreviewUtil;
|
||||||
import org.thoughtcrime.securesms.logging.Log;
|
import org.thoughtcrime.securesms.logging.Log;
|
||||||
|
import org.thoughtcrime.securesms.loki.LokiAPIUtilities;
|
||||||
import org.thoughtcrime.securesms.loki.LokiMessageDatabase;
|
import org.thoughtcrime.securesms.loki.LokiMessageDatabase;
|
||||||
import org.thoughtcrime.securesms.loki.LokiPreKeyBundleDatabase;
|
import org.thoughtcrime.securesms.loki.LokiPreKeyBundleDatabase;
|
||||||
import org.thoughtcrime.securesms.loki.LokiPreKeyRecordDatabase;
|
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.messages.shared.SharedContact;
|
||||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
||||||
import org.whispersystems.signalservice.loki.api.DeviceLinkingSession;
|
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.LokiStorageAPI;
|
||||||
import org.whispersystems.signalservice.loki.api.PairingAuthorisation;
|
import org.whispersystems.signalservice.loki.api.PairingAuthorisation;
|
||||||
import org.whispersystems.signalservice.loki.crypto.LokiServiceCipher;
|
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());
|
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
|
// Loki - Store message server ID
|
||||||
updateGroupChatMessageServerID(messageServerIDOrNull, insertResult);
|
updateGroupChatMessageServerID(messageServerIDOrNull, insertResult);
|
||||||
|
|
||||||
|
@ -3,7 +3,7 @@ package org.thoughtcrime.securesms.loki
|
|||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.inputmethod.InputMethodManager
|
import android.view.inputmethod.InputMethodManager
|
||||||
import kotlinx.android.synthetic.main.activity_account_details.*
|
import kotlinx.android.synthetic.main.activity_display_name.*
|
||||||
import network.loki.messenger.R
|
import network.loki.messenger.R
|
||||||
import org.thoughtcrime.securesms.ApplicationContext
|
import org.thoughtcrime.securesms.ApplicationContext
|
||||||
import org.thoughtcrime.securesms.BaseActionBarActivity
|
import org.thoughtcrime.securesms.BaseActionBarActivity
|
||||||
@ -19,22 +19,22 @@ class DisplayNameActivity : BaseActionBarActivity() {
|
|||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
setContentView(R.layout.activity_account_details)
|
setContentView(R.layout.activity_display_name)
|
||||||
nextButton.setOnClickListener { continueIfPossible() }
|
nextButton.setOnClickListener { continueIfPossible() }
|
||||||
Analytics.shared.track("Display Name Screen Viewed")
|
Analytics.shared.track("Display Name Screen Viewed")
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun continueIfPossible() {
|
private fun continueIfPossible() {
|
||||||
val uncheckedName = nameEditText.text.toString()
|
val name = nameEditText.text.toString()
|
||||||
val name = if (uncheckedName.isNotEmpty()) { uncheckedName.trim() } else { null }
|
if (name.isEmpty()) {
|
||||||
if (name != null) {
|
return nameEditText.input.setError("Invalid")
|
||||||
|
}
|
||||||
if (name.toByteArray().size > ProfileCipher.NAME_PADDED_LENGTH) {
|
if (name.toByteArray().size > ProfileCipher.NAME_PADDED_LENGTH) {
|
||||||
return nameEditText.input.setError("Too Long")
|
return nameEditText.input.setError("Too Long")
|
||||||
} else {
|
} else {
|
||||||
Analytics.shared.track("Display Name Updated")
|
Analytics.shared.track("Display Name Updated")
|
||||||
TextSecurePreferences.setProfileName(this, name)
|
TextSecurePreferences.setProfileName(this, name)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
val inputMethodManager = getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager
|
val inputMethodManager = getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager
|
||||||
inputMethodManager.hideSoftInputFromWindow(nameEditText.windowToken, 0)
|
inputMethodManager.hideSoftInputFromWindow(nameEditText.windowToken, 0)
|
||||||
TextSecurePreferences.setHasSeenWelcomeScreen(this, true)
|
TextSecurePreferences.setHasSeenWelcomeScreen(this, true)
|
||||||
|
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()
|
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
|
val database = databaseHelper.readableDatabase
|
||||||
return database.get(serverDisplayNameTable, "${Companion.hexEncodedPublicKey} = ? AND ${Companion.serverID} = ?", arrayOf( hexEncodedPublicKey, serverID )) { cursor ->
|
return database.get(serverDisplayNameTable, "${Companion.hexEncodedPublicKey} = ? AND ${Companion.serverID} = ?", arrayOf( hexEncodedPublicKey, serverID )) { cursor ->
|
||||||
cursor.getString(cursor.getColumnIndexOrThrow(displayName))
|
cursor.getString(cursor.getColumnIndexOrThrow(displayName))
|
||||||
|
3
src/org/thoughtcrime/securesms/loki/Mention.kt
Normal file
3
src/org/thoughtcrime/securesms/loki/Mention.kt
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
package org.thoughtcrime.securesms.loki
|
||||||
|
|
||||||
|
data class Mention(val locationInString: Int, val hexEncodedPublicKey: String, val displayName: String)
|
56
src/org/thoughtcrime/securesms/loki/MentionUtilities.kt
Normal file
56
src/org/thoughtcrime/securesms/loki/MentionUtilities.kt
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
package org.thoughtcrime.securesms.loki
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.text.Spannable
|
||||||
|
import android.text.SpannableString
|
||||||
|
import android.text.style.BackgroundColorSpan
|
||||||
|
import android.util.Range
|
||||||
|
import network.loki.messenger.R
|
||||||
|
import org.thoughtcrime.securesms.database.DatabaseFactory
|
||||||
|
import org.thoughtcrime.securesms.util.TextSecurePreferences
|
||||||
|
import org.whispersystems.signalservice.loki.api.LokiGroupChatAPI
|
||||||
|
import java.util.regex.Pattern
|
||||||
|
|
||||||
|
object MentionUtilities {
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun highlightMentions(text: CharSequence, isGroupThread: Boolean, context: Context): String {
|
||||||
|
return MentionUtilities.highlightMentions(text, false, isGroupThread, context).toString() // isOutgoingMessage is irrelevant
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun highlightMentions(text: CharSequence, isOutgoingMessage: Boolean, isGroupThread: Boolean, context: Context): SpannableString {
|
||||||
|
var text = text
|
||||||
|
val pattern = Pattern.compile("@[0-9a-fA-F]*")
|
||||||
|
var matcher = pattern.matcher(text)
|
||||||
|
val mentions = mutableListOf<Range<Int>>()
|
||||||
|
var startIndex = 0
|
||||||
|
if (matcher.find(startIndex) && isGroupThread) {
|
||||||
|
while (true) {
|
||||||
|
val userID = text.subSequence(matcher.start() + 1, matcher.end()).toString() // +1 to get rid of the @
|
||||||
|
val userDisplayName: String? = if (userID.toLowerCase() == TextSecurePreferences.getLocalNumber(context).toLowerCase()) {
|
||||||
|
TextSecurePreferences.getProfileName(context)
|
||||||
|
} else {
|
||||||
|
val publicChatID = LokiGroupChatAPI.publicChatServer + "." + LokiGroupChatAPI.publicChatServerID
|
||||||
|
DatabaseFactory.getLokiUserDatabase(context).getServerDisplayName(publicChatID, userID)
|
||||||
|
}
|
||||||
|
if (userDisplayName != null) {
|
||||||
|
text = text.subSequence(0, matcher.start()).toString() + "@" + userDisplayName + text.subSequence(matcher.end(), text.length)
|
||||||
|
val endIndex = matcher.start() + 1 + userDisplayName.length
|
||||||
|
startIndex = endIndex
|
||||||
|
mentions.add(Range.create(matcher.start(), endIndex))
|
||||||
|
} else {
|
||||||
|
startIndex = matcher.end()
|
||||||
|
}
|
||||||
|
matcher = pattern.matcher(text)
|
||||||
|
if (!matcher.find(startIndex)) { break }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val result = SpannableString(text)
|
||||||
|
for (range in mentions) {
|
||||||
|
val highlightColor = if (isOutgoingMessage) context.resources.getColor(R.color.loki_dark_green) else context.resources.getColor(R.color.loki_green)
|
||||||
|
result.setSpan(BackgroundColorSpan(highlightColor), range.lower, range.upper, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
@ -8,7 +8,7 @@ import android.view.ViewGroup
|
|||||||
import kotlinx.android.synthetic.main.fragment_new_conversation.*
|
import kotlinx.android.synthetic.main.fragment_new_conversation.*
|
||||||
import network.loki.messenger.R
|
import network.loki.messenger.R
|
||||||
|
|
||||||
class NewConversationFragment() : Fragment() {
|
class NewConversationFragment : Fragment() {
|
||||||
|
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||||
return inflater.inflate(R.layout.fragment_new_conversation, container, false)
|
return inflater.inflate(R.layout.fragment_new_conversation, container, false)
|
||||||
|
@ -9,9 +9,6 @@ import android.view.View
|
|||||||
import android.view.inputmethod.InputMethodManager
|
import android.view.inputmethod.InputMethodManager
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import kotlinx.android.synthetic.main.activity_seed.*
|
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 network.loki.messenger.R
|
||||||
import org.thoughtcrime.securesms.ApplicationContext
|
import org.thoughtcrime.securesms.ApplicationContext
|
||||||
import org.thoughtcrime.securesms.BaseActionBarActivity
|
import org.thoughtcrime.securesms.BaseActionBarActivity
|
||||||
@ -180,13 +177,12 @@ class SeedActivity : BaseActionBarActivity(), DeviceLinkingDialogDelegate {
|
|||||||
IdentityKeyUtil.generateIdentityKeyPair(this, seed)
|
IdentityKeyUtil.generateIdentityKeyPair(this, seed)
|
||||||
}
|
}
|
||||||
val keyPair = IdentityKeyUtil.getIdentityKeyPair(this)
|
val keyPair = IdentityKeyUtil.getIdentityKeyPair(this)
|
||||||
val publicKey = keyPair.publicKey
|
val userHexEncodedPublicKey = keyPair.hexEncodedPublicKey
|
||||||
val hexEncodedPublicKey = keyPair.hexEncodedPublicKey
|
|
||||||
val registrationID = KeyHelper.generateRegistrationId(false)
|
val registrationID = KeyHelper.generateRegistrationId(false)
|
||||||
TextSecurePreferences.setLocalRegistrationId(this, registrationID)
|
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)
|
IdentityDatabase.VerifiedStatus.VERIFIED, true, System.currentTimeMillis(), true)
|
||||||
TextSecurePreferences.setLocalNumber(this, hexEncodedPublicKey)
|
TextSecurePreferences.setLocalNumber(this, userHexEncodedPublicKey)
|
||||||
when (mode) {
|
when (mode) {
|
||||||
Mode.Register -> Analytics.shared.track("Seed Created")
|
Mode.Register -> Analytics.shared.track("Seed Created")
|
||||||
Mode.Restore -> Analytics.shared.track("Seed Restored")
|
Mode.Restore -> Analytics.shared.track("Seed Restored")
|
||||||
@ -195,23 +191,21 @@ class SeedActivity : BaseActionBarActivity(), DeviceLinkingDialogDelegate {
|
|||||||
if (mode == Mode.Link) {
|
if (mode == Mode.Link) {
|
||||||
TextSecurePreferences.setHasSeenWelcomeScreen(this, true)
|
TextSecurePreferences.setHasSeenWelcomeScreen(this, true)
|
||||||
TextSecurePreferences.setPromptedPushRegistration(this, true)
|
TextSecurePreferences.setPromptedPushRegistration(this, true)
|
||||||
val primaryDevicePublicKey = publicKeyEditText.text.trim().toString()
|
val masterHexEncodedPublicKey = publicKeyEditText.text.trim().toString()
|
||||||
val authorisation = PairingAuthorisation(primaryDevicePublicKey, hexEncodedPublicKey).sign(PairingAuthorisation.Type.REQUEST, keyPair.privateKey.serialize())
|
val authorisation = PairingAuthorisation(masterHexEncodedPublicKey, userHexEncodedPublicKey).sign(PairingAuthorisation.Type.REQUEST, keyPair.privateKey.serialize())
|
||||||
if (authorisation == null) {
|
if (authorisation == null) {
|
||||||
Log.d("Loki", "Failed to sign outgoing pairing request.")
|
Log.d("Loki", "Failed to sign pairing request.")
|
||||||
resetForRegistration()
|
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)
|
val application = ApplicationContext.getInstance(this)
|
||||||
application.startLongPollingIfNeeded()
|
application.startLongPollingIfNeeded()
|
||||||
application.setUpP2PAPI()
|
application.setUpP2PAPI()
|
||||||
application.setUpStorageAPIIfNeeded()
|
application.setUpStorageAPIIfNeeded()
|
||||||
DeviceLinkingDialog.show(this, DeviceLinkingView.Mode.Slave, this)
|
DeviceLinkingDialog.show(this, DeviceLinkingView.Mode.Slave, this)
|
||||||
CoroutineScope(Dispatchers.Main).launch {
|
|
||||||
retryIfNeeded(8) {
|
retryIfNeeded(8) {
|
||||||
sendPairingAuthorisationMessage(this@SeedActivity, authorisation.primaryDevicePublicKey, authorisation).get()
|
sendPairingAuthorisationMessage(this@SeedActivity, authorisation.primaryDevicePublicKey, authorisation).get()
|
||||||
}
|
}
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
startActivity(Intent(this, DisplayNameActivity::class.java))
|
startActivity(Intent(this, DisplayNameActivity::class.java))
|
||||||
finish()
|
finish()
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
@ -100,7 +100,7 @@ public class ProfilePreference extends Preference {
|
|||||||
int height = avatarView.getHeight();
|
int height = avatarView.getHeight();
|
||||||
if (width == 0 || height == 0) return true;
|
if (width == 0 || height == 0) return true;
|
||||||
avatarView.getViewTreeObserver().removeOnPreDrawListener(this);
|
avatarView.getViewTreeObserver().removeOnPreDrawListener(this);
|
||||||
JazzIdenticonDrawable identicon = new JazzIdenticonDrawable(width, height, userHexEncodedPublicKey);
|
JazzIdenticonDrawable identicon = new JazzIdenticonDrawable(width, height, userHexEncodedPublicKey.toLowerCase());
|
||||||
avatarView.setImageDrawable(identicon);
|
avatarView.setImageDrawable(identicon);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user