mirror of
https://github.com/oxen-io/session-android.git
synced 2025-04-28 14:30:47 +00:00
Merge branch 'dev'
This commit is contained in:
commit
c67d164db6
@ -153,8 +153,8 @@ dependencies {
|
|||||||
testImplementation 'org.robolectric:shadows-multidex:4.4'
|
testImplementation 'org.robolectric:shadows-multidex:4.4'
|
||||||
}
|
}
|
||||||
|
|
||||||
def canonicalVersionCode = 221
|
def canonicalVersionCode = 222
|
||||||
def canonicalVersionName = "1.11.9"
|
def canonicalVersionName = "1.11.10"
|
||||||
|
|
||||||
def postFixSize = 10
|
def postFixSize = 10
|
||||||
def abiPostFix = ['armeabi-v7a' : 1,
|
def abiPostFix = ['armeabi-v7a' : 1,
|
||||||
|
@ -3,10 +3,7 @@ package org.thoughtcrime.securesms.conversation.v2
|
|||||||
import android.Manifest
|
import android.Manifest
|
||||||
import android.animation.FloatEvaluator
|
import android.animation.FloatEvaluator
|
||||||
import android.animation.ValueAnimator
|
import android.animation.ValueAnimator
|
||||||
import android.content.ClipData
|
import android.content.*
|
||||||
import android.content.ClipboardManager
|
|
||||||
import android.content.Context
|
|
||||||
import android.content.Intent
|
|
||||||
import android.content.res.Resources
|
import android.content.res.Resources
|
||||||
import android.database.Cursor
|
import android.database.Cursor
|
||||||
import android.graphics.Rect
|
import android.graphics.Rect
|
||||||
@ -237,7 +234,14 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
|
|||||||
setUpLinkPreviewObserver()
|
setUpLinkPreviewObserver()
|
||||||
restoreDraftIfNeeded()
|
restoreDraftIfNeeded()
|
||||||
addOpenGroupGuidelinesIfNeeded()
|
addOpenGroupGuidelinesIfNeeded()
|
||||||
scrollToBottomButton.setOnClickListener { conversationRecyclerView.smoothScrollToPosition(0) }
|
scrollToBottomButton.setOnClickListener {
|
||||||
|
val layoutManager = conversationRecyclerView.layoutManager ?: return@setOnClickListener
|
||||||
|
if (layoutManager.isSmoothScrolling) {
|
||||||
|
conversationRecyclerView.scrollToPosition(0)
|
||||||
|
} else {
|
||||||
|
conversationRecyclerView.smoothScrollToPosition(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
unreadCount = DatabaseFactory.getMmsSmsDatabase(this).getUnreadCount(threadID)
|
unreadCount = DatabaseFactory.getMmsSmsDatabase(this).getUnreadCount(threadID)
|
||||||
updateUnreadCountIndicator()
|
updateUnreadCountIndicator()
|
||||||
setUpTypingObserver()
|
setUpTypingObserver()
|
||||||
@ -972,7 +976,23 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun showGIFPicker() {
|
private fun showGIFPicker() {
|
||||||
|
val hasSeenGIFMetaDataWarning: Boolean = TextSecurePreferences.hasSeenGIFMetaDataWarning(this)
|
||||||
|
if (!hasSeenGIFMetaDataWarning) {
|
||||||
|
val builder = AlertDialog.Builder(this)
|
||||||
|
builder.setTitle("Search GIFs?")
|
||||||
|
builder.setMessage("You will not have full metadata protection when sending GIFs.")
|
||||||
|
builder.setPositiveButton("OK") { dialog: DialogInterface, which: Int ->
|
||||||
|
TextSecurePreferences.setHasSeenGIFMetaDataWarning(this)
|
||||||
AttachmentManager.selectGif(this, ConversationActivityV2.PICK_GIF)
|
AttachmentManager.selectGif(this, ConversationActivityV2.PICK_GIF)
|
||||||
|
dialog.dismiss()
|
||||||
|
}
|
||||||
|
builder.setNegativeButton(
|
||||||
|
"Cancel"
|
||||||
|
) { dialog: DialogInterface, which: Int -> dialog.dismiss() }
|
||||||
|
builder.create().show()
|
||||||
|
} else {
|
||||||
|
AttachmentManager.selectGif(this, ConversationActivityV2.PICK_GIF)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun showDocumentPicker() {
|
private fun showDocumentPicker() {
|
||||||
@ -1325,12 +1345,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)
|
||||||
|
@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,16 +1,18 @@
|
|||||||
package org.thoughtcrime.securesms.conversation.v2.components;
|
package org.thoughtcrime.securesms.conversation.v2.components;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import android.util.AttributeSet;
|
import android.util.AttributeSet;
|
||||||
|
|
||||||
import network.loki.messenger.R;
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
import org.session.libsession.utilities.Util;
|
import org.session.libsession.utilities.Util;
|
||||||
|
|
||||||
import java.lang.ref.WeakReference;
|
import java.lang.ref.WeakReference;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import network.loki.messenger.R;
|
||||||
|
|
||||||
public class ExpirationTimerView extends androidx.appcompat.widget.AppCompatImageView {
|
public class ExpirationTimerView extends androidx.appcompat.widget.AppCompatImageView {
|
||||||
|
|
||||||
private long startedAt;
|
private long startedAt;
|
||||||
@ -86,10 +88,12 @@ public class ExpirationTimerView extends androidx.appcompat.widget.AppCompatImag
|
|||||||
long progressed = System.currentTimeMillis() - startedAt;
|
long progressed = System.currentTimeMillis() - startedAt;
|
||||||
long remaining = expiresIn - progressed;
|
long remaining = expiresIn - progressed;
|
||||||
|
|
||||||
if (remaining < TimeUnit.SECONDS.toMillis(30)) {
|
if (remaining <= 0) {
|
||||||
return 50;
|
return 0;
|
||||||
} else {
|
} else if (remaining < TimeUnit.SECONDS.toMillis(30)) {
|
||||||
return 1000;
|
return 1000;
|
||||||
|
} else {
|
||||||
|
return 5000;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -106,16 +110,20 @@ public class ExpirationTimerView extends androidx.appcompat.widget.AppCompatImag
|
|||||||
ExpirationTimerView timerView = expirationTimerViewReference.get();
|
ExpirationTimerView timerView = expirationTimerViewReference.get();
|
||||||
if (timerView == null) return;
|
if (timerView == null) return;
|
||||||
|
|
||||||
timerView.setExpirationTime(timerView.startedAt, timerView.expiresIn);
|
long nextUpdate = timerView.calculateAnimationDelay(timerView.startedAt, timerView.expiresIn);
|
||||||
|
|
||||||
synchronized (timerView) {
|
synchronized (timerView) {
|
||||||
if (!timerView.visible) {
|
if (timerView.visible) {
|
||||||
|
timerView.setExpirationTime(timerView.startedAt, timerView.expiresIn);
|
||||||
|
} else {
|
||||||
|
timerView.stopped = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (nextUpdate <= 0) {
|
||||||
timerView.stopped = true;
|
timerView.stopped = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Util.runOnMainDelayed(this, nextUpdate);
|
||||||
Util.runOnMainDelayed(this, timerView.calculateAnimationDelay(timerView.startedAt, timerView.expiresIn));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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()
|
|
||||||
}
|
|
||||||
}
|
|
@ -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
|
||||||
}
|
}
|
@ -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)
|
||||||
|
@ -59,6 +59,7 @@ class VisibleMessageView : LinearLayout {
|
|||||||
const val longPressMovementTreshold = 10.0f // dp
|
const val longPressMovementTreshold = 10.0f // dp
|
||||||
const val longPressDurationThreshold = 250L // ms
|
const val longPressDurationThreshold = 250L // ms
|
||||||
const val maxDoubleTapInterval = 200L
|
const val maxDoubleTapInterval = 200L
|
||||||
|
const val maxTimeBetweenBreaks = 5 * 60 * 1000L // 5 minutes
|
||||||
}
|
}
|
||||||
|
|
||||||
// region Lifecycle
|
// region Lifecycle
|
||||||
@ -111,7 +112,7 @@ class VisibleMessageView : LinearLayout {
|
|||||||
senderNameTextView.visibility = View.GONE
|
senderNameTextView.visibility = View.GONE
|
||||||
}
|
}
|
||||||
// Date break
|
// Date break
|
||||||
val showDateBreak = (previous == null || !DateUtils.isSameHour(message.timestamp, previous.timestamp))
|
val showDateBreak = (previous == null || message.timestamp - previous.timestamp > maxTimeBetweenBreaks)
|
||||||
dateBreakTextView.isVisible = showDateBreak
|
dateBreakTextView.isVisible = showDateBreak
|
||||||
dateBreakTextView.text = if (showDateBreak) DateUtils.getDisplayFormattedTimeSpanString(context, Locale.getDefault(), message.timestamp) else ""
|
dateBreakTextView.text = if (showDateBreak) DateUtils.getDisplayFormattedTimeSpanString(context, Locale.getDefault(), message.timestamp) else ""
|
||||||
// Timestamp
|
// Timestamp
|
||||||
@ -217,7 +218,9 @@ class VisibleMessageView : LinearLayout {
|
|||||||
if (message.expireStarted + message.expiresIn <= System.currentTimeMillis()) {
|
if (message.expireStarted + message.expiresIn <= System.currentTimeMillis()) {
|
||||||
ApplicationContext.getInstance(context).expiringMessageManager.checkSchedule()
|
ApplicationContext.getInstance(context).expiringMessageManager.checkSchedule()
|
||||||
}
|
}
|
||||||
} else if (!message.isOutgoing && !message.isMediaPending) {
|
} else if (!message.isMediaPending) {
|
||||||
|
expirationTimerView.setPercentComplete(0.0f)
|
||||||
|
expirationTimerView.stopAnimation()
|
||||||
ThreadUtils.queue {
|
ThreadUtils.queue {
|
||||||
val expirationManager = ApplicationContext.getInstance(context).expiringMessageManager
|
val expirationManager = ApplicationContext.getInstance(context).expiringMessageManager
|
||||||
val id = message.getId()
|
val id = message.getId()
|
||||||
@ -225,6 +228,9 @@ class VisibleMessageView : LinearLayout {
|
|||||||
if (mms) DatabaseFactory.getMmsDatabase(context).markExpireStarted(id) else DatabaseFactory.getSmsDatabase(context).markExpireStarted(id)
|
if (mms) DatabaseFactory.getMmsDatabase(context).markExpireStarted(id) else DatabaseFactory.getSmsDatabase(context).markExpireStarted(id)
|
||||||
expirationManager.scheduleDeletion(id, mms, message.expiresIn)
|
expirationManager.scheduleDeletion(id, mms, message.expiresIn)
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
expirationTimerView.stopAnimation()
|
||||||
|
expirationTimerView.setPercentComplete(0.0f)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
expirationTimerView.isVisible = false
|
expirationTimerView.isVisible = false
|
||||||
|
@ -46,14 +46,20 @@ class ConversationView : LinearLayout {
|
|||||||
accentView.visibility = View.VISIBLE
|
accentView.visibility = View.VISIBLE
|
||||||
} else {
|
} else {
|
||||||
accentView.setBackgroundResource(R.color.accent)
|
accentView.setBackgroundResource(R.color.accent)
|
||||||
accentView.visibility = if (unreadCount > 0) View.VISIBLE else View.INVISIBLE
|
// Using thread.isRead we can determine if the last message was our own, and display it as 'read' even though previous messages may not be
|
||||||
|
// This would also not trigger the disappearing message timer which may or may not be desirable
|
||||||
|
accentView.visibility = if (unreadCount > 0 && !thread.isRead) View.VISIBLE else View.INVISIBLE
|
||||||
|
}
|
||||||
|
val formattedUnreadCount = if (thread.isRead) {
|
||||||
|
null
|
||||||
|
} else {
|
||||||
|
if (unreadCount < 100) unreadCount.toString() else "99+"
|
||||||
}
|
}
|
||||||
val formattedUnreadCount = if (unreadCount < 100) unreadCount.toString() else "99+"
|
|
||||||
unreadCountTextView.text = formattedUnreadCount
|
unreadCountTextView.text = formattedUnreadCount
|
||||||
val textSize = if (unreadCount < 100) 12.0f else 9.0f
|
val textSize = if (unreadCount < 100) 12.0f else 9.0f
|
||||||
unreadCountTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, textSize)
|
unreadCountTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, textSize)
|
||||||
unreadCountTextView.setTypeface(Typeface.DEFAULT, if (unreadCount < 100) Typeface.BOLD else Typeface.NORMAL)
|
unreadCountTextView.setTypeface(Typeface.DEFAULT, if (unreadCount < 100) Typeface.BOLD else Typeface.NORMAL)
|
||||||
unreadCountIndicator.isVisible = (unreadCount != 0)
|
unreadCountIndicator.isVisible = (unreadCount != 0 && !thread.isRead)
|
||||||
val senderDisplayName = getUserDisplayName(thread.recipient)
|
val senderDisplayName = getUserDisplayName(thread.recipient)
|
||||||
?: thread.recipient.address.toString()
|
?: thread.recipient.address.toString()
|
||||||
conversationViewDisplayNameTextView.text = senderDisplayName
|
conversationViewDisplayNameTextView.text = senderDisplayName
|
||||||
@ -69,7 +75,7 @@ class ConversationView : LinearLayout {
|
|||||||
val rawSnippet = thread.getDisplayBody(context)
|
val rawSnippet = thread.getDisplayBody(context)
|
||||||
val snippet = highlightMentions(rawSnippet, thread.threadId, context)
|
val snippet = highlightMentions(rawSnippet, thread.threadId, context)
|
||||||
snippetTextView.text = snippet
|
snippetTextView.text = snippet
|
||||||
snippetTextView.typeface = if (unreadCount > 0) Typeface.DEFAULT_BOLD else Typeface.DEFAULT
|
snippetTextView.typeface = if (unreadCount > 0 && !thread.isRead) Typeface.DEFAULT_BOLD else Typeface.DEFAULT
|
||||||
snippetTextView.visibility = if (isTyping) View.GONE else View.VISIBLE
|
snippetTextView.visibility = if (isTyping) View.GONE else View.VISIBLE
|
||||||
if (isTyping) {
|
if (isTyping) {
|
||||||
typingIndicatorView.startAnimation()
|
typingIndicatorView.startAnimation()
|
||||||
|
@ -33,15 +33,11 @@ import org.session.libsession.utilities.SSKEnvironment.ProfileManagerProtocol
|
|||||||
import org.session.libsession.utilities.TextSecurePreferences
|
import org.session.libsession.utilities.TextSecurePreferences
|
||||||
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity
|
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity
|
||||||
import org.thoughtcrime.securesms.avatar.AvatarSelection
|
import org.thoughtcrime.securesms.avatar.AvatarSelection
|
||||||
import org.thoughtcrime.securesms.util.ConfigurationMessageUtilities
|
|
||||||
import org.thoughtcrime.securesms.util.UiModeUtilities
|
|
||||||
import org.thoughtcrime.securesms.util.push
|
|
||||||
import org.thoughtcrime.securesms.mms.GlideApp
|
import org.thoughtcrime.securesms.mms.GlideApp
|
||||||
import org.thoughtcrime.securesms.mms.GlideRequests
|
import org.thoughtcrime.securesms.mms.GlideRequests
|
||||||
import org.thoughtcrime.securesms.permissions.Permissions
|
import org.thoughtcrime.securesms.permissions.Permissions
|
||||||
import org.thoughtcrime.securesms.profiles.ProfileMediaConstraints
|
import org.thoughtcrime.securesms.profiles.ProfileMediaConstraints
|
||||||
import org.thoughtcrime.securesms.util.BitmapDecodingException
|
import org.thoughtcrime.securesms.util.*
|
||||||
import org.thoughtcrime.securesms.util.BitmapUtil
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.security.SecureRandom
|
import java.security.SecureRandom
|
||||||
import java.util.*
|
import java.util.*
|
||||||
@ -85,6 +81,7 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() {
|
|||||||
chatsButton.setOnClickListener { showChatSettings() }
|
chatsButton.setOnClickListener { showChatSettings() }
|
||||||
sendInvitationButton.setOnClickListener { sendInvitation() }
|
sendInvitationButton.setOnClickListener { sendInvitation() }
|
||||||
faqButton.setOnClickListener { showFAQ() }
|
faqButton.setOnClickListener { showFAQ() }
|
||||||
|
surveyButton.setOnClickListener { showSurvey() }
|
||||||
helpTranslateButton.setOnClickListener { helpTranslate() }
|
helpTranslateButton.setOnClickListener { helpTranslate() }
|
||||||
seedButton.setOnClickListener { showSeed() }
|
seedButton.setOnClickListener { showSeed() }
|
||||||
clearAllDataButton.setOnClickListener { clearAllData() }
|
clearAllDataButton.setOnClickListener { clearAllData() }
|
||||||
@ -296,6 +293,16 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun showSurvey() {
|
||||||
|
try {
|
||||||
|
val url = "https://getsession.org/survey"
|
||||||
|
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url))
|
||||||
|
startActivity(intent)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Toast.makeText(this, "Can't open URL", Toast.LENGTH_LONG).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun helpTranslate() {
|
private fun helpTranslate() {
|
||||||
try {
|
try {
|
||||||
val url = "https://crowdin.com/project/session-android"
|
val url = "https://crowdin.com/project/session-android"
|
||||||
|
@ -193,6 +193,7 @@
|
|||||||
android:background="?android:dividerHorizontal" />
|
android:background="?android:dividerHorizontal" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
|
android:padding="@dimen/small_spacing"
|
||||||
android:id="@+id/sendInvitationButton"
|
android:id="@+id/sendInvitationButton"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
@ -203,6 +204,7 @@
|
|||||||
android:text="@string/activity_settings_invite_button_title" />
|
android:text="@string/activity_settings_invite_button_title" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
|
android:padding="@dimen/small_spacing"
|
||||||
android:id="@+id/faqButton"
|
android:id="@+id/faqButton"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
@ -214,6 +216,19 @@
|
|||||||
android:text="@string/activity_settings_faq_button_title" />
|
android:text="@string/activity_settings_faq_button_title" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
|
android:padding="@dimen/small_spacing"
|
||||||
|
android:id="@+id/surveyButton"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="@dimen/medium_spacing"
|
||||||
|
android:textColor="@color/text"
|
||||||
|
android:textSize="@dimen/medium_font_size"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:gravity="center"
|
||||||
|
android:text="@string/activity_settings_survey_feedback" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:padding="@dimen/small_spacing"
|
||||||
android:id="@+id/helpTranslateButton"
|
android:id="@+id/helpTranslateButton"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
57
app/src/main/res/layout/fragment_modal_url_bottom_sheet.xml
Normal file
57
app/src/main/res/layout/fragment_modal_url_bottom_sheet.xml
Normal 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>
|
@ -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>
|
||||||
@ -897,5 +898,6 @@
|
|||||||
<string name="delete_message_for_me">Delete just for me</string>
|
<string name="delete_message_for_me">Delete just for me</string>
|
||||||
<string name="delete_message_for_everyone">Delete for everyone</string>
|
<string name="delete_message_for_everyone">Delete for everyone</string>
|
||||||
<string name="delete_message_for_me_and_recipient">Delete for me and %s</string>
|
<string name="delete_message_for_me_and_recipient">Delete for me and %s</string>
|
||||||
|
<string name="activity_settings_survey_feedback">Feedback/Survey</string>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
@ -15,23 +15,18 @@ import org.session.libsession.messaging.sending_receiving.notifications.PushNoti
|
|||||||
import org.session.libsession.messaging.sending_receiving.pollers.ClosedGroupPollerV2
|
import org.session.libsession.messaging.sending_receiving.pollers.ClosedGroupPollerV2
|
||||||
import org.session.libsession.messaging.sending_receiving.quotes.QuoteModel
|
import org.session.libsession.messaging.sending_receiving.quotes.QuoteModel
|
||||||
import org.session.libsession.snode.SnodeAPI
|
import org.session.libsession.snode.SnodeAPI
|
||||||
import org.session.libsession.utilities.Address
|
import org.session.libsession.utilities.*
|
||||||
import org.session.libsession.utilities.GroupRecord
|
|
||||||
import org.session.libsession.utilities.recipients.Recipient
|
import org.session.libsession.utilities.recipients.Recipient
|
||||||
import org.session.libsession.utilities.GroupUtil
|
|
||||||
import org.session.libsession.utilities.SSKEnvironment
|
|
||||||
import org.session.libsession.utilities.TextSecurePreferences
|
|
||||||
import org.session.libsession.utilities.ProfileKeyUtil
|
|
||||||
import org.session.libsignal.crypto.ecc.DjbECPrivateKey
|
import org.session.libsignal.crypto.ecc.DjbECPrivateKey
|
||||||
import org.session.libsignal.crypto.ecc.DjbECPublicKey
|
import org.session.libsignal.crypto.ecc.DjbECPublicKey
|
||||||
import org.session.libsignal.crypto.ecc.ECKeyPair
|
import org.session.libsignal.crypto.ecc.ECKeyPair
|
||||||
import org.session.libsignal.utilities.guava.Optional
|
|
||||||
import org.session.libsignal.messages.SignalServiceGroup
|
import org.session.libsignal.messages.SignalServiceGroup
|
||||||
import org.session.libsignal.protos.SignalServiceProtos
|
import org.session.libsignal.protos.SignalServiceProtos
|
||||||
import org.session.libsignal.utilities.removing05PrefixIfNeeded
|
|
||||||
import org.session.libsignal.utilities.toHexString
|
|
||||||
import org.session.libsignal.utilities.Base64
|
import org.session.libsignal.utilities.Base64
|
||||||
import org.session.libsignal.utilities.Log
|
import org.session.libsignal.utilities.Log
|
||||||
|
import org.session.libsignal.utilities.guava.Optional
|
||||||
|
import org.session.libsignal.utilities.removing05PrefixIfNeeded
|
||||||
|
import org.session.libsignal.utilities.toHexString
|
||||||
import java.security.MessageDigest
|
import java.security.MessageDigest
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import kotlin.collections.ArrayList
|
import kotlin.collections.ArrayList
|
||||||
@ -285,7 +280,8 @@ private fun handleNewClosedGroup(sender: String, sentTimestamp: Long, groupPubli
|
|||||||
val userPublicKey = TextSecurePreferences.getLocalNumber(context)
|
val userPublicKey = TextSecurePreferences.getLocalNumber(context)
|
||||||
// Create the group
|
// Create the group
|
||||||
val groupID = GroupUtil.doubleEncodeGroupID(groupPublicKey)
|
val groupID = GroupUtil.doubleEncodeGroupID(groupPublicKey)
|
||||||
if (storage.getGroup(groupID) != null) {
|
val groupExists = storage.getGroup(groupID) != null
|
||||||
|
if (groupExists) {
|
||||||
// Update the group
|
// Update the group
|
||||||
if (!storage.isGroupActive(groupPublicKey)) {
|
if (!storage.isGroupActive(groupPublicKey)) {
|
||||||
// Clear zombie list if the group wasn't active
|
// Clear zombie list if the group wasn't active
|
||||||
@ -309,10 +305,10 @@ private fun handleNewClosedGroup(sender: String, sentTimestamp: Long, groupPubli
|
|||||||
// Notify the PN server
|
// Notify the PN server
|
||||||
PushNotificationAPI.performOperation(PushNotificationAPI.ClosedGroupOperation.Subscribe, groupPublicKey, storage.getUserPublicKey()!!)
|
PushNotificationAPI.performOperation(PushNotificationAPI.ClosedGroupOperation.Subscribe, groupPublicKey, storage.getUserPublicKey()!!)
|
||||||
// Notify the user
|
// Notify the user
|
||||||
if (userPublicKey == sender) {
|
if (userPublicKey == sender && !groupExists) {
|
||||||
val threadID = storage.getOrCreateThreadIdFor(Address.fromSerialized(groupID))
|
val threadID = storage.getOrCreateThreadIdFor(Address.fromSerialized(groupID))
|
||||||
storage.insertOutgoingInfoMessage(context, groupID, SignalServiceGroup.Type.CREATION, name, members, admins, threadID, sentTimestamp)
|
storage.insertOutgoingInfoMessage(context, groupID, SignalServiceGroup.Type.CREATION, name, members, admins, threadID, sentTimestamp)
|
||||||
} else {
|
} else if (userPublicKey != sender) {
|
||||||
storage.insertIncomingInfoMessage(context, sender, groupID, SignalServiceGroup.Type.CREATION, name, members, admins, sentTimestamp)
|
storage.insertIncomingInfoMessage(context, sender, groupID, SignalServiceGroup.Type.CREATION, name, members, admins, sentTimestamp)
|
||||||
}
|
}
|
||||||
// Start polling
|
// Start polling
|
||||||
|
@ -80,6 +80,7 @@ object TextSecurePreferences {
|
|||||||
const val UNIVERSAL_UNIDENTIFIED_ACCESS = "pref_universal_unidentified_access"
|
const val UNIVERSAL_UNIDENTIFIED_ACCESS = "pref_universal_unidentified_access"
|
||||||
const val TYPING_INDICATORS = "pref_typing_indicators"
|
const val TYPING_INDICATORS = "pref_typing_indicators"
|
||||||
const val LINK_PREVIEWS = "pref_link_previews"
|
const val LINK_PREVIEWS = "pref_link_previews"
|
||||||
|
private const val GIF_METADATA_WARNING = "has_seen_gif_metadata_warning"
|
||||||
private const val GIF_GRID_LAYOUT = "pref_gif_grid_layout"
|
private const val GIF_GRID_LAYOUT = "pref_gif_grid_layout"
|
||||||
const val IS_USING_FCM = "pref_is_using_fcm"
|
const val IS_USING_FCM = "pref_is_using_fcm"
|
||||||
private const val FCM_TOKEN = "pref_fcm_token"
|
private const val FCM_TOKEN = "pref_fcm_token"
|
||||||
@ -285,6 +286,16 @@ object TextSecurePreferences {
|
|||||||
setBooleanPreference(context, LINK_PREVIEWS, enabled)
|
setBooleanPreference(context, LINK_PREVIEWS, enabled)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun hasSeenGIFMetaDataWarning(context: Context): Boolean {
|
||||||
|
return getBooleanPreference(context, GIF_METADATA_WARNING, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun setHasSeenGIFMetaDataWarning(context: Context) {
|
||||||
|
setBooleanPreference(context, GIF_METADATA_WARNING, true)
|
||||||
|
}
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun isGifSearchInGridLayout(context: Context): Boolean {
|
fun isGifSearchInGridLayout(context: Context): Boolean {
|
||||||
return getBooleanPreference(context, GIF_GRID_LAYOUT, false)
|
return getBooleanPreference(context, GIF_GRID_LAYOUT, false)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user