mirror of
https://github.com/oxen-io/session-android.git
synced 2025-12-12 14:55:06 +00:00
Merge remote-tracking branch 'upstream/ui' into ui
# Conflicts: # app/src/main/java/org/thoughtcrime/securesms/components/AlbumThumbnailView.java # app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt # app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageContentView.kt
This commit is contained in:
@@ -26,7 +26,7 @@ import android.widget.TextView;
|
||||
import com.codewaves.stickyheadergrid.StickyHeaderGridAdapter;
|
||||
|
||||
|
||||
import org.thoughtcrime.securesms.components.ThumbnailView;
|
||||
import org.thoughtcrime.securesms.conversation.v2.utilities.ThumbnailView;
|
||||
import org.thoughtcrime.securesms.database.MediaDatabase.MediaRecord;
|
||||
import org.thoughtcrime.securesms.database.loaders.BucketedThreadMediaLoader.BucketedThreadMedia;
|
||||
import org.thoughtcrime.securesms.mms.GlideRequests;
|
||||
|
||||
@@ -12,7 +12,7 @@ import android.widget.TextView;
|
||||
|
||||
import network.loki.messenger.R;
|
||||
|
||||
import org.thoughtcrime.securesms.components.v2.ThumbnailView;
|
||||
import org.thoughtcrime.securesms.conversation.v2.utilities.ThumbnailView;
|
||||
import org.thoughtcrime.securesms.mms.GlideRequests;
|
||||
|
||||
import org.thoughtcrime.securesms.mms.Slide;
|
||||
|
||||
@@ -12,6 +12,7 @@ import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.UiThread;
|
||||
|
||||
import org.thoughtcrime.securesms.conversation.v2.utilities.ThumbnailView;
|
||||
import org.thoughtcrime.securesms.mms.GlideRequests;
|
||||
|
||||
import org.thoughtcrime.securesms.mms.Slide;
|
||||
@@ -27,7 +28,7 @@ import network.loki.messenger.R;
|
||||
|
||||
public class ConversationItemThumbnail extends FrameLayout {
|
||||
|
||||
private ThumbnailView thumbnail;
|
||||
private ThumbnailView thumbnail;
|
||||
private AlbumThumbnailView album;
|
||||
private ImageView shade;
|
||||
private ConversationItemFooter footer;
|
||||
|
||||
@@ -8,6 +8,7 @@ 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;
|
||||
|
||||
@@ -5,6 +5,7 @@ import android.graphics.Canvas;
|
||||
import android.util.AttributeSet;
|
||||
|
||||
import org.session.libsession.utilities.ThemeUtil;
|
||||
import org.thoughtcrime.securesms.conversation.v2.utilities.ThumbnailView;
|
||||
|
||||
import network.loki.messenger.R;
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
import android.widget.FrameLayout;
|
||||
|
||||
import org.thoughtcrime.securesms.conversation.v2.utilities.ThumbnailView;
|
||||
import org.thoughtcrime.securesms.mms.GlideRequests;
|
||||
|
||||
import org.thoughtcrime.securesms.mms.Slide;
|
||||
|
||||
@@ -11,7 +11,6 @@ import android.text.SpannableStringBuilder;
|
||||
import android.text.TextUtils;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.TypedValue;
|
||||
|
||||
import network.loki.messenger.R;
|
||||
import org.thoughtcrime.securesms.components.emoji.EmojiProvider.EmojiDrawable;
|
||||
import org.thoughtcrime.securesms.components.emoji.parsing.EmojiParser;
|
||||
@@ -19,9 +18,7 @@ import org.session.libsession.utilities.TextSecurePreferences;
|
||||
import org.session.libsession.utilities.Util;
|
||||
import org.session.libsignal.utilities.guava.Optional;
|
||||
|
||||
|
||||
public class EmojiTextView extends AppCompatTextView {
|
||||
|
||||
private final boolean scaleEmojis;
|
||||
|
||||
private static final char ELLIPSIS = '…';
|
||||
@@ -46,14 +43,9 @@ public class EmojiTextView extends AppCompatTextView {
|
||||
public EmojiTextView(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
|
||||
TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.EmojiTextView, 0, 0);
|
||||
scaleEmojis = a.getBoolean(R.styleable.EmojiTextView_scaleEmojis, false);
|
||||
maxLength = a.getInteger(R.styleable.EmojiTextView_emoji_maxLength, -1);
|
||||
a.recycle();
|
||||
|
||||
a = context.obtainStyledAttributes(attrs, new int[]{android.R.attr.textSize});
|
||||
originalFontSize = a.getDimensionPixelSize(0, 0);
|
||||
a.recycle();
|
||||
scaleEmojis = true;
|
||||
maxLength = 1000;
|
||||
originalFontSize = getResources().getDimension(R.dimen.small_font_size);
|
||||
}
|
||||
|
||||
@Override public void setText(@Nullable CharSequence text, BufferType type) {
|
||||
@@ -182,8 +174,11 @@ public class EmojiTextView extends AppCompatTextView {
|
||||
|
||||
@Override
|
||||
public void invalidateDrawable(@NonNull Drawable drawable) {
|
||||
if (drawable instanceof EmojiDrawable) invalidate();
|
||||
else super.invalidateDrawable(drawable);
|
||||
if (drawable instanceof EmojiDrawable) {
|
||||
invalidate();
|
||||
} else {
|
||||
super.invalidateDrawable(drawable);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -5,25 +5,35 @@ import android.animation.ValueAnimator
|
||||
import android.content.res.Resources
|
||||
import android.database.Cursor
|
||||
import android.graphics.Rect
|
||||
import android.graphics.Typeface
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.util.TypedValue
|
||||
import android.view.*
|
||||
import android.widget.RelativeLayout
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.lifecycle.ViewModelProviders
|
||||
import androidx.loader.app.LoaderManager
|
||||
import androidx.loader.content.Loader
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import kotlinx.android.synthetic.main.activity_conversation_v2.*
|
||||
import kotlinx.android.synthetic.main.activity_conversation_v2.view.*
|
||||
import kotlinx.android.synthetic.main.activity_conversation_v2_action_bar.*
|
||||
import kotlinx.android.synthetic.main.activity_home.*
|
||||
import kotlinx.android.synthetic.main.view_conversation.view.*
|
||||
import kotlinx.android.synthetic.main.view_input_bar.view.*
|
||||
import kotlinx.android.synthetic.main.view_input_bar_recording.*
|
||||
import kotlinx.android.synthetic.main.view_input_bar_recording.view.*
|
||||
import network.loki.messenger.R
|
||||
import nl.komponents.kovenant.ui.successUi
|
||||
import org.session.libsession.messaging.contacts.Contact
|
||||
import org.session.libsession.messaging.mentions.MentionsManager
|
||||
import org.session.libsession.messaging.open_groups.OpenGroupAPIV2
|
||||
import org.session.libsession.utilities.TextSecurePreferences
|
||||
import org.thoughtcrime.securesms.ApplicationContext
|
||||
import org.thoughtcrime.securesms.ApplicationContext
|
||||
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity
|
||||
import org.thoughtcrime.securesms.conversation.v2.dialogs.*
|
||||
import org.thoughtcrime.securesms.conversation.v2.input_bar.InputBarButton
|
||||
import org.thoughtcrime.securesms.conversation.v2.input_bar.InputBarDelegate
|
||||
import org.thoughtcrime.securesms.conversation.v2.input_bar.InputBarRecordingViewDelegate
|
||||
@@ -35,8 +45,13 @@ import org.thoughtcrime.securesms.database.DatabaseFactory
|
||||
import org.thoughtcrime.securesms.database.DraftDatabase
|
||||
import org.thoughtcrime.securesms.database.DraftDatabase.Drafts
|
||||
import org.thoughtcrime.securesms.database.model.MessageRecord
|
||||
import org.thoughtcrime.securesms.linkpreview.LinkPreviewRepository
|
||||
import org.thoughtcrime.securesms.linkpreview.LinkPreviewViewModel
|
||||
import org.thoughtcrime.securesms.linkpreview.LinkPreviewViewModel.LinkPreviewState
|
||||
import org.thoughtcrime.securesms.loki.utilities.toPx
|
||||
import org.thoughtcrime.securesms.mms.GlideApp
|
||||
import org.thoughtcrime.securesms.util.DateUtils
|
||||
import java.util.*
|
||||
import kotlin.math.*
|
||||
|
||||
class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDelegate,
|
||||
@@ -44,6 +59,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
|
||||
private val scrollButtonFullVisibilityThreshold by lazy { toPx(120.0f, resources) }
|
||||
private val scrollButtonNoVisibilityThreshold by lazy { toPx(20.0f, resources) }
|
||||
private val screenWidth = Resources.getSystem().displayMetrics.widthPixels
|
||||
private var linkPreviewViewModel: LinkPreviewViewModel? = null
|
||||
private var threadID: Long = -1
|
||||
private var actionMode: ActionMode? = null
|
||||
private var isLockViewExpanded = false
|
||||
@@ -52,7 +68,6 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
|
||||
|
||||
// TODO: Selected message background color
|
||||
// TODO: Overflow menu background + text color
|
||||
// TODO: Typing indicators
|
||||
|
||||
private val adapter by lazy {
|
||||
val cursor = DatabaseFactory.getMmsSmsDatabase(this).getConversation(threadID)
|
||||
@@ -101,6 +116,12 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
|
||||
restoreDraftIfNeeded()
|
||||
addOpenGroupGuidelinesIfNeeded()
|
||||
scrollToBottomButton.setOnClickListener { conversationRecyclerView.smoothScrollToPosition(0) }
|
||||
updateUnreadCount()
|
||||
setUpTypingObserver()
|
||||
updateSubtitle()
|
||||
getLatestOpenGroupInfoIfNeeded()
|
||||
setUpBlockedBanner()
|
||||
setUpLinkPreviewObserver()
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
@@ -180,6 +201,47 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
|
||||
conversationRecyclerView.layoutParams = recyclerViewLayoutParams
|
||||
}
|
||||
|
||||
private fun setUpTypingObserver() {
|
||||
ApplicationContext.getInstance(this).typingStatusRepository.getTypists(threadID).observe(this) { state ->
|
||||
val recipients = if (state != null) state.typists else listOf()
|
||||
typingIndicatorViewContainer.isVisible = recipients.isNotEmpty()
|
||||
typingIndicatorViewContainer.setTypists(recipients)
|
||||
inputBarHeightChanged(inputBar.height)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getLatestOpenGroupInfoIfNeeded() {
|
||||
val openGroup = DatabaseFactory.getLokiThreadDatabase(this).getOpenGroupChat(threadID) ?: return
|
||||
OpenGroupAPIV2.getMemberCount(openGroup.room, openGroup.server).successUi { updateSubtitle() }
|
||||
}
|
||||
|
||||
private fun setUpBlockedBanner() {
|
||||
if (thread.isGroupRecipient) { return }
|
||||
val contactDB = DatabaseFactory.getSessionContactDatabase(this)
|
||||
val sessionID = thread.address.toString()
|
||||
val contact = contactDB.getContactWithSessionID(sessionID)
|
||||
val name = contact?.displayName(Contact.ContactContext.REGULAR) ?: sessionID
|
||||
blockedBannerTextView.text = resources.getString(R.string.activity_conversation_blocked_banner_text, name)
|
||||
blockedBanner.isVisible = thread.isBlocked
|
||||
blockedBanner.setOnClickListener { unblock() }
|
||||
}
|
||||
|
||||
private fun setUpLinkPreviewObserver() {
|
||||
val linkPreviewViewModel = ViewModelProviders.of(this, LinkPreviewViewModel.Factory(LinkPreviewRepository(this)))[LinkPreviewViewModel::class.java]
|
||||
this.linkPreviewViewModel = linkPreviewViewModel
|
||||
if (!TextSecurePreferences.isLinkPreviewsEnabled(this)) {
|
||||
linkPreviewViewModel.onUserCancel(); return
|
||||
}
|
||||
linkPreviewViewModel.linkPreviewState.observe(this, { previewState: LinkPreviewState? ->
|
||||
if (previewState == null) return@observe
|
||||
if (previewState.isLoading) {
|
||||
inputBar.draftLinkPreview()
|
||||
} else {
|
||||
inputBar.updateLinkPreviewDraft(glide, previewState.linkPreview.get())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
override fun onPrepareOptionsMenu(menu: Menu): Boolean {
|
||||
ConversationMenuHelper.onPrepareOptionsMenu(menu, menuInflater, thread, this) { onOptionsItemSelected(it) }
|
||||
super.onPrepareOptionsMenu(menu)
|
||||
@@ -194,9 +256,13 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
|
||||
|
||||
// region Updating & Animation
|
||||
override fun inputBarHeightChanged(newValue: Int) {
|
||||
@Suppress("NAME_SHADOWING") val newValue = max(newValue, resources.getDimension(R.dimen.input_bar_height).roundToInt())
|
||||
// 36 DP is the exact height of the typing indicator view. It's also exactly 18 * 2, and 18 is the large message
|
||||
// corner radius. This makes 36 DP look "correct" in the context of other messages on the screen.
|
||||
val typingIndicatorHeight = if (typingIndicatorViewContainer.isVisible) toPx(36, resources) else 0
|
||||
// Recycler view
|
||||
val recyclerViewLayoutParams = conversationRecyclerView.layoutParams as RelativeLayout.LayoutParams
|
||||
recyclerViewLayoutParams.bottomMargin = newValue + additionalContentContainer.height
|
||||
recyclerViewLayoutParams.bottomMargin = newValue + additionalContentContainer.height + typingIndicatorHeight
|
||||
conversationRecyclerView.layoutParams = recyclerViewLayoutParams
|
||||
// Additional content container
|
||||
val additionalContentContainerLayoutParams = additionalContentContainer.layoutParams as RelativeLayout.LayoutParams
|
||||
@@ -216,6 +282,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
|
||||
}
|
||||
|
||||
override fun inputBarEditTextContentChanged(newContent: CharSequence) {
|
||||
linkPreviewViewModel?.onTextChanged(this, inputBar.text, 0, 0)
|
||||
// TODO: Implement the full mention show/hide logic
|
||||
if (newContent.contains("@")) {
|
||||
showMentionCandidates()
|
||||
@@ -330,6 +397,35 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
|
||||
(scrollButtonFullVisibilityThreshold - scrollButtonNoVisibilityThreshold)
|
||||
val alpha = max(min(rawAlpha, 1.0f), 0.0f)
|
||||
scrollToBottomButton.alpha = alpha
|
||||
updateUnreadCount()
|
||||
}
|
||||
|
||||
private fun updateUnreadCount() {
|
||||
val unreadCount = DatabaseFactory.getMmsSmsDatabase(this).getUnreadCount(threadID)
|
||||
val formattedUnreadCount = if (unreadCount < 100) unreadCount.toString() else "99+"
|
||||
unreadCountTextView.text = formattedUnreadCount
|
||||
val textSize = if (unreadCount < 100) 12.0f else 9.0f
|
||||
unreadCountTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, textSize)
|
||||
unreadCountTextView.setTypeface(Typeface.DEFAULT, if (unreadCount < 100) Typeface.BOLD else Typeface.NORMAL)
|
||||
unreadCountIndicator.isVisible = (unreadCount != 0)
|
||||
}
|
||||
|
||||
private fun updateSubtitle() {
|
||||
muteIconImageView.isVisible = thread.isMuted
|
||||
conversationSubtitleView.isVisible = true
|
||||
if (thread.isMuted) {
|
||||
conversationSubtitleView.text = getString(R.string.ConversationActivity_muted_until_date, DateUtils.getFormattedDateTime(thread.mutedUntil, "EEE, MMM d, yyyy HH:mm", Locale.getDefault()))
|
||||
} else if (thread.isGroupRecipient) {
|
||||
val openGroup = DatabaseFactory.getLokiThreadDatabase(this).getOpenGroupChat(threadID)
|
||||
if (openGroup != null) {
|
||||
val userCount = DatabaseFactory.getLokiAPIDatabase(this).getUserCount(openGroup.room, openGroup.server) ?: 0
|
||||
conversationSubtitleView.text = getString(R.string.ConversationActivity_member_count, userCount)
|
||||
} else {
|
||||
conversationSubtitleView.isVisible = false
|
||||
}
|
||||
} else {
|
||||
conversationSubtitleView.isVisible = false
|
||||
}
|
||||
}
|
||||
// endregion
|
||||
|
||||
@@ -351,6 +447,10 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
|
||||
this.actionMode = null
|
||||
}
|
||||
} else {
|
||||
// NOTE:
|
||||
// We have to use onContentClick (rather than a click listener directly on
|
||||
// the view) so as to not interfere with all the other gestures. Do not add
|
||||
// onClickListeners directly to message content views.
|
||||
view.onContentClick()
|
||||
}
|
||||
}
|
||||
@@ -432,6 +532,10 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
|
||||
lockViewLocation[0] + lockView.width + lockViewHitMargin, lockViewLocation[1] + lockView.height)
|
||||
return hitRect.contains(x, y)
|
||||
}
|
||||
|
||||
private fun unblock() {
|
||||
// TODO: Implement
|
||||
}
|
||||
// endregion
|
||||
|
||||
// region General
|
||||
|
||||
@@ -5,6 +5,7 @@ import android.util.AttributeSet
|
||||
import android.util.Log
|
||||
import android.view.MotionEvent
|
||||
import android.view.VelocityTracker
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import kotlinx.android.synthetic.main.activity_conversation_v2.*
|
||||
import org.thoughtcrime.securesms.loki.utilities.disableClipping
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
package org.thoughtcrime.securesms.conversation.v2.components
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import android.view.LayoutInflater
|
||||
import android.widget.LinearLayout
|
||||
import androidx.core.view.isVisible
|
||||
import kotlinx.android.synthetic.main.view_link_preview_draft.view.*
|
||||
import network.loki.messenger.R
|
||||
import org.session.libsession.messaging.sending_receiving.link_preview.LinkPreview
|
||||
import org.thoughtcrime.securesms.mms.GlideRequests
|
||||
import org.thoughtcrime.securesms.mms.ImageSlide
|
||||
|
||||
class LinkPreviewDraftView : LinearLayout {
|
||||
|
||||
// region Lifecycle
|
||||
constructor(context: Context) : super(context) { initialize() }
|
||||
constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { initialize() }
|
||||
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) { initialize() }
|
||||
|
||||
private fun initialize() {
|
||||
LayoutInflater.from(context).inflate(R.layout.view_link_preview_draft, this)
|
||||
linkPreviewDraftContainer.isVisible = false
|
||||
thumbnailImageView.clipToOutline = true
|
||||
}
|
||||
// endregion
|
||||
|
||||
// region Updating
|
||||
fun update(glide: GlideRequests, linkPreview: LinkPreview) {
|
||||
linkPreviewDraftContainer.isVisible = true
|
||||
linkPreviewDraftLoader.isVisible = false
|
||||
if (linkPreview.getThumbnail().isPresent) {
|
||||
thumbnailImageView.setImageResource(glide, ImageSlide(context, linkPreview.getThumbnail().get()), false, false)
|
||||
}
|
||||
linkPreviewDraftTitleTextView.text = linkPreview.title
|
||||
}
|
||||
// endregion
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package org.thoughtcrime.securesms.conversation.v2
|
||||
package org.thoughtcrime.securesms.conversation.v2.components
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
@@ -7,6 +7,8 @@ import android.view.LayoutInflater
|
||||
import android.widget.FrameLayout
|
||||
import kotlinx.android.synthetic.main.view_open_group_guidelines.view.*
|
||||
import network.loki.messenger.R
|
||||
import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2
|
||||
import org.thoughtcrime.securesms.loki.activities.OpenGroupGuidelinesActivity
|
||||
import org.thoughtcrime.securesms.loki.utilities.push
|
||||
|
||||
class OpenGroupGuidelinesView : FrameLayout {
|
||||
@@ -1,4 +1,4 @@
|
||||
package org.thoughtcrime.securesms.components;
|
||||
package org.thoughtcrime.securesms.conversation.v2.components;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
@@ -13,18 +13,14 @@ import android.widget.LinearLayout;
|
||||
import network.loki.messenger.R;
|
||||
|
||||
public class TypingIndicatorView extends LinearLayout {
|
||||
private boolean isActive;
|
||||
private long startTime;
|
||||
|
||||
private static final long DURATION = 300;
|
||||
private static final long PRE_DELAY = 500;
|
||||
private static final long POST_DELAY = 500;
|
||||
private static final long CYCLE_DURATION = 1500;
|
||||
private static final long DOT_DURATION = 600;
|
||||
private static final float MIN_ALPHA = 0.4f;
|
||||
private static final float MIN_SCALE = 0.75f;
|
||||
|
||||
private boolean isActive;
|
||||
private long startTime;
|
||||
|
||||
private View dot1;
|
||||
private View dot2;
|
||||
private View dot3;
|
||||
@@ -40,7 +36,7 @@ public class TypingIndicatorView extends LinearLayout {
|
||||
}
|
||||
|
||||
private void initialize(@Nullable AttributeSet attrs) {
|
||||
inflate(getContext(), R.layout.typing_indicator_view, this);
|
||||
inflate(getContext(), R.layout.view_typing_indicator, this);
|
||||
|
||||
setWillNotDraw(false);
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
package org.thoughtcrime.securesms.conversation.v2.components
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import android.view.LayoutInflater
|
||||
import android.widget.LinearLayout
|
||||
import kotlinx.android.synthetic.main.view_conversation_typing_container.view.*
|
||||
import network.loki.messenger.R
|
||||
import org.session.libsession.utilities.recipients.Recipient
|
||||
|
||||
class TypingIndicatorViewContainer : LinearLayout {
|
||||
|
||||
constructor(context: Context) : super(context) { initialize() }
|
||||
constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { initialize() }
|
||||
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) { initialize() }
|
||||
|
||||
private fun initialize() {
|
||||
LayoutInflater.from(context).inflate(R.layout.view_conversation_typing_container, this)
|
||||
}
|
||||
|
||||
fun setTypists(typists: List<Recipient>) {
|
||||
if (typists.isEmpty()) { typingIndicator.stopAnimation(); return }
|
||||
typingIndicator.startAnimation()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package org.thoughtcrime.securesms.conversation.v2.dialogs
|
||||
|
||||
import android.graphics.Typeface
|
||||
import android.text.Spannable
|
||||
import android.text.SpannableStringBuilder
|
||||
import android.text.style.StyleSpan
|
||||
import android.view.LayoutInflater
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import kotlinx.android.synthetic.main.dialog_blocked.view.*
|
||||
import kotlinx.android.synthetic.main.dialog_blocked.view.cancelButton
|
||||
import network.loki.messenger.R
|
||||
import org.session.libsession.messaging.contacts.Contact
|
||||
import org.session.libsession.utilities.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.conversation.v2.utilities.BaseDialog
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory
|
||||
|
||||
class BlockedDialog(private val recipient: Recipient) : BaseDialog() {
|
||||
|
||||
override fun setContentView(builder: AlertDialog.Builder) {
|
||||
val contentView = LayoutInflater.from(requireContext()).inflate(R.layout.dialog_blocked, null)
|
||||
val contactDB = DatabaseFactory.getSessionContactDatabase(requireContext())
|
||||
val sessionID = recipient.address.toString()
|
||||
val contact = contactDB.getContactWithSessionID(sessionID)
|
||||
val name = contact?.displayName(Contact.ContactContext.REGULAR) ?: sessionID
|
||||
val title = resources.getString(R.string.dialog_blocked_title, name)
|
||||
contentView.blockedTitleTextView.text = title
|
||||
val explanation = resources.getString(R.string.dialog_blocked_explanation, name)
|
||||
val spannable = SpannableStringBuilder(explanation)
|
||||
val startIndex = explanation.indexOf(name)
|
||||
spannable.setSpan(StyleSpan(Typeface.BOLD), startIndex, startIndex + name.count(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
|
||||
contentView.blockedExplanationTextView.text = spannable
|
||||
contentView.cancelButton.setOnClickListener { dismiss() }
|
||||
contentView.unblockButton.setOnClickListener { unblock() }
|
||||
builder.setView(contentView)
|
||||
}
|
||||
|
||||
private fun unblock() {
|
||||
// TODO: Implement
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package org.thoughtcrime.securesms.conversation.v2.dialogs
|
||||
|
||||
import android.graphics.Typeface
|
||||
import android.text.Spannable
|
||||
import android.text.SpannableStringBuilder
|
||||
import android.text.style.StyleSpan
|
||||
import android.view.LayoutInflater
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import kotlinx.android.synthetic.main.dialog_download.view.*
|
||||
import network.loki.messenger.R
|
||||
import org.session.libsession.messaging.contacts.Contact
|
||||
import org.session.libsession.utilities.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.conversation.v2.utilities.BaseDialog
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory
|
||||
|
||||
class DownloadDialog(private val recipient: Recipient) : BaseDialog() {
|
||||
|
||||
override fun setContentView(builder: AlertDialog.Builder) {
|
||||
val contentView = LayoutInflater.from(requireContext()).inflate(R.layout.dialog_download, null)
|
||||
val contactDB = DatabaseFactory.getSessionContactDatabase(requireContext())
|
||||
val sessionID = recipient.address.toString()
|
||||
val contact = contactDB.getContactWithSessionID(sessionID)
|
||||
val name = contact?.displayName(Contact.ContactContext.REGULAR) ?: sessionID
|
||||
val title = resources.getString(R.string.dialog_download_title, name)
|
||||
contentView.downloadTitleTextView.text = title
|
||||
val explanation = resources.getString(R.string.dialog_download_explanation, name)
|
||||
val spannable = SpannableStringBuilder(explanation)
|
||||
val startIndex = explanation.indexOf(name)
|
||||
spannable.setSpan(StyleSpan(Typeface.BOLD), startIndex, startIndex + name.count(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
|
||||
contentView.downloadExplanationTextView.text = spannable
|
||||
contentView.cancelButton.setOnClickListener { dismiss() }
|
||||
contentView.downloadButton.setOnClickListener { trust() }
|
||||
builder.setView(contentView)
|
||||
}
|
||||
|
||||
private fun trust() {
|
||||
// TODO: Implement
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package org.thoughtcrime.securesms.conversation.v2.dialogs
|
||||
|
||||
import android.graphics.Typeface
|
||||
import android.text.Spannable
|
||||
import android.text.SpannableStringBuilder
|
||||
import android.text.style.StyleSpan
|
||||
import android.view.LayoutInflater
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import kotlinx.android.synthetic.main.dialog_join_open_group.view.*
|
||||
import network.loki.messenger.R
|
||||
import org.session.libsession.messaging.open_groups.OpenGroupV2
|
||||
import org.thoughtcrime.securesms.conversation.v2.utilities.BaseDialog
|
||||
|
||||
class JoinOpenGroupDialog(private val openGroup: OpenGroupV2) : BaseDialog() {
|
||||
|
||||
override fun setContentView(builder: AlertDialog.Builder) {
|
||||
val contentView = LayoutInflater.from(requireContext()).inflate(R.layout.dialog_join_open_group, null)
|
||||
val name = openGroup.name
|
||||
val title = resources.getString(R.string.dialog_join_open_group_title, name)
|
||||
contentView.joinOpenGroupTitleTextView.text = title
|
||||
val explanation = resources.getString(R.string.dialog_join_open_group_explanation, name)
|
||||
val spannable = SpannableStringBuilder(explanation)
|
||||
val startIndex = explanation.indexOf(name)
|
||||
spannable.setSpan(StyleSpan(Typeface.BOLD), startIndex, startIndex + name.count(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
|
||||
contentView.joinOpenGroupExplanationTextView.text = spannable
|
||||
contentView.cancelButton.setOnClickListener { dismiss() }
|
||||
contentView.joinButton.setOnClickListener { join() }
|
||||
builder.setView(contentView)
|
||||
}
|
||||
|
||||
private fun join() {
|
||||
// TODO: Implement
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package org.thoughtcrime.securesms.conversation.v2.dialogs
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import kotlinx.android.synthetic.main.dialog_link_preview.view.*
|
||||
import network.loki.messenger.R
|
||||
import org.thoughtcrime.securesms.conversation.v2.utilities.BaseDialog
|
||||
|
||||
class LinkPreviewDialog() : BaseDialog() {
|
||||
|
||||
override fun setContentView(builder: AlertDialog.Builder) {
|
||||
val contentView = LayoutInflater.from(requireContext()).inflate(R.layout.dialog_link_preview, null)
|
||||
contentView.cancelButton.setOnClickListener { dismiss() }
|
||||
contentView.enableLinkPreviewsButton.setOnClickListener { enable() }
|
||||
builder.setView(contentView)
|
||||
}
|
||||
|
||||
private fun enable() {
|
||||
// TODO: Implement
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package org.thoughtcrime.securesms.conversation.v2.dialogs
|
||||
|
||||
import android.graphics.Typeface
|
||||
import android.text.Spannable
|
||||
import android.text.SpannableStringBuilder
|
||||
import android.text.style.StyleSpan
|
||||
import android.view.LayoutInflater
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import kotlinx.android.synthetic.main.dialog_open_url.view.*
|
||||
import network.loki.messenger.R
|
||||
import org.thoughtcrime.securesms.conversation.v2.utilities.BaseDialog
|
||||
|
||||
class OpenURLDialog(private val url: String) : BaseDialog() {
|
||||
|
||||
override fun setContentView(builder: AlertDialog.Builder) {
|
||||
val contentView = LayoutInflater.from(requireContext()).inflate(R.layout.dialog_open_url, null)
|
||||
val explanation = resources.getString(R.string.dialog_open_url_explanation, url)
|
||||
val spannable = SpannableStringBuilder(explanation)
|
||||
val startIndex = explanation.indexOf(url)
|
||||
spannable.setSpan(StyleSpan(Typeface.BOLD), startIndex, startIndex + url.count(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
|
||||
contentView.openURLExplanationTextView.text = spannable
|
||||
contentView.cancelButton.setOnClickListener { dismiss() }
|
||||
contentView.openURLButton.setOnClickListener { open() }
|
||||
builder.setView(contentView)
|
||||
}
|
||||
|
||||
private fun open() {
|
||||
// TODO: Implement
|
||||
}
|
||||
}
|
||||
@@ -2,24 +2,23 @@ package org.thoughtcrime.securesms.conversation.v2.input_bar
|
||||
|
||||
import android.content.Context
|
||||
import android.content.res.Resources
|
||||
import android.text.Editable
|
||||
import android.util.AttributeSet
|
||||
import android.util.Log
|
||||
import android.view.LayoutInflater
|
||||
import android.view.MotionEvent
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.RelativeLayout
|
||||
import androidx.core.view.isVisible
|
||||
import kotlinx.android.synthetic.main.view_input_bar.view.*
|
||||
import kotlinx.android.synthetic.main.view_quote.view.*
|
||||
import network.loki.messenger.R
|
||||
import org.session.libsession.messaging.sending_receiving.link_preview.LinkPreview
|
||||
import org.thoughtcrime.securesms.conversation.v2.components.LinkPreviewDraftView
|
||||
import org.thoughtcrime.securesms.conversation.v2.messages.QuoteView
|
||||
import org.thoughtcrime.securesms.conversation.v2.messages.QuoteViewDelegate
|
||||
import org.thoughtcrime.securesms.database.model.MessageRecord
|
||||
import org.thoughtcrime.securesms.database.model.MmsMessageRecord
|
||||
import org.thoughtcrime.securesms.loki.utilities.toDp
|
||||
import org.thoughtcrime.securesms.loki.utilities.toPx
|
||||
import org.thoughtcrime.securesms.mms.SlideDeck
|
||||
import org.thoughtcrime.securesms.mms.GlideRequests
|
||||
import kotlin.math.max
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
@@ -27,6 +26,7 @@ class InputBar : RelativeLayout, InputBarEditTextDelegate, QuoteViewDelegate {
|
||||
private val screenWidth = Resources.getSystem().displayMetrics.widthPixels
|
||||
private val vMargin by lazy { toDp(4, resources) }
|
||||
private val minHeight by lazy { toPx(56, resources) }
|
||||
private var linkPreviewDraftView: LinkPreviewDraftView? = null
|
||||
var delegate: InputBarDelegate? = null
|
||||
var additionalContentHeight = 0
|
||||
|
||||
@@ -96,6 +96,7 @@ class InputBar : RelativeLayout, InputBarEditTextDelegate, QuoteViewDelegate {
|
||||
}
|
||||
|
||||
fun draftQuote(message: MessageRecord) {
|
||||
linkPreviewDraftView = null
|
||||
inputBarAdditionalContentContainer.removeAllViews()
|
||||
val quoteView = QuoteView(context, QuoteView.Mode.Draft)
|
||||
quoteView.delegate = this
|
||||
@@ -121,6 +122,22 @@ class InputBar : RelativeLayout, InputBarEditTextDelegate, QuoteViewDelegate {
|
||||
additionalContentHeight = 0
|
||||
setHeight(newHeight)
|
||||
}
|
||||
|
||||
fun draftLinkPreview() {
|
||||
val linkPreviewDraftHeight = toPx(88, resources)
|
||||
inputBarAdditionalContentContainer.removeAllViews()
|
||||
val linkPreviewDraftView = LinkPreviewDraftView(context)
|
||||
this.linkPreviewDraftView = linkPreviewDraftView
|
||||
inputBarAdditionalContentContainer.addView(linkPreviewDraftView)
|
||||
val newHeight = max(inputBarEditText.height + 2 * vMargin, minHeight) + linkPreviewDraftHeight
|
||||
additionalContentHeight = linkPreviewDraftHeight
|
||||
setHeight(newHeight)
|
||||
}
|
||||
|
||||
fun updateLinkPreviewDraft(glide: GlideRequests, linkPreview: LinkPreview) {
|
||||
val linkPreviewDraftView = this.linkPreviewDraftView ?: return
|
||||
linkPreviewDraftView.update(glide, linkPreview)
|
||||
}
|
||||
// endregion
|
||||
}
|
||||
|
||||
|
||||
@@ -1,16 +1,9 @@
|
||||
package org.thoughtcrime.securesms.conversation.v2.messages
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.Outline
|
||||
import android.graphics.Path
|
||||
import android.graphics.RectF
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.os.Build
|
||||
import android.util.AttributeSet
|
||||
import android.util.Log
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewOutlineProvider
|
||||
import android.widget.LinearLayout
|
||||
import androidx.core.content.res.ResourcesCompat
|
||||
@@ -19,6 +12,7 @@ import network.loki.messenger.R
|
||||
import org.thoughtcrime.securesms.database.model.MmsMessageRecord
|
||||
import org.thoughtcrime.securesms.loki.utilities.UiModeUtilities
|
||||
import org.thoughtcrime.securesms.mms.GlideRequests
|
||||
import org.thoughtcrime.securesms.mms.ImageSlide
|
||||
|
||||
class LinkPreviewView : LinearLayout {
|
||||
|
||||
@@ -39,10 +33,9 @@ class LinkPreviewView : LinearLayout {
|
||||
mainLinkPreviewContainer.clipToOutline = true
|
||||
// Thumbnail
|
||||
val linkPreview = message.linkPreviews.first()
|
||||
// TODO: Handle downloading state
|
||||
val uri = linkPreview.thumbnail.get().dataUri ?: return
|
||||
glide.load(uri).into(thumbnailImageView)
|
||||
// TODO: Properly use glide and the actual thumbnail
|
||||
if (linkPreview.getThumbnail().isPresent) {
|
||||
thumbnailImageView.setImageResource(glide, ImageSlide(context, linkPreview.getThumbnail().get()), false, false)
|
||||
}
|
||||
// Title
|
||||
titleTextView.text = linkPreview.title
|
||||
val textColorID = if (message.isOutgoing && UiModeUtilities.isDayUiMode(context)) {
|
||||
|
||||
@@ -13,26 +13,24 @@ import androidx.annotation.DrawableRes
|
||||
import androidx.core.content.res.ResourcesCompat
|
||||
import androidx.core.graphics.BlendModeColorFilterCompat
|
||||
import androidx.core.graphics.BlendModeCompat
|
||||
import androidx.core.text.toSpannable
|
||||
import kotlinx.android.synthetic.main.view_visible_message_content.view.*
|
||||
import network.loki.messenger.R
|
||||
import org.session.libsession.utilities.ThemeUtil
|
||||
import org.session.libsession.utilities.ViewUtil
|
||||
import org.session.libsession.utilities.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.components.v2.AlbumThumbnailView
|
||||
import org.thoughtcrime.securesms.components.emoji.EmojiTextView
|
||||
import org.thoughtcrime.securesms.database.model.MessageRecord
|
||||
import org.thoughtcrime.securesms.database.model.MmsMessageRecord
|
||||
import org.thoughtcrime.securesms.loki.utilities.UiMode
|
||||
import org.thoughtcrime.securesms.loki.utilities.UiModeUtilities
|
||||
import org.thoughtcrime.securesms.loki.utilities.getColorWithID
|
||||
import org.thoughtcrime.securesms.loki.utilities.toPx
|
||||
import org.thoughtcrime.securesms.loki.utilities.*
|
||||
import org.thoughtcrime.securesms.loki.utilities.MentionUtilities.highlightMentions
|
||||
import org.thoughtcrime.securesms.mms.GlideRequests
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
class VisibleMessageContentView : LinearLayout {
|
||||
var onContentClick: (() -> Unit)? = null
|
||||
|
||||
// TODO: Large emojis
|
||||
|
||||
// region Lifecycle
|
||||
constructor(context: Context) : super(context) { initialize() }
|
||||
constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { initialize() }
|
||||
@@ -124,16 +122,18 @@ class VisibleMessageContentView : LinearLayout {
|
||||
companion object {
|
||||
|
||||
fun getBodyTextView(context: Context, message: MessageRecord): TextView {
|
||||
val result = TextView(context)
|
||||
val result = EmojiTextView(context)
|
||||
val vPadding = context.resources.getDimension(R.dimen.small_spacing).toInt()
|
||||
val hPadding = toPx(12, context.resources)
|
||||
result.setPadding(hPadding, vPadding, hPadding, vPadding)
|
||||
result.text = message.body
|
||||
result.setTextSize(TypedValue.COMPLEX_UNIT_PX, context.resources.getDimension(R.dimen.small_font_size))
|
||||
val color = getTextColor(context, message)
|
||||
result.setTextColor(color)
|
||||
result.setLinkTextColor(color)
|
||||
Linkify.addLinks(result, Linkify.WEB_URLS)
|
||||
var body = message.body.toSpannable()
|
||||
Linkify.addLinks(body, Linkify.WEB_URLS)
|
||||
body = MentionUtilities.highlightMentions(body, message.isOutgoing, message.threadId, context);
|
||||
result.text = body
|
||||
return result
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
package org.thoughtcrime.securesms.conversation.v2.utilities
|
||||
|
||||
import android.app.Dialog
|
||||
import android.graphics.Color
|
||||
import android.graphics.drawable.ColorDrawable
|
||||
import android.os.Bundle
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import org.thoughtcrime.securesms.loki.utilities.UiModeUtilities
|
||||
|
||||
open class BaseDialog : DialogFragment() {
|
||||
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
val builder = AlertDialog.Builder(requireContext())
|
||||
setContentView(builder)
|
||||
val result = builder.create()
|
||||
result.window?.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
|
||||
val isLightMode = UiModeUtilities.isDayUiMode(requireContext())
|
||||
result.window?.setDimAmount(if (isLightMode) 0.1f else 0.75f)
|
||||
return result
|
||||
}
|
||||
|
||||
open fun setContentView(builder: AlertDialog.Builder) {
|
||||
// To be overridden by subclasses
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package org.thoughtcrime.securesms.components;
|
||||
package org.thoughtcrime.securesms.conversation.v2.utilities;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
@@ -24,6 +24,9 @@ import com.bumptech.glide.request.RequestOptions;
|
||||
|
||||
import network.loki.messenger.R;
|
||||
|
||||
import org.thoughtcrime.securesms.components.GlideBitmapListeningTarget;
|
||||
import org.thoughtcrime.securesms.components.GlideDrawableListeningTarget;
|
||||
import org.thoughtcrime.securesms.components.TransferControlView;
|
||||
import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader.DecryptableUri;
|
||||
import org.thoughtcrime.securesms.mms.GlideRequest;
|
||||
import org.thoughtcrime.securesms.mms.GlideRequests;
|
||||
@@ -94,10 +97,9 @@ public class ThumbnailView extends FrameLayout {
|
||||
bounds[MAX_WIDTH] = typedArray.getDimensionPixelSize(R.styleable.ThumbnailView_maxWidth, 0);
|
||||
bounds[MIN_HEIGHT] = typedArray.getDimensionPixelSize(R.styleable.ThumbnailView_minHeight, 0);
|
||||
bounds[MAX_HEIGHT] = typedArray.getDimensionPixelSize(R.styleable.ThumbnailView_maxHeight, 0);
|
||||
radius = typedArray.getDimensionPixelSize(R.styleable.ThumbnailView_thumbnail_radius, getResources().getDimensionPixelSize(R.dimen.thumbnail_default_radius));
|
||||
typedArray.recycle();
|
||||
} else {
|
||||
radius = getResources().getDimensionPixelSize(R.dimen.message_corner_collapse_radius);
|
||||
radius = 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -275,7 +277,7 @@ public class ThumbnailView extends FrameLayout {
|
||||
|
||||
this.slide = slide;
|
||||
|
||||
this.captionIcon.setVisibility(slide.getCaption().isPresent() ? VISIBLE : GONE);
|
||||
this.captionIcon.setVisibility(GONE);
|
||||
|
||||
dimens[WIDTH] = naturalWidth;
|
||||
dimens[HEIGHT] = naturalHeight;
|
||||
@@ -398,6 +400,7 @@ public class ThumbnailView extends FrameLayout {
|
||||
}
|
||||
|
||||
private class ThumbnailClickDispatcher implements View.OnClickListener {
|
||||
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
if (thumbnailClickListener != null &&
|
||||
@@ -413,9 +416,9 @@ public class ThumbnailView extends FrameLayout {
|
||||
}
|
||||
|
||||
private class DownloadClickDispatcher implements View.OnClickListener {
|
||||
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
Log.i(TAG, "onClick() for download button");
|
||||
if (downloadClickListener != null && slide != null) {
|
||||
downloadClickListener.onClick(view, Collections.singletonList(slide));
|
||||
} else {
|
||||
@@ -95,7 +95,7 @@ public class LinkPreviewRepository implements InjectableType {
|
||||
|
||||
private @NonNull RequestController fetchMetadata(@NonNull String url, Callback<Metadata> callback) {
|
||||
Call call = client.newCall(new Request.Builder().url(url).removeHeader("User-Agent").addHeader("User-Agent",
|
||||
"WhatsApp").cacheControl(NO_CACHE).build());
|
||||
"WhatsApp").cacheControl(NO_CACHE).build());
|
||||
|
||||
call.enqueue(new okhttp3.Callback() {
|
||||
@Override
|
||||
@@ -186,18 +186,18 @@ public class LinkPreviewRepository implements InjectableType {
|
||||
byte[] bytes = baos.toByteArray();
|
||||
Uri uri = BlobProvider.getInstance().forData(bytes).createForSingleSessionInMemory();
|
||||
|
||||
return Optional.of(new UriAttachment(uri,
|
||||
uri,
|
||||
contentType,
|
||||
AttachmentTransferProgress.TRANSFER_PROGRESS_STARTED,
|
||||
bytes.length,
|
||||
bitmap.getWidth(),
|
||||
bitmap.getHeight(),
|
||||
null,
|
||||
null,
|
||||
false,
|
||||
false,
|
||||
null));
|
||||
return Optional.of(new UriAttachment(uri,
|
||||
uri,
|
||||
contentType,
|
||||
AttachmentTransferProgress.TRANSFER_PROGRESS_STARTED,
|
||||
bytes.length,
|
||||
bitmap.getWidth(),
|
||||
bitmap.getHeight(),
|
||||
null,
|
||||
null,
|
||||
false,
|
||||
false,
|
||||
null));
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -74,7 +74,7 @@ public class LinkPreviewViewModel extends ViewModel {
|
||||
activeRequest = null;
|
||||
}
|
||||
|
||||
if (!link.isPresent() || !isCursorPositionValid(text, link.get(), cursorStart, cursorEnd)) {
|
||||
if (!link.isPresent()) {
|
||||
activeUrl = null;
|
||||
linkPreviewState.setValue(LinkPreviewState.forEmpty());
|
||||
return;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package org.thoughtcrime.securesms.conversation.v2
|
||||
package org.thoughtcrime.securesms.loki.activities
|
||||
|
||||
import android.os.Bundle
|
||||
import kotlinx.android.synthetic.main.activity_open_group_guidelines.*
|
||||
@@ -12,18 +12,16 @@ import network.loki.messenger.R
|
||||
import org.thoughtcrime.securesms.ApplicationContext
|
||||
import org.thoughtcrime.securesms.loki.protocol.MultiDeviceProtocol
|
||||
import org.session.libsession.utilities.KeyPairUtilities
|
||||
import org.thoughtcrime.securesms.conversation.v2.utilities.BaseDialog
|
||||
import org.thoughtcrime.securesms.loki.utilities.UiModeUtilities
|
||||
|
||||
class ClearAllDataDialog : DialogFragment() {
|
||||
class ClearAllDataDialog : BaseDialog() {
|
||||
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
val builder = AlertDialog.Builder(requireContext())
|
||||
override fun setContentView(builder: AlertDialog.Builder) {
|
||||
val contentView = LayoutInflater.from(requireContext()).inflate(R.layout.dialog_clear_all_data, null)
|
||||
contentView.cancelButton.setOnClickListener { dismiss() }
|
||||
contentView.clearAllDataButton.setOnClickListener { clearAllData() }
|
||||
builder.setView(contentView)
|
||||
val result = builder.create()
|
||||
result.window?.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
|
||||
return result
|
||||
}
|
||||
|
||||
private fun clearAllData() {
|
||||
|
||||
@@ -1,24 +1,20 @@
|
||||
package org.thoughtcrime.securesms.loki.dialogs
|
||||
|
||||
import android.app.Dialog
|
||||
import android.content.ClipData
|
||||
import android.content.ClipboardManager
|
||||
import android.content.Context
|
||||
import android.graphics.Color
|
||||
import android.graphics.drawable.ColorDrawable
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import kotlinx.android.synthetic.main.dialog_seed.view.*
|
||||
import network.loki.messenger.R
|
||||
import org.session.libsession.utilities.IdentityKeyUtil
|
||||
import org.thoughtcrime.securesms.loki.utilities.MnemonicUtilities
|
||||
import org.session.libsignal.crypto.MnemonicCodec
|
||||
import org.session.libsignal.utilities.hexEncodedPrivateKey
|
||||
import org.thoughtcrime.securesms.conversation.v2.utilities.BaseDialog
|
||||
|
||||
class SeedDialog : DialogFragment() {
|
||||
class SeedDialog : BaseDialog() {
|
||||
|
||||
private val seed by lazy {
|
||||
var hexEncodedSeed = IdentityKeyUtil.retrieve(requireContext(), IdentityKeyUtil.LOKI_SEED)
|
||||
@@ -31,16 +27,12 @@ class SeedDialog : DialogFragment() {
|
||||
MnemonicCodec(loadFileContents).encode(hexEncodedSeed!!, MnemonicCodec.Language.Configuration.english)
|
||||
}
|
||||
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
val builder = AlertDialog.Builder(requireContext())
|
||||
override fun setContentView(builder: AlertDialog.Builder) {
|
||||
val contentView = LayoutInflater.from(requireContext()).inflate(R.layout.dialog_seed, null)
|
||||
contentView.seedTextView.text = seed
|
||||
contentView.cancelButton.setOnClickListener { dismiss() }
|
||||
contentView.copyButton.setOnClickListener { copySeed() }
|
||||
builder.setView(contentView)
|
||||
val result = builder.create()
|
||||
result.window?.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
|
||||
return result
|
||||
}
|
||||
|
||||
private fun copySeed() {
|
||||
|
||||
@@ -7,6 +7,7 @@ import android.text.SpannableString
|
||||
import android.text.style.ForegroundColorSpan
|
||||
import android.text.style.StyleSpan
|
||||
import android.util.Range
|
||||
import androidx.core.content.res.ResourcesCompat
|
||||
import network.loki.messenger.R
|
||||
import nl.komponents.kovenant.combine.Tuple2
|
||||
import org.session.libsession.messaging.contacts.Contact
|
||||
@@ -23,7 +24,7 @@ object MentionUtilities {
|
||||
|
||||
@JvmStatic
|
||||
fun highlightMentions(text: CharSequence, isOutgoingMessage: Boolean, threadID: Long, context: Context): SpannableString {
|
||||
var text = text
|
||||
@Suppress("NAME_SHADOWING") var text = text
|
||||
val threadDB = DatabaseFactory.getThreadDatabase(context)
|
||||
val isOpenGroup = threadDB.getRecipientForThreadId(threadID)?.isOpenGroupRecipient ?: false
|
||||
val pattern = Pattern.compile("@[0-9a-fA-F]*")
|
||||
@@ -38,7 +39,7 @@ object MentionUtilities {
|
||||
TextSecurePreferences.getProfileName(context)
|
||||
} else {
|
||||
val contact = DatabaseFactory.getSessionContactDatabase(context).getContactWithSessionID(publicKey)
|
||||
val context = if (isOpenGroup) Contact.ContactContext.OPEN_GROUP else Contact.ContactContext.REGULAR
|
||||
@Suppress("NAME_SHADOWING") val context = if (isOpenGroup) Contact.ContactContext.OPEN_GROUP else Contact.ContactContext.REGULAR
|
||||
contact?.displayName(context)
|
||||
}
|
||||
if (userDisplayName != null) {
|
||||
@@ -54,10 +55,15 @@ object MentionUtilities {
|
||||
}
|
||||
}
|
||||
val result = SpannableString(text)
|
||||
val isLightMode = UiModeUtilities.isDayUiMode(context)
|
||||
for (mention in mentions) {
|
||||
val isLightMode = UiModeUtilities.isDayUiMode(context)
|
||||
val colorID = if (isLightMode && isOutgoingMessage) R.color.black else R.color.accent
|
||||
result.setSpan(ForegroundColorSpan(context.resources.getColorWithID(colorID, context.theme)), mention.first.lower, mention.first.upper, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
|
||||
val colorID = if (isOutgoingMessage) {
|
||||
if (isLightMode) R.color.white else R.color.black
|
||||
} else {
|
||||
R.color.accent
|
||||
}
|
||||
val color = ResourcesCompat.getColor(context.resources, colorID, context.theme)
|
||||
result.setSpan(ForegroundColorSpan(color), mention.first.lower, mention.first.upper, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
|
||||
result.setSpan(StyleSpan(Typeface.BOLD), mention.first.lower, mention.first.upper, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
|
||||
}
|
||||
return result
|
||||
|
||||
@@ -4,9 +4,11 @@ import android.content.Context
|
||||
import android.content.res.Resources
|
||||
import android.graphics.Typeface
|
||||
import android.util.AttributeSet
|
||||
import android.util.TypedValue
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.widget.LinearLayout
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import kotlinx.android.synthetic.main.view_conversation.view.*
|
||||
import network.loki.messenger.R
|
||||
@@ -37,13 +39,20 @@ class ConversationView : LinearLayout {
|
||||
fun bind(thread: ThreadRecord, isTyping: Boolean, glide: GlideRequests) {
|
||||
this.thread = thread
|
||||
populateUserPublicKeyCacheIfNeeded(thread.threadId, context) // FIXME: This is a bad place to do this
|
||||
val unreadCount = thread.unreadCount
|
||||
if (thread.recipient.isBlocked) {
|
||||
accentView.setBackgroundResource(R.color.destructive)
|
||||
accentView.visibility = View.VISIBLE
|
||||
} else {
|
||||
accentView.setBackgroundResource(R.color.accent)
|
||||
accentView.visibility = if (thread.unreadCount > 0) View.VISIBLE else View.INVISIBLE
|
||||
accentView.visibility = if (unreadCount > 0) View.VISIBLE else View.INVISIBLE
|
||||
}
|
||||
val formattedUnreadCount = if (unreadCount < 100) unreadCount.toString() else "99+"
|
||||
unreadCountTextView.text = formattedUnreadCount
|
||||
val textSize = if (unreadCount < 100) 12.0f else 9.0f
|
||||
unreadCountTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, textSize)
|
||||
unreadCountTextView.setTypeface(Typeface.DEFAULT, if (unreadCount < 100) Typeface.BOLD else Typeface.NORMAL)
|
||||
unreadCountIndicator.isVisible = (unreadCount != 0)
|
||||
profilePictureView.glide = glide
|
||||
profilePictureView.update(thread.recipient, thread.threadId)
|
||||
val senderDisplayName = getUserDisplayName(thread.recipient) ?: thread.recipient.address.toString()
|
||||
@@ -53,7 +62,7 @@ class ConversationView : LinearLayout {
|
||||
val rawSnippet = thread.getDisplayBody(context)
|
||||
val snippet = highlightMentions(rawSnippet, thread.threadId, context)
|
||||
snippetTextView.text = snippet
|
||||
snippetTextView.typeface = if (thread.unreadCount > 0) Typeface.DEFAULT_BOLD else Typeface.DEFAULT
|
||||
snippetTextView.typeface = if (unreadCount > 0) Typeface.DEFAULT_BOLD else Typeface.DEFAULT
|
||||
snippetTextView.visibility = if (isTyping) View.GONE else View.VISIBLE
|
||||
if (isTyping) {
|
||||
typingIndicatorView.startAnimation()
|
||||
|
||||
@@ -10,7 +10,7 @@ import android.view.ViewGroup;
|
||||
import network.loki.messenger.R;
|
||||
|
||||
|
||||
import org.thoughtcrime.securesms.components.ThumbnailView;
|
||||
import org.thoughtcrime.securesms.conversation.v2.utilities.ThumbnailView;
|
||||
import org.thoughtcrime.securesms.mediasend.Media;
|
||||
import org.thoughtcrime.securesms.mms.GlideRequests;
|
||||
import org.thoughtcrime.securesms.util.StableIdGenerator;
|
||||
|
||||
@@ -41,7 +41,7 @@ import org.thoughtcrime.securesms.MediaPreviewActivity;
|
||||
import org.thoughtcrime.securesms.loki.views.MessageAudioView;
|
||||
import org.thoughtcrime.securesms.components.DocumentView;
|
||||
import org.thoughtcrime.securesms.components.RemovableEditableMediaView;
|
||||
import org.thoughtcrime.securesms.components.ThumbnailView;
|
||||
import org.thoughtcrime.securesms.conversation.v2.utilities.ThumbnailView;
|
||||
import org.session.libsignal.utilities.NoExternalStorageException;
|
||||
import org.thoughtcrime.securesms.giph.ui.GiphyActivity;
|
||||
import org.session.libsignal.utilities.Log;
|
||||
|
||||
Reference in New Issue
Block a user