Merge branch 'dev'

This commit is contained in:
Harris 2021-09-27 09:36:55 +10:00
commit c67d164db6
15 changed files with 259 additions and 94 deletions

View File

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

View File

@ -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() {
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 ->
TextSecurePreferences.setHasSeenGIFMetaDataWarning(this)
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 }
val formattedTimestamp = DateUtils.getDisplayFormattedTimeSpanString(this, Locale.getDefault(), message.timestamp) if (messageSize > 1) {
builder.append("$formattedTimestamp: $body").append('\n') 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') { 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,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));
} }
} }
} }

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

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

View File

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

View File

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

View File

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

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

View File

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

View File

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