From 49b164a8c52cdffa345eb252c35b88488912ebc0 Mon Sep 17 00:00:00 2001 From: Harris Date: Thu, 2 Sep 2021 09:27:23 +1000 Subject: [PATCH 01/14] feat: make date breaks more dynamic, spaced out in five minute clusters --- .../securesms/conversation/v2/messages/VisibleMessageView.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageView.kt index 5e5b1da220..e57ea8035b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageView.kt @@ -59,6 +59,7 @@ class VisibleMessageView : LinearLayout { const val longPressMovementTreshold = 10.0f // dp const val longPressDurationThreshold = 250L // ms const val maxDoubleTapInterval = 200L + const val maxTimeBetweenBreaks = 5 * 60 * 1000L // 5 minutes } // region Lifecycle @@ -111,7 +112,7 @@ class VisibleMessageView : LinearLayout { senderNameTextView.visibility = View.GONE } // 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.text = if (showDateBreak) DateUtils.getDisplayFormattedTimeSpanString(context, Locale.getDefault(), message.timestamp) else "" // Timestamp From ded0370e8e376a9fecb349c517c0b99a54a68d06 Mon Sep 17 00:00:00 2001 From: Harris Date: Thu, 2 Sep 2021 10:19:43 +1000 Subject: [PATCH 02/14] feat: add gif metadata warning --- .../conversation/v2/ConversationActivityV2.kt | 23 +++++++++++++++---- .../utilities/TextSecurePreferences.kt | 10 ++++++++ 2 files changed, 28 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt index c25d157bb3..ce06e86c1a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt @@ -3,10 +3,7 @@ package org.thoughtcrime.securesms.conversation.v2 import android.Manifest import android.animation.FloatEvaluator import android.animation.ValueAnimator -import android.content.ClipData -import android.content.ClipboardManager -import android.content.Context -import android.content.Intent +import android.content.* import android.content.res.Resources import android.database.Cursor import android.graphics.Rect @@ -972,7 +969,23 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe } private fun showGIFPicker() { - AttachmentManager.selectGif(this, ConversationActivityV2.PICK_GIF) + 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 -> + AttachmentManager.selectGif(this, ConversationActivityV2.PICK_GIF) + dialog.dismiss() + } + builder.setNegativeButton( + "Cancel" + ) { dialog: DialogInterface, which: Int -> dialog.dismiss() } + builder.create().show() + TextSecurePreferences.setHasSeenGIFMetaDataWarning(this) + } else { + AttachmentManager.selectGif(this, ConversationActivityV2.PICK_GIF) + } } private fun showDocumentPicker() { diff --git a/libsession/src/main/java/org/session/libsession/utilities/TextSecurePreferences.kt b/libsession/src/main/java/org/session/libsession/utilities/TextSecurePreferences.kt index 7ac0feae6d..ab9bdcc08f 100644 --- a/libsession/src/main/java/org/session/libsession/utilities/TextSecurePreferences.kt +++ b/libsession/src/main/java/org/session/libsession/utilities/TextSecurePreferences.kt @@ -285,6 +285,16 @@ object TextSecurePreferences { setBooleanPreference(context, LINK_PREVIEWS, enabled) } + @JvmStatic + fun hasSeenGIFMetaDataWarning(context: Context): Boolean { + return getBooleanPreference(context, "has_seen_gif_metadata_warning", false) + } + + @JvmStatic + fun setHasSeenGIFMetaDataWarning(context: Context) { + setBooleanPreference(context, "has_seen_gif_metadata_warning", true) + } + @JvmStatic fun isGifSearchInGridLayout(context: Context): Boolean { return getBooleanPreference(context, GIF_GRID_LAYOUT, false) From adeffbdc0718d7f8879baf54e84188a8b427f646 Mon Sep 17 00:00:00 2001 From: Harris Date: Thu, 2 Sep 2021 10:21:14 +1000 Subject: [PATCH 03/14] feat: replace hardcoded with const string --- .../session/libsession/utilities/TextSecurePreferences.kt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/libsession/src/main/java/org/session/libsession/utilities/TextSecurePreferences.kt b/libsession/src/main/java/org/session/libsession/utilities/TextSecurePreferences.kt index ab9bdcc08f..8c67a36b80 100644 --- a/libsession/src/main/java/org/session/libsession/utilities/TextSecurePreferences.kt +++ b/libsession/src/main/java/org/session/libsession/utilities/TextSecurePreferences.kt @@ -80,6 +80,7 @@ object TextSecurePreferences { const val UNIVERSAL_UNIDENTIFIED_ACCESS = "pref_universal_unidentified_access" const val TYPING_INDICATORS = "pref_typing_indicators" 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" const val IS_USING_FCM = "pref_is_using_fcm" private const val FCM_TOKEN = "pref_fcm_token" @@ -287,12 +288,12 @@ object TextSecurePreferences { @JvmStatic fun hasSeenGIFMetaDataWarning(context: Context): Boolean { - return getBooleanPreference(context, "has_seen_gif_metadata_warning", false) + return getBooleanPreference(context, GIF_METADATA_WARNING, false) } @JvmStatic fun setHasSeenGIFMetaDataWarning(context: Context) { - setBooleanPreference(context, "has_seen_gif_metadata_warning", true) + setBooleanPreference(context, GIF_METADATA_WARNING, true) } @JvmStatic From b1535940f9f9f258366db3707335913e6b751cee Mon Sep 17 00:00:00 2001 From: Harris Date: Fri, 3 Sep 2021 13:53:49 +1000 Subject: [PATCH 04/14] refactor: move the set has seen to positive button handler --- .../securesms/conversation/v2/ConversationActivityV2.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt index ce06e86c1a..c439862bac 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt @@ -975,6 +975,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe 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) dialog.dismiss() } @@ -982,7 +983,6 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe "Cancel" ) { dialog: DialogInterface, which: Int -> dialog.dismiss() } builder.create().show() - TextSecurePreferences.setHasSeenGIFMetaDataWarning(this) } else { AttachmentManager.selectGif(this, ConversationActivityV2.PICK_GIF) } From 2b6107d86867c9bf1f712c5f32d314e24db956ea Mon Sep 17 00:00:00 2001 From: Harris Date: Mon, 13 Sep 2021 13:45:55 +1000 Subject: [PATCH 05/14] fix: expiring messages across multi-device --- .../v2/components/ExpirationTimerView.java | 28 ++++++++++++------- .../v2/messages/VisibleMessageView.kt | 7 ++++- 2 files changed, 24 insertions(+), 11 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/components/ExpirationTimerView.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/components/ExpirationTimerView.java index 5a04e77ac2..6765232c77 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/components/ExpirationTimerView.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/components/ExpirationTimerView.java @@ -1,16 +1,18 @@ package org.thoughtcrime.securesms.conversation.v2.components; import android.content.Context; +import android.util.AttributeSet; + import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import android.util.AttributeSet; -import network.loki.messenger.R; import org.session.libsession.utilities.Util; import java.lang.ref.WeakReference; import java.util.concurrent.TimeUnit; +import network.loki.messenger.R; + public class ExpirationTimerView extends androidx.appcompat.widget.AppCompatImageView { private long startedAt; @@ -86,10 +88,12 @@ public class ExpirationTimerView extends androidx.appcompat.widget.AppCompatImag long progressed = System.currentTimeMillis() - startedAt; long remaining = expiresIn - progressed; - if (remaining < TimeUnit.SECONDS.toMillis(30)) { - return 50; - } else { + if (remaining <= 0) { + return 0; + } else if (remaining < TimeUnit.SECONDS.toMillis(30)) { return 1000; + } else { + return 5000; } } @@ -106,16 +110,20 @@ public class ExpirationTimerView extends androidx.appcompat.widget.AppCompatImag ExpirationTimerView timerView = expirationTimerViewReference.get(); if (timerView == null) return; - timerView.setExpirationTime(timerView.startedAt, timerView.expiresIn); - + long nextUpdate = timerView.calculateAnimationDelay(timerView.startedAt, timerView.expiresIn); 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; return; } } - - Util.runOnMainDelayed(this, timerView.calculateAnimationDelay(timerView.startedAt, timerView.expiresIn)); + Util.runOnMainDelayed(this, nextUpdate); } } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageView.kt index 5e5b1da220..ac945c5fc4 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageView.kt @@ -217,7 +217,9 @@ class VisibleMessageView : LinearLayout { if (message.expireStarted + message.expiresIn <= System.currentTimeMillis()) { ApplicationContext.getInstance(context).expiringMessageManager.checkSchedule() } - } else if (!message.isOutgoing && !message.isMediaPending) { + } else if (!message.isMediaPending) { + expirationTimerView.setPercentComplete(0.0f) + expirationTimerView.stopAnimation() ThreadUtils.queue { val expirationManager = ApplicationContext.getInstance(context).expiringMessageManager val id = message.getId() @@ -225,6 +227,9 @@ class VisibleMessageView : LinearLayout { if (mms) DatabaseFactory.getMmsDatabase(context).markExpireStarted(id) else DatabaseFactory.getSmsDatabase(context).markExpireStarted(id) expirationManager.scheduleDeletion(id, mms, message.expiresIn) } + } else { + expirationTimerView.stopAnimation() + expirationTimerView.setPercentComplete(0.0f) } } else { expirationTimerView.isVisible = false From 66e95787a27a6dcdc961b0163ef4223e135dae98 Mon Sep 17 00:00:00 2001 From: Harris Date: Tue, 14 Sep 2021 10:27:34 +1000 Subject: [PATCH 06/14] feat: add bottom sheet modal url with copy option --- .../conversation/v2/ModalUrlBottomSheet.kt | 72 +++++++++++++++++++ .../conversation/v2/dialogs/OpenURLDialog.kt | 40 ----------- .../v2/messages/LinkPreviewView.kt | 10 +-- .../v2/messages/VisibleMessageContentView.kt | 4 +- .../fragment_modal_url_bottom_sheet.xml | 58 +++++++++++++++ 5 files changed, 135 insertions(+), 49 deletions(-) create mode 100644 app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ModalUrlBottomSheet.kt delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/conversation/v2/dialogs/OpenURLDialog.kt create mode 100644 app/src/main/res/layout/fragment_modal_url_bottom_sheet.xml diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ModalUrlBottomSheet.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ModalUrlBottomSheet.kt new file mode 100644 index 0000000000..859f208a52 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ModalUrlBottomSheet.kt @@ -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() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/dialogs/OpenURLDialog.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/dialogs/OpenURLDialog.kt deleted file mode 100644 index ea0230f578..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/dialogs/OpenURLDialog.kt +++ /dev/null @@ -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() - } -} \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/LinkPreviewView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/LinkPreviewView.kt index 0457f82702..45921122f2 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/LinkPreviewView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/LinkPreviewView.kt @@ -3,7 +3,6 @@ package org.thoughtcrime.securesms.conversation.v2.messages import android.content.Context import android.graphics.Canvas import android.graphics.Rect -import android.text.method.LinkMovementMethod import android.util.AttributeSet import android.view.LayoutInflater import android.view.MotionEvent @@ -11,20 +10,17 @@ import android.widget.LinearLayout import android.widget.TextView import androidx.appcompat.app.AppCompatActivity import androidx.core.content.res.ResourcesCompat -import androidx.core.text.getSpans -import androidx.core.text.toSpannable import androidx.core.view.isVisible import kotlinx.android.synthetic.main.view_link_preview.view.* import network.loki.messenger.R 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.ModalURLSpan import org.thoughtcrime.securesms.conversation.v2.utilities.TextUtilities.getIntersectedModalSpans import org.thoughtcrime.securesms.database.model.MmsMessageRecord -import org.thoughtcrime.securesms.util.UiModeUtilities import org.thoughtcrime.securesms.mms.GlideRequests import org.thoughtcrime.securesms.mms.ImageSlide +import org.thoughtcrime.securesms.util.UiModeUtilities class LinkPreviewView : LinearLayout { private val cornerMask by lazy { CornerMask(this) } @@ -97,7 +93,7 @@ class LinkPreviewView : LinearLayout { fun openURL() { val url = this.url ?: return val activity = context as AppCompatActivity - OpenURLDialog(url).show(activity.supportFragmentManager, "Open URL Dialog") + ModalUrlBottomSheet(url).show(activity.supportFragmentManager, "Open URL Dialog") } // endregion } \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageContentView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageContentView.kt index 38831ed5ab..f98b3a23dd 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageContentView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageContentView.kt @@ -31,8 +31,8 @@ import org.session.libsession.utilities.ViewUtil import org.session.libsession.utilities.recipients.Recipient import org.thoughtcrime.securesms.components.emoji.EmojiTextView 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.dialogs.OpenURLDialog import org.thoughtcrime.securesms.conversation.v2.utilities.MentionUtilities import org.thoughtcrime.securesms.conversation.v2.utilities.ModalURLSpan 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 replacementSpan = ModalURLSpan(updatedUrl) { url -> 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 end = body.getSpanEnd(urlSpan) diff --git a/app/src/main/res/layout/fragment_modal_url_bottom_sheet.xml b/app/src/main/res/layout/fragment_modal_url_bottom_sheet.xml new file mode 100644 index 0000000000..dd5907d679 --- /dev/null +++ b/app/src/main/res/layout/fragment_modal_url_bottom_sheet.xml @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + \ No newline at end of file From 790436bad85a9d5d74da42d45672f85f313e1f51 Mon Sep 17 00:00:00 2001 From: Harris Date: Tue, 14 Sep 2021 14:14:42 +1000 Subject: [PATCH 07/14] refactor: change the "copy" text to "copy URL" for added context --- app/src/main/res/layout/fragment_modal_url_bottom_sheet.xml | 5 ++--- app/src/main/res/values/strings.xml | 1 + 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/main/res/layout/fragment_modal_url_bottom_sheet.xml b/app/src/main/res/layout/fragment_modal_url_bottom_sheet.xml index dd5907d679..8fb7c17f87 100644 --- a/app/src/main/res/layout/fragment_modal_url_bottom_sheet.xml +++ b/app/src/main/res/layout/fragment_modal_url_bottom_sheet.xml @@ -7,8 +7,7 @@ app:behavior_hideable="true" app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior" android:gravity="center_horizontal" - android:paddingVertical="@dimen/large_spacing" - > + android:paddingVertical="@dimen/large_spacing"> Open URL? Are you sure you want to open %s? Open + Copy URL Enable Link Previews? 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. From 792dc2752c29659da99388e9fc6b5d666b05d0aa Mon Sep 17 00:00:00 2001 From: Harris Date: Tue, 14 Sep 2021 14:15:40 +1000 Subject: [PATCH 08/14] fix: remove bottom padding in layout --- app/src/main/res/layout/fragment_modal_url_bottom_sheet.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/res/layout/fragment_modal_url_bottom_sheet.xml b/app/src/main/res/layout/fragment_modal_url_bottom_sheet.xml index 8fb7c17f87..190d4ef0c7 100644 --- a/app/src/main/res/layout/fragment_modal_url_bottom_sheet.xml +++ b/app/src/main/res/layout/fragment_modal_url_bottom_sheet.xml @@ -7,7 +7,7 @@ app:behavior_hideable="true" app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior" android:gravity="center_horizontal" - android:paddingVertical="@dimen/large_spacing"> + android:paddingTop="@dimen/large_spacing"> Date: Wed, 15 Sep 2021 11:04:43 +1000 Subject: [PATCH 09/14] fix: copy message fixed for single messages --- .../conversation/v2/ConversationActivityV2.kt | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt index c439862bac..e0453e10c7 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt @@ -1338,12 +1338,21 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe override fun copyMessages(messages: Set) { val sortedMessages = messages.sortedBy { it.dateSent } + val messageSize = sortedMessages.size 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) if (TextUtils.isEmpty(body)) { continue } - val formattedTimestamp = DateUtils.getDisplayFormattedTimeSpanString(this, Locale.getDefault(), message.timestamp) - builder.append("$formattedTimestamp: $body").append('\n') + if (messageSize > 1) { + val formattedTimestamp = DateUtils.getDisplayFormattedTimeSpanString(this, Locale.getDefault(), message.timestamp) + builder.append("$formattedTimestamp: ") + } + builder.append(body) + if (messageIterator.hasNext()) { + builder.append('\n') + } } if (builder.isNotEmpty() && builder[builder.length - 1] == '\n') { builder.deleteCharAt(builder.length - 1) From 45a6e11cf595faeef0014088b22acf198cc7aeae Mon Sep 17 00:00:00 2001 From: Harris Date: Wed, 15 Sep 2021 16:08:40 +1000 Subject: [PATCH 10/14] fix: checking if group exists and not adding it for self sends. may require more testing to see if it can return in the groupExists block above insert outgoing messages fixes #723 --- .../ReceivedMessageHandler.kt | 20 ++++++++----------- 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/ReceivedMessageHandler.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/ReceivedMessageHandler.kt index de2b8a349c..a99c268687 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/ReceivedMessageHandler.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/ReceivedMessageHandler.kt @@ -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.quotes.QuoteModel import org.session.libsession.snode.SnodeAPI -import org.session.libsession.utilities.Address -import org.session.libsession.utilities.GroupRecord +import org.session.libsession.utilities.* 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.DjbECPublicKey import org.session.libsignal.crypto.ecc.ECKeyPair -import org.session.libsignal.utilities.guava.Optional import org.session.libsignal.messages.SignalServiceGroup 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.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.util.* import kotlin.collections.ArrayList @@ -285,7 +280,8 @@ private fun handleNewClosedGroup(sender: String, sentTimestamp: Long, groupPubli val userPublicKey = TextSecurePreferences.getLocalNumber(context) // Create the group val groupID = GroupUtil.doubleEncodeGroupID(groupPublicKey) - if (storage.getGroup(groupID) != null) { + val groupExists = storage.getGroup(groupID) != null + if (groupExists) { // Update the group if (!storage.isGroupActive(groupPublicKey)) { // 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 PushNotificationAPI.performOperation(PushNotificationAPI.ClosedGroupOperation.Subscribe, groupPublicKey, storage.getUserPublicKey()!!) // Notify the user - if (userPublicKey == sender) { + if (userPublicKey == sender && !groupExists) { val threadID = storage.getOrCreateThreadIdFor(Address.fromSerialized(groupID)) 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) } // Start polling From cb5b9cc575fcb90aa3f5cf686fdb866c49f9053e Mon Sep 17 00:00:00 2001 From: Harris Date: Wed, 15 Sep 2021 16:55:51 +1000 Subject: [PATCH 11/14] fix: display threads as read when the last message is from us triggered by multi device purely visual in the ConversationView's bind() method --- .../securesms/home/ConversationView.kt | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/home/ConversationView.kt b/app/src/main/java/org/thoughtcrime/securesms/home/ConversationView.kt index 28531ff4cc..dbc5710ade 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/home/ConversationView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/home/ConversationView.kt @@ -46,14 +46,20 @@ class ConversationView : LinearLayout { accentView.visibility = View.VISIBLE } else { 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 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) + unreadCountIndicator.isVisible = (unreadCount != 0 && !thread.isRead) val senderDisplayName = getUserDisplayName(thread.recipient) ?: thread.recipient.address.toString() conversationViewDisplayNameTextView.text = senderDisplayName @@ -69,7 +75,7 @@ class ConversationView : LinearLayout { val rawSnippet = thread.getDisplayBody(context) val snippet = highlightMentions(rawSnippet, thread.threadId, context) 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 if (isTyping) { typingIndicatorView.startAnimation() From ecf2308e84d41fc4a98e6fdd877c69e0b76b8c22 Mon Sep 17 00:00:00 2001 From: Harris Date: Fri, 17 Sep 2021 16:44:32 +1000 Subject: [PATCH 12/14] feat: add survey button --- .../securesms/preferences/SettingsActivity.kt | 17 ++++++++++++----- app/src/main/res/layout/activity_settings.xml | 15 +++++++++++++++ app/src/main/res/values/strings.xml | 1 + 3 files changed, 28 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/SettingsActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/preferences/SettingsActivity.kt index bc0c48eaf5..de059b217b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/SettingsActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/SettingsActivity.kt @@ -33,15 +33,11 @@ import org.session.libsession.utilities.SSKEnvironment.ProfileManagerProtocol import org.session.libsession.utilities.TextSecurePreferences import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity 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.GlideRequests import org.thoughtcrime.securesms.permissions.Permissions import org.thoughtcrime.securesms.profiles.ProfileMediaConstraints -import org.thoughtcrime.securesms.util.BitmapDecodingException -import org.thoughtcrime.securesms.util.BitmapUtil +import org.thoughtcrime.securesms.util.* import java.io.File import java.security.SecureRandom import java.util.* @@ -85,6 +81,7 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() { chatsButton.setOnClickListener { showChatSettings() } sendInvitationButton.setOnClickListener { sendInvitation() } faqButton.setOnClickListener { showFAQ() } + surveyButton.setOnClickListener { showSurvey() } helpTranslateButton.setOnClickListener { helpTranslate() } seedButton.setOnClickListener { showSeed() } 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() { try { val url = "https://crowdin.com/project/session-android" diff --git a/app/src/main/res/layout/activity_settings.xml b/app/src/main/res/layout/activity_settings.xml index 0e8bf779ff..39c229df9c 100644 --- a/app/src/main/res/layout/activity_settings.xml +++ b/app/src/main/res/layout/activity_settings.xml @@ -193,6 +193,7 @@ android:background="?android:dividerHorizontal" /> + + Delete just for me Delete for everyone Delete for me and %s + Feedback/Survey From b3a850a876834ad3e0575dc6d6c4aadd3436564d Mon Sep 17 00:00:00 2001 From: Harris Date: Tue, 21 Sep 2021 14:50:25 +1000 Subject: [PATCH 13/14] feat: instantly scroll to bottom if already smooth scrolling on button tap fixes #736 --- .../securesms/conversation/v2/ConversationActivityV2.kt | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt index e0453e10c7..b536e36ea8 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt @@ -234,7 +234,14 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe setUpLinkPreviewObserver() restoreDraftIfNeeded() 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) updateUnreadCountIndicator() setUpTypingObserver() From 5a290ddf68de8f7fa1549827f0aa1ed770cd8a48 Mon Sep 17 00:00:00 2001 From: Harris Date: Tue, 21 Sep 2021 14:51:58 +1000 Subject: [PATCH 14/14] build: bump the version number --- app/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index addf8c5c70..89327b29d5 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -153,8 +153,8 @@ dependencies { testImplementation 'org.robolectric:shadows-multidex:4.4' } -def canonicalVersionCode = 221 -def canonicalVersionName = "1.11.9" +def canonicalVersionCode = 222 +def canonicalVersionName = "1.11.10" def postFixSize = 10 def abiPostFix = ['armeabi-v7a' : 1,