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:
jubb
2021-06-24 16:26:45 +10:00
61 changed files with 1088 additions and 159 deletions

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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
}

View File

@@ -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 {

View File

@@ -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);

View File

@@ -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()
}
}

View File

@@ -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
}
}

View File

@@ -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
}
}

View File

@@ -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
}
}

View File

@@ -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
}
}

View File

@@ -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
}
}

View File

@@ -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
}

View File

@@ -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)) {

View File

@@ -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
}

View File

@@ -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
}
}

View File

@@ -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 {

View File

@@ -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));
}

View File

@@ -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;

View File

@@ -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.*

View File

@@ -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() {

View File

@@ -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() {

View File

@@ -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

View File

@@ -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()

View File

@@ -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;

View File

@@ -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;