Merge pull request #729 from hjubb/copy_url_link

Allow copying URL from conversation
This commit is contained in:
Harris 2021-09-21 00:13:59 +00:00 committed by GitHub
commit 4533a25a3c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 147 additions and 52 deletions

View File

@ -1338,12 +1338,21 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
override fun copyMessages(messages: Set<MessageRecord>) { override fun copyMessages(messages: Set<MessageRecord>) {
val sortedMessages = messages.sortedBy { it.dateSent } val sortedMessages = messages.sortedBy { it.dateSent }
val messageSize = sortedMessages.size
val builder = StringBuilder() val builder = StringBuilder()
for (message in sortedMessages) { val messageIterator = sortedMessages.iterator()
while (messageIterator.hasNext()) {
val message = messageIterator.next()
val body = MentionUtilities.highlightMentions(message.body, threadID, this) val body = MentionUtilities.highlightMentions(message.body, threadID, this)
if (TextUtils.isEmpty(body)) { continue } if (TextUtils.isEmpty(body)) { continue }
if (messageSize > 1) {
val formattedTimestamp = DateUtils.getDisplayFormattedTimeSpanString(this, Locale.getDefault(), message.timestamp) val formattedTimestamp = DateUtils.getDisplayFormattedTimeSpanString(this, Locale.getDefault(), message.timestamp)
builder.append("$formattedTimestamp: $body").append('\n') builder.append("$formattedTimestamp: ")
}
builder.append(body)
if (messageIterator.hasNext()) {
builder.append('\n')
}
} }
if (builder.isNotEmpty() && builder[builder.length - 1] == '\n') { if (builder.isNotEmpty() && builder[builder.length - 1] == '\n') {
builder.deleteCharAt(builder.length - 1) builder.deleteCharAt(builder.length - 1)

View File

@ -0,0 +1,72 @@
package org.thoughtcrime.securesms.conversation.v2
import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context.CLIPBOARD_SERVICE
import android.content.Intent
import android.graphics.Typeface
import android.net.Uri
import android.os.Bundle
import android.text.Spannable
import android.text.SpannableStringBuilder
import android.text.style.StyleSpan
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import kotlinx.android.synthetic.main.fragment_modal_url_bottom_sheet.*
import network.loki.messenger.R
import org.thoughtcrime.securesms.util.UiModeUtilities
class ModalUrlBottomSheet(private val url: String): BottomSheetDialogFragment(), View.OnClickListener {
override fun onCreateView(inflater: LayoutInflater,container: ViewGroup?,savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_modal_url_bottom_sheet, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
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)
openURLExplanationTextView.text = spannable
cancelButton.setOnClickListener(this)
copyButton.setOnClickListener(this)
openURLButton.setOnClickListener(this)
}
private fun open() {
try {
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url))
requireContext().startActivity(intent)
} catch (e: Exception) {
Toast.makeText(context, R.string.invalid_url, Toast.LENGTH_SHORT).show()
}
dismiss()
}
private fun copy() {
val clip = ClipData.newPlainText("URL", url)
val manager = requireContext().getSystemService(CLIPBOARD_SERVICE) as ClipboardManager
manager.setPrimaryClip(clip)
Toast.makeText(requireContext(), R.string.copied_to_clipboard, Toast.LENGTH_SHORT).show()
dismiss()
}
override fun onStart() {
super.onStart()
val window = dialog?.window ?: return
val isLightMode = UiModeUtilities.isDayUiMode(requireContext())
window.setDimAmount(if (isLightMode) 0.1f else 0.75f)
}
override fun onClick(v: View?) {
when (v) {
openURLButton -> open()
copyButton -> copy()
cancelButton -> dismiss()
}
}
}

View File

@ -1,40 +0,0 @@
package org.thoughtcrime.securesms.conversation.v2.dialogs
import android.content.Intent
import android.graphics.Typeface
import android.net.Uri
import android.text.Spannable
import android.text.SpannableStringBuilder
import android.text.style.StyleSpan
import android.view.LayoutInflater
import android.widget.Toast
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
/** Shown upon tapping a URL. */
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() {
try {
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url))
requireContext().startActivity(intent)
} catch (e: Exception) {
Toast.makeText(context, R.string.invalid_url, Toast.LENGTH_SHORT).show()
}
dismiss()
}
}

View File

@ -3,7 +3,6 @@ package org.thoughtcrime.securesms.conversation.v2.messages
import android.content.Context import android.content.Context
import android.graphics.Canvas import android.graphics.Canvas
import android.graphics.Rect import android.graphics.Rect
import android.text.method.LinkMovementMethod
import android.util.AttributeSet import android.util.AttributeSet
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.MotionEvent import android.view.MotionEvent
@ -11,20 +10,17 @@ import android.widget.LinearLayout
import android.widget.TextView import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.res.ResourcesCompat import androidx.core.content.res.ResourcesCompat
import androidx.core.text.getSpans
import androidx.core.text.toSpannable
import androidx.core.view.isVisible import androidx.core.view.isVisible
import kotlinx.android.synthetic.main.view_link_preview.view.* import kotlinx.android.synthetic.main.view_link_preview.view.*
import network.loki.messenger.R import network.loki.messenger.R
import org.thoughtcrime.securesms.components.CornerMask import org.thoughtcrime.securesms.components.CornerMask
import org.thoughtcrime.securesms.conversation.v2.dialogs.OpenURLDialog import org.thoughtcrime.securesms.conversation.v2.ModalUrlBottomSheet
import org.thoughtcrime.securesms.conversation.v2.utilities.MessageBubbleUtilities import org.thoughtcrime.securesms.conversation.v2.utilities.MessageBubbleUtilities
import org.thoughtcrime.securesms.conversation.v2.utilities.ModalURLSpan
import org.thoughtcrime.securesms.conversation.v2.utilities.TextUtilities.getIntersectedModalSpans import org.thoughtcrime.securesms.conversation.v2.utilities.TextUtilities.getIntersectedModalSpans
import org.thoughtcrime.securesms.database.model.MmsMessageRecord import org.thoughtcrime.securesms.database.model.MmsMessageRecord
import org.thoughtcrime.securesms.util.UiModeUtilities
import org.thoughtcrime.securesms.mms.GlideRequests import org.thoughtcrime.securesms.mms.GlideRequests
import org.thoughtcrime.securesms.mms.ImageSlide import org.thoughtcrime.securesms.mms.ImageSlide
import org.thoughtcrime.securesms.util.UiModeUtilities
class LinkPreviewView : LinearLayout { class LinkPreviewView : LinearLayout {
private val cornerMask by lazy { CornerMask(this) } private val cornerMask by lazy { CornerMask(this) }
@ -97,7 +93,7 @@ class LinkPreviewView : LinearLayout {
fun openURL() { fun openURL() {
val url = this.url ?: return val url = this.url ?: return
val activity = context as AppCompatActivity val activity = context as AppCompatActivity
OpenURLDialog(url).show(activity.supportFragmentManager, "Open URL Dialog") ModalUrlBottomSheet(url).show(activity.supportFragmentManager, "Open URL Dialog")
} }
// endregion // endregion
} }

View File

@ -31,8 +31,8 @@ import org.session.libsession.utilities.ViewUtil
import org.session.libsession.utilities.recipients.Recipient import org.session.libsession.utilities.recipients.Recipient
import org.thoughtcrime.securesms.components.emoji.EmojiTextView import org.thoughtcrime.securesms.components.emoji.EmojiTextView
import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2 import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2
import org.thoughtcrime.securesms.conversation.v2.ModalUrlBottomSheet
import org.thoughtcrime.securesms.conversation.v2.components.AlbumThumbnailView import org.thoughtcrime.securesms.conversation.v2.components.AlbumThumbnailView
import org.thoughtcrime.securesms.conversation.v2.dialogs.OpenURLDialog
import org.thoughtcrime.securesms.conversation.v2.utilities.MentionUtilities import org.thoughtcrime.securesms.conversation.v2.utilities.MentionUtilities
import org.thoughtcrime.securesms.conversation.v2.utilities.ModalURLSpan import org.thoughtcrime.securesms.conversation.v2.utilities.ModalURLSpan
import org.thoughtcrime.securesms.conversation.v2.utilities.TextUtilities.getIntersectedModalSpans import org.thoughtcrime.securesms.conversation.v2.utilities.TextUtilities.getIntersectedModalSpans
@ -237,7 +237,7 @@ class VisibleMessageContentView : LinearLayout {
val updatedUrl = urlSpan.url.let { HttpUrl.parse(it).toString() } val updatedUrl = urlSpan.url.let { HttpUrl.parse(it).toString() }
val replacementSpan = ModalURLSpan(updatedUrl) { url -> val replacementSpan = ModalURLSpan(updatedUrl) { url ->
val activity = context as AppCompatActivity val activity = context as AppCompatActivity
OpenURLDialog(url).show(activity.supportFragmentManager, "Open URL Dialog") ModalUrlBottomSheet(url).show(activity.supportFragmentManager, "Open URL Dialog")
} }
val start = body.getSpanStart(urlSpan) val start = body.getSpanStart(urlSpan)
val end = body.getSpanEnd(urlSpan) val end = body.getSpanEnd(urlSpan)

View File

@ -0,0 +1,57 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:behavior_hideable="true"
app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior"
android:gravity="center_horizontal"
android:paddingTop="@dimen/large_spacing">
<TextView
android:id="@+id/openURLTitleTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/dialog_open_url_title"
android:textColor="@color/text"
android:textStyle="bold"
android:textSize="@dimen/large_font_size" />
<TextView
android:id="@+id/openURLExplanationTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/dialog_open_url_explanation"
android:paddingHorizontal="@dimen/medium_spacing"
android:textColor="@color/text"
android:textSize="@dimen/small_font_size"
android:layout_margin="@dimen/large_spacing"
android:textAlignment="center" />
<TextView
android:id="@+id/openURLButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="@style/BottomSheetActionItem"
android:text="@string/open"
/>
<TextView
android:id="@+id/copyButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="@style/BottomSheetActionItem"
android:text="@string/copy_url"
/>
<TextView
android:textColor="@color/destructive"
android:id="@+id/cancelButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="@style/BottomSheetActionItem"
android:text="@string/cancel"
/>
</LinearLayout>

View File

@ -872,6 +872,7 @@
<string name="dialog_open_url_title">Open URL?</string> <string name="dialog_open_url_title">Open URL?</string>
<string name="dialog_open_url_explanation">Are you sure you want to open %s?</string> <string name="dialog_open_url_explanation">Are you sure you want to open %s?</string>
<string name="open">Open</string> <string name="open">Open</string>
<string name="copy_url">Copy URL</string>
<string name="dialog_link_preview_title">Enable Link Previews?</string> <string name="dialog_link_preview_title">Enable Link Previews?</string>
<string name="dialog_link_preview_explanation">Enabling link previews will show previews for URLs you send and receive. This can be useful, but Session will need to contact linked websites to generate previews. You can always disable link previews in Session\'s settings.</string> <string name="dialog_link_preview_explanation">Enabling link previews will show previews for URLs you send and receive. This can be useful, but Session will need to contact linked websites to generate previews. You can always disable link previews in Session\'s settings.</string>