Merge tag 'v6.9.2' into molly-6.9

This commit is contained in:
Oscar Mira 2023-01-26 16:50:22 +01:00
commit b65a9b0728
249 changed files with 2396 additions and 1606 deletions

View File

@ -20,7 +20,7 @@ RUN yes | sdkmanager --licenses
RUN sdkmanager "platform-tools"
ARG ANDROID_API_LEVEL=32
ARG ANDROID_API_LEVEL=33
ARG ANDROID_BUILD_TOOLS_VERSION=32.0.0
RUN sdkmanager "platforms;android-${ANDROID_API_LEVEL}"

View File

@ -83,8 +83,8 @@ ktlint {
version = "0.43.2"
}
def canonicalVersionCode = 1196
def canonicalVersionName = "6.8.3"
def canonicalVersionCode = 1199
def canonicalVersionName = "6.9.2"
def mollyRevision = 0
def postFixSize = 100

View File

@ -86,9 +86,10 @@
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
tools:replace="android:allowBackup"
android:resizeableActivity="true"
android:allowBackup="false"
android:fullBackupOnly="false"
android:allowBackup="true"
android:backupAgent=".absbackup.SignalBackupAgent"
android:theme="@style/TextSecure.LightTheme"
android:largeHeap="true">

View File

@ -4,8 +4,6 @@ import android.content.Context;
import android.net.Uri;
import android.provider.DocumentsContract;
import androidx.annotation.RequiresApi;
import org.signal.core.util.logging.Log;
/**
@ -22,7 +20,6 @@ public class DocumentFileHelper {
*
* @return true if rename successful
*/
@RequiresApi(21)
public static boolean renameTo(Context context, DocumentFile documentFile, String displayName) {
if (documentFile instanceof TreeDocumentFile) {
Log.d(TAG, "Renaming document directly");

View File

@ -5,7 +5,6 @@ import android.content.Context
import android.content.Intent
import android.os.Build
import androidx.activity.result.contract.ActivityResultContract
import androidx.annotation.RequiresApi
import androidx.biometric.BiometricManager
import androidx.biometric.BiometricPrompt
import androidx.biometric.BiometricPrompt.PromptInfo
@ -44,7 +43,7 @@ class BiometricDeviceAuthentication(
Log.i(TAG, "Skipping show system biometric or device lock dialog unless forced")
}
true
} else if (Build.VERSION.SDK_INT >= 21) {
} else {
if (force) {
Log.i(TAG, "firing intent...")
showConfirmDeviceCredentialIntent()
@ -52,9 +51,6 @@ class BiometricDeviceAuthentication(
Log.i(TAG, "Skipping firing intent unless forced")
}
true
} else {
Log.w(TAG, "Not compatible...")
false
}
}
@ -65,7 +61,6 @@ class BiometricDeviceAuthentication(
class BiometricDeviceLockContract : ActivityResultContract<String, Int>() {
@RequiresApi(api = 21)
override fun createIntent(context: Context, input: String): Intent {
val keyguardManager = ServiceUtil.getKeyguardManager(context)
return keyguardManager.createConfirmDeviceCredentialIntent(input, "")

View File

@ -142,8 +142,8 @@ public class DeviceActivity extends PassphraseRequiredActivity
private void goToDeviceLink(@Nullable Uri uri) {
deviceLinkFragment.setLinkClickedListener(uri, DeviceActivity.this);
deviceAddFragment.setSharedElementReturnTransition(TransitionInflater.from(DeviceActivity.this).inflateTransition(R.transition.fragment_shared));
deviceAddFragment.setExitTransition(TransitionInflater.from(DeviceActivity.this).inflateTransition(android.R.transition.fade));
deviceAddFragment.setSharedElementReturnTransition(TransitionInflater.from(DeviceActivity.this).inflateTransition(R.transition.fragment_shared));
deviceAddFragment.setExitTransition(TransitionInflater.from(DeviceActivity.this).inflateTransition(android.R.transition.fade));
deviceLinkFragment.setSharedElementEnterTransition(TransitionInflater.from(DeviceActivity.this).inflateTransition(R.transition.fragment_shared));
deviceLinkFragment.setEnterTransition(TransitionInflater.from(DeviceActivity.this).inflateTransition(android.R.transition.fade));

View File

@ -52,22 +52,19 @@ public class DeviceAddFragment extends LoggingFragment {
this.devicesImage = container.findViewById(R.id.devices);
ViewCompat.setTransitionName(devicesImage, "devices");
if (Build.VERSION.SDK_INT >= 21) {
container.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
@TargetApi(21)
@Override
public void onLayoutChange(View v, int left, int top, int right, int bottom,
int oldLeft, int oldTop, int oldRight, int oldBottom)
{
v.removeOnLayoutChangeListener(this);
container.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
@Override
public void onLayoutChange(View v, int left, int top, int right, int bottom,
int oldLeft, int oldTop, int oldRight, int oldBottom)
{
v.removeOnLayoutChangeListener(this);
Animator reveal = ViewAnimationUtils.createCircularReveal(v, right, bottom, 0, (int) Math.hypot(right, bottom));
reveal.setInterpolator(new DecelerateInterpolator(2f));
reveal.setDuration(800);
reveal.start();
}
});
}
Animator reveal = ViewAnimationUtils.createCircularReveal(v, right, bottom, 0, (int) Math.hypot(right, bottom));
reveal.setInterpolator(new DecelerateInterpolator(2f));
reveal.setDuration(800);
reveal.start();
}
});
return container;
}

View File

@ -0,0 +1,11 @@
package org.thoughtcrime.securesms.absbackup
/**
* Abstracts away the implementation of pieces of data we want to hand off to various backup services.
* Here we can control precisely which data gets backed up and more importantly, what does not.
*/
interface AndroidBackupItem {
fun getKey(): String
fun getDataForBackup(): ByteArray
fun restoreData(data: ByteArray)
}

View File

@ -0,0 +1,65 @@
package org.thoughtcrime.securesms.absbackup
import android.app.backup.BackupAgent
import android.app.backup.BackupDataInput
import android.app.backup.BackupDataOutput
import android.os.ParcelFileDescriptor
import org.signal.core.util.logging.Log
import org.thoughtcrime.securesms.absbackup.backupables.KbsAuthTokens
import java.io.DataInputStream
import java.io.DataOutputStream
import java.io.FileInputStream
import java.io.FileOutputStream
import java.io.IOException
/**
* Uses the [Android Backup Service](https://developer.android.com/guide/topics/data/keyvaluebackup) and backs up everything in [items]
*/
class SignalBackupAgent : BackupAgent() {
private val items: List<AndroidBackupItem> = listOf(
KbsAuthTokens,
)
override fun onBackup(oldState: ParcelFileDescriptor?, data: BackupDataOutput, newState: ParcelFileDescriptor) {
val contentsHash = cumulativeHashCode()
if (oldState == null) {
performBackup(data)
} else {
val hash = try {
DataInputStream(FileInputStream(oldState.fileDescriptor)).use { it.readInt() }
} catch (e: IOException) {
Log.w(TAG, "No old state, may be first backup request or bug with not writing to newState at end.", e)
}
if (hash != contentsHash) {
performBackup(data)
}
}
DataOutputStream(FileOutputStream(newState.fileDescriptor)).use { it.writeInt(contentsHash) }
}
private fun performBackup(data: BackupDataOutput) {
items.forEach {
val backupData = it.getDataForBackup()
data.writeEntityHeader(it.getKey(), backupData.size)
data.writeEntityData(backupData, backupData.size)
}
}
override fun onRestore(dataInput: BackupDataInput, appVersionCode: Int, newState: ParcelFileDescriptor) {
while (dataInput.readNextHeader()) {
val buffer = ByteArray(dataInput.dataSize)
dataInput.readEntityData(buffer, 0, dataInput.dataSize)
items.find { dataInput.key == it.getKey() }?.restoreData(buffer)
}
DataOutputStream(FileOutputStream(newState.fileDescriptor)).use { it.writeInt(cumulativeHashCode()) }
}
private fun cumulativeHashCode(): Int {
return items.fold("") { acc: String, androidBackupItem: AndroidBackupItem -> acc + androidBackupItem.getDataForBackup().decodeToString() }.hashCode()
}
companion object {
private const val TAG = "SignalBackupAgent"
}
}

View File

@ -0,0 +1,40 @@
package org.thoughtcrime.securesms.absbackup.backupables
import com.google.protobuf.InvalidProtocolBufferException
import org.signal.core.util.logging.Log
import org.thoughtcrime.securesms.absbackup.AndroidBackupItem
import org.thoughtcrime.securesms.absbackup.ExternalBackupProtos
import org.thoughtcrime.securesms.keyvalue.SignalStore
/**
* This backs up the not-secret KBS Auth tokens, which can be combined with a PIN to prove ownership of a phone number in order to complete the registration process.
*/
object KbsAuthTokens : AndroidBackupItem {
private const val TAG = "KbsAuthTokens"
override fun getKey(): String {
return TAG
}
override fun getDataForBackup(): ByteArray {
val registrationRecoveryTokenList = SignalStore.kbsValues().kbsAuthTokenList
val proto = ExternalBackupProtos.KbsAuthToken.newBuilder()
.addAllToken(registrationRecoveryTokenList)
.build()
return proto.toByteArray()
}
override fun restoreData(data: ByteArray) {
if (SignalStore.kbsValues().kbsAuthTokenList.isNotEmpty()) {
return
}
try {
val proto = ExternalBackupProtos.KbsAuthToken.parseFrom(data)
SignalStore.kbsValues().putAuthTokenList(proto.tokenList)
} catch (e: InvalidProtocolBufferException) {
Log.w(TAG, "Cannot restore KbsAuthToken from backup service.")
}
}
}

View File

@ -13,7 +13,6 @@ import android.view.ViewGroup
import android.view.animation.AccelerateInterpolator
import android.view.animation.DecelerateInterpolator
import android.view.animation.Interpolator
import androidx.annotation.RequiresApi
private const val POSITION_ON_SCREEN = "signal.circleavatartransition.positiononscreen"
private const val WIDTH = "signal.circleavatartransition.width"
@ -22,7 +21,6 @@ private const val HEIGHT = "signal.circleavatartransition.height"
/**
* Custom transition for Circular avatars, because once you have multiple things animating stuff was getting broken and weird.
*/
@RequiresApi(21)
class CircleAvatarTransition(context: Context, attrs: AttributeSet?) : Transition(context, attrs) {
override fun captureStartValues(transitionValues: TransitionValues) {
captureValues(transitionValues)

View File

@ -7,11 +7,9 @@ import android.transition.Transition
import android.transition.TransitionValues
import android.util.AttributeSet
import android.view.ViewGroup
import androidx.annotation.RequiresApi
import androidx.core.animation.doOnEnd
import androidx.core.animation.doOnStart
@RequiresApi(21)
class CrossfaderTransition(context: Context, attrs: AttributeSet?) : Transition(context, attrs) {
companion object {

View File

@ -10,7 +10,6 @@ import android.transition.TransitionValues
import android.util.AttributeSet
import android.view.View
import android.view.ViewGroup
import androidx.annotation.RequiresApi
import androidx.core.animation.addListener
import androidx.fragment.app.FragmentContainerView
@ -19,7 +18,6 @@ private const val BOUNDS = "signal.wipedowntransition.bottom"
/**
* WipeDownTransition will animate the bottom position of a view such that it "wipes" down the screen to a final position.
*/
@RequiresApi(21)
class WipeDownTransition(context: Context, attrs: AttributeSet?) : Transition(context, attrs) {
override fun captureStartValues(transitionValues: TransitionValues) {
captureValues(transitionValues)

View File

@ -248,10 +248,6 @@ public class ComposeText extends EmojiEditText {
editorInfo.imeOptions &= ~EditorInfo.IME_FLAG_NO_ENTER_ACTION;
}
if (Build.VERSION.SDK_INT < 21) {
return inputConnection;
}
if (mediaListener == null) {
return inputConnection;
}

View File

@ -41,7 +41,6 @@ class SignalBottomActionBar(context: Context, attributeSet: AttributeSet) : Line
init {
orientation = HORIZONTAL
setBackgroundResource(R.drawable.signal_bottom_action_bar_background)
elevation = 20f
}

View File

@ -19,15 +19,8 @@ import java.util.List;
public class ExpiredBuildReminder extends Reminder {
public ExpiredBuildReminder(final Context context) {
super(null, Build.VERSION.SDK_INT < 21
? context.getString(R.string.ExpiredBuildReminder_api_19_message)
: context.getString(R.string.ExpiredBuildReminder_this_version_of_signal_has_expired));
if (Build.VERSION.SDK_INT < 21) {
addAction(new Action(context.getString(R.string.API19Reminder_learn_more), R.id.reminder_action_api_19_learn_more));
} else {
addAction(new Action(context.getString(R.string.ExpiredBuildReminder_update_now), R.id.reminder_action_update_now));
}
super(null, context.getString(R.string.ExpiredBuildReminder_this_version_of_signal_has_expired));
addAction(new Action(context.getString(R.string.ExpiredBuildReminder_update_now), R.id.reminder_action_update_now));
}
@Override

View File

@ -1,5 +1,7 @@
package org.thoughtcrime.securesms.components.settings
import android.graphics.PorterDuff
import android.graphics.PorterDuffColorFilter
import android.text.Spanned
import android.text.method.LinkMovementMethod
import android.text.style.ClickableSpan
@ -230,7 +232,9 @@ class ExternalLinkPreferenceViewHolder(itemView: View) : PreferenceViewHolder<Ex
override fun bind(model: ExternalLinkPreference) {
super.bind(model)
val externalLinkIcon = requireNotNull(ContextCompat.getDrawable(context, R.drawable.ic_open_20))
val externalLinkIcon = requireNotNull(ContextCompat.getDrawable(context, R.drawable.symbol_open_20)).apply {
colorFilter = PorterDuffColorFilter(ContextCompat.getColor(context, R.color.signal_icon_tint_primary), PorterDuff.Mode.SRC_IN)
}
externalLinkIcon.setBounds(0, 0, ViewUtil.dpToPx(20), ViewUtil.dpToPx(20))
if (ViewUtil.isLtr(itemView)) {

View File

@ -47,7 +47,7 @@ class AppSettingsFragment : DSLSettingsFragment(R.string.text_secure_normal__men
clickPref(
title = DSLSettingsText.from(R.string.AccountSettingsFragment__account),
icon = DSLSettingsIcon.from(R.drawable.ic_profile_circle_24),
icon = DSLSettingsIcon.from(R.drawable.symbol_person_circle_24),
onClick = {
findNavController().safeNavigate(R.id.action_appSettingsFragment_to_accountSettingsFragment)
}
@ -55,7 +55,7 @@ class AppSettingsFragment : DSLSettingsFragment(R.string.text_secure_normal__men
clickPref(
title = DSLSettingsText.from(R.string.preferences__linked_devices),
icon = DSLSettingsIcon.from(R.drawable.ic_linked_devices_24),
icon = DSLSettingsIcon.from(R.drawable.symbol_devices_24),
onClick = {
findNavController().safeNavigate(R.id.action_appSettingsFragment_to_deviceActivity)
}
@ -63,7 +63,7 @@ class AppSettingsFragment : DSLSettingsFragment(R.string.text_secure_normal__men
externalLinkPref(
title = DSLSettingsText.from(R.string.preferences__donate_to_signal),
icon = DSLSettingsIcon.from(R.drawable.ic_heart_24),
icon = DSLSettingsIcon.from(R.drawable.symbol_heart_24),
linkId = R.string.donate_url
)
@ -71,7 +71,7 @@ class AppSettingsFragment : DSLSettingsFragment(R.string.text_secure_normal__men
clickPref(
title = DSLSettingsText.from(R.string.preferences__appearance),
icon = DSLSettingsIcon.from(R.drawable.ic_appearance_24),
icon = DSLSettingsIcon.from(R.drawable.symbol_appearance_24),
onClick = {
findNavController().safeNavigate(R.id.action_appSettingsFragment_to_appearanceSettingsFragment)
}
@ -79,7 +79,7 @@ class AppSettingsFragment : DSLSettingsFragment(R.string.text_secure_normal__men
clickPref(
title = DSLSettingsText.from(R.string.preferences_chats__chats),
icon = DSLSettingsIcon.from(R.drawable.ic_chat_message_24),
icon = DSLSettingsIcon.from(R.drawable.symbol_chat_24),
onClick = {
findNavController().safeNavigate(R.id.action_appSettingsFragment_to_chatsSettingsFragment)
}
@ -87,7 +87,7 @@ class AppSettingsFragment : DSLSettingsFragment(R.string.text_secure_normal__men
clickPref(
title = DSLSettingsText.from(R.string.preferences__stories),
icon = DSLSettingsIcon.from(R.drawable.ic_stories_24),
icon = DSLSettingsIcon.from(R.drawable.symbol_stories_24),
onClick = {
findNavController().safeNavigate(AppSettingsFragmentDirections.actionAppSettingsFragmentToStoryPrivacySettings(R.string.preferences__stories))
}
@ -95,7 +95,7 @@ class AppSettingsFragment : DSLSettingsFragment(R.string.text_secure_normal__men
clickPref(
title = DSLSettingsText.from(R.string.preferences__notifications),
icon = DSLSettingsIcon.from(R.drawable.ic_bell_24),
icon = DSLSettingsIcon.from(R.drawable.symbol_bell_24),
onClick = {
findNavController().safeNavigate(R.id.action_appSettingsFragment_to_notificationsSettingsFragment)
}
@ -103,7 +103,7 @@ class AppSettingsFragment : DSLSettingsFragment(R.string.text_secure_normal__men
clickPref(
title = DSLSettingsText.from(R.string.preferences__privacy),
icon = DSLSettingsIcon.from(R.drawable.ic_lock_24),
icon = DSLSettingsIcon.from(R.drawable.symbol_lock_24),
onClick = {
findNavController().safeNavigate(R.id.action_appSettingsFragment_to_privacySettingsFragment)
}
@ -119,7 +119,7 @@ class AppSettingsFragment : DSLSettingsFragment(R.string.text_secure_normal__men
clickPref(
title = DSLSettingsText.from(R.string.preferences__data_and_storage),
icon = DSLSettingsIcon.from(R.drawable.ic_archive_24dp),
icon = DSLSettingsIcon.from(R.drawable.symbol_data_24),
onClick = {
findNavController().safeNavigate(R.id.action_appSettingsFragment_to_dataAndStorageSettingsFragment)
}
@ -141,7 +141,7 @@ class AppSettingsFragment : DSLSettingsFragment(R.string.text_secure_normal__men
clickPref(
title = DSLSettingsText.from(R.string.preferences__help),
icon = DSLSettingsIcon.from(R.drawable.ic_help_24),
icon = DSLSettingsIcon.from(R.drawable.symbol_help_24),
onClick = {
findNavController().safeNavigate(R.id.action_appSettingsFragment_to_helpSettingsFragment)
}
@ -149,7 +149,7 @@ class AppSettingsFragment : DSLSettingsFragment(R.string.text_secure_normal__men
clickPref(
title = DSLSettingsText.from(R.string.AppSettingsFragment__invite_your_friends),
icon = DSLSettingsIcon.from(R.drawable.ic_invite_24),
icon = DSLSettingsIcon.from(R.drawable.symbol_invite_24),
onClick = {
findNavController().safeNavigate(R.id.action_appSettingsFragment_to_inviteActivity)
}

View File

@ -178,7 +178,7 @@ class EditNotificationProfileFragment : DSLSettingsFragment(layoutId = R.layout.
emojiView?.setImageDrawable(drawable)
viewModel.onEmojiSelected(emoji)
} else {
emojiView?.setImageResource(R.drawable.ic_add_emoji)
emojiView?.setImageResource(R.drawable.symbol_emoji_plus_24)
viewModel.onEmojiSelected("")
}
}

View File

@ -28,8 +28,7 @@ class VoiceNoteProximityWakeLockManager(
private val mediaController: MediaControllerCompat
) : DefaultLifecycleObserver {
private val wakeLock: PowerManager.WakeLock? =
ServiceUtil.getPowerManager(activity.applicationContext).newWakeLock(PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK, TAG)
private val wakeLock: PowerManager.WakeLock? = ServiceUtil.getPowerManager(activity.applicationContext).newWakeLock(PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK, TAG)
private val sensorManager: SensorManager = ServiceUtil.getSensorManager(activity)
private val proximitySensor: Sensor? = sensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY)

View File

@ -189,7 +189,6 @@ public final class WebRtcAnswerDeclineButton extends LinearLayout implements Acc
swipeUpText.setAlpha(1);
swipeDownText.setAlpha(1);
answer.setRotation(0);
answer.getDrawable().setTint(getResources().getColor(R.color.green_600));
answer.getBackground().setTint(Color.WHITE);

View File

@ -0,0 +1,23 @@
package org.thoughtcrime.securesms.contacts.paged
import android.view.View
import org.thoughtcrime.securesms.util.adapter.mapping.PagingMappingAdapter
/**
* Default contact search adapter, using the models defined in `ContactSearchItems`
*/
class ContactSearchAdapter(
displayCheckBox: Boolean,
displaySmsTag: ContactSearchItems.DisplaySmsTag,
recipientListener: (View, ContactSearchData.KnownRecipient, Boolean) -> Unit,
storyListener: (View, ContactSearchData.Story, Boolean) -> Unit,
storyContextMenuCallbacks: ContactSearchItems.StoryContextMenuCallbacks,
expandListener: (ContactSearchData.Expand) -> Unit
) : PagingMappingAdapter<ContactSearchKey>() {
init {
ContactSearchItems.registerStoryItems(this, displayCheckBox, storyListener, storyContextMenuCallbacks)
ContactSearchItems.registerKnownRecipientItems(this, displayCheckBox, displaySmsTag, recipientListener)
ContactSearchItems.registerHeaders(this)
ContactSearchItems.registerExpands(this, expandListener)
}
}

View File

@ -18,7 +18,7 @@ sealed class ContactSearchData(val contactSearchKey: ContactSearchKey) {
val recipient: Recipient,
val count: Int,
val privacyMode: DistributionListPrivacyMode
) : ContactSearchData(ContactSearchKey.RecipientSearchKey.Story(recipient.id))
) : ContactSearchData(ContactSearchKey.RecipientSearchKey(recipient.id, true))
/**
* A row displaying a known recipient.
@ -27,7 +27,7 @@ sealed class ContactSearchData(val contactSearchKey: ContactSearchKey) {
val recipient: Recipient,
val shortSummary: Boolean = false,
val headerLetter: String? = null
) : ContactSearchData(ContactSearchKey.RecipientSearchKey.KnownRecipient(recipient.id))
) : ContactSearchData(ContactSearchKey.RecipientSearchKey(recipient.id, false))
/**
* A row containing a title for a given section

View File

@ -45,6 +45,18 @@ object ContactSearchItems {
)
}
fun registerKnownRecipientItems(
mappingAdapter: MappingAdapter,
displayCheckBox: Boolean,
displaySmsTag: DisplaySmsTag,
recipientListener: (View, ContactSearchData.KnownRecipient, Boolean) -> Unit
) {
mappingAdapter.registerFactory(
RecipientModel::class.java,
LayoutFactory({ KnownRecipientViewHolder(it, displayCheckBox, displaySmsTag, recipientListener) }, R.layout.contact_search_item)
)
}
fun registerHeaders(mappingAdapter: MappingAdapter) {
mappingAdapter.registerFactory(
HeaderModel::class.java,
@ -52,21 +64,7 @@ object ContactSearchItems {
)
}
fun register(
mappingAdapter: MappingAdapter,
displayCheckBox: Boolean,
displaySmsTag: DisplaySmsTag,
recipientListener: RecipientClickListener,
storyListener: StoryClickListener,
storyContextMenuCallbacks: StoryContextMenuCallbacks,
expandListener: (ContactSearchData.Expand) -> Unit
) {
registerStoryItems(mappingAdapter, displayCheckBox, storyListener, storyContextMenuCallbacks)
mappingAdapter.registerFactory(
RecipientModel::class.java,
LayoutFactory({ KnownRecipientViewHolder(it, displayCheckBox, displaySmsTag, recipientListener) }, R.layout.contact_search_item)
)
registerHeaders(mappingAdapter)
fun registerExpands(mappingAdapter: MappingAdapter, expandListener: (ContactSearchData.Expand) -> Unit) {
mappingAdapter.registerFactory(
ExpandModel::class.java,
LayoutFactory({ ExpandViewHolder(it, expandListener) }, R.layout.contacts_expand_item)
@ -90,7 +88,7 @@ object ContactSearchItems {
/**
* Story Model
*/
private class StoryModel(val story: ContactSearchData.Story, val isSelected: Boolean, val hasBeenNotified: Boolean) : MappingModel<StoryModel> {
class StoryModel(val story: ContactSearchData.Story, val isSelected: Boolean, val hasBeenNotified: Boolean) : MappingModel<StoryModel> {
override fun areItemsTheSame(newItem: StoryModel): Boolean {
return newItem.story == story
@ -226,7 +224,7 @@ object ContactSearchItems {
/**
* Recipient model
*/
private class RecipientModel(val knownRecipient: ContactSearchData.KnownRecipient, val isSelected: Boolean, val shortSummary: Boolean) : MappingModel<RecipientModel> {
class RecipientModel(val knownRecipient: ContactSearchData.KnownRecipient, val isSelected: Boolean, val shortSummary: Boolean) : MappingModel<RecipientModel> {
override fun areItemsTheSame(newItem: RecipientModel): Boolean {
return newItem.knownRecipient == knownRecipient
@ -363,7 +361,7 @@ object ContactSearchItems {
/**
* Mapping Model for section headers
*/
private class HeaderModel(val header: ContactSearchData.Header) : MappingModel<HeaderModel> {
class HeaderModel(val header: ContactSearchData.Header) : MappingModel<HeaderModel> {
override fun areItemsTheSame(newItem: HeaderModel): Boolean {
return header.sectionKey == newItem.header.sectionKey
}
@ -407,7 +405,7 @@ object ContactSearchItems {
/**
* Mapping Model for expandable content rows.
*/
private class ExpandModel(val expand: ContactSearchData.Expand) : MappingModel<ExpandModel> {
class ExpandModel(val expand: ContactSearchData.Expand) : MappingModel<ExpandModel> {
override fun areItemsTheSame(newItem: ExpandModel): Boolean {
return expand.contactSearchKey == newItem.expand.contactSearchKey
}

View File

@ -12,44 +12,18 @@ sealed class ContactSearchKey {
/**
* Generates a ShareContact object used to display which contacts have been selected. This should *not*
* be used for the final sharing process, as it is not always truthful about, for example, KnownRecipient of
* be used for the final sharing process, as it is not always truthful about, for example,
* a group vs. a group's Story.
*/
open fun requireShareContact(): ShareContact = error("This key cannot be converted into a ShareContact")
open fun requireParcelable(): ParcelableRecipientSearchKey = error("This key cannot be parcelized")
open fun requireRecipientSearchKey(): RecipientSearchKey = error("This key cannot be parcelized")
sealed class RecipientSearchKey : ContactSearchKey() {
@Parcelize
data class RecipientSearchKey(val recipientId: RecipientId, val isStory: Boolean) : ContactSearchKey(), Parcelable {
override fun requireRecipientSearchKey(): RecipientSearchKey = this
abstract val recipientId: RecipientId
abstract val isStory: Boolean
data class Story(override val recipientId: RecipientId) : RecipientSearchKey() {
override fun requireShareContact(): ShareContact {
return ShareContact(recipientId)
}
override fun requireParcelable(): ParcelableRecipientSearchKey {
return ParcelableRecipientSearchKey(ParcelableType.STORY, recipientId)
}
override val isStory: Boolean = true
}
/**
* Key to a recipient which already exists in our database
*/
data class KnownRecipient(override val recipientId: RecipientId) : RecipientSearchKey() {
override fun requireShareContact(): ShareContact {
return ShareContact(recipientId)
}
override fun requireParcelable(): ParcelableRecipientSearchKey {
return ParcelableRecipientSearchKey(ParcelableType.KNOWN_RECIPIENT, recipientId)
}
override val isStory: Boolean = false
}
override fun requireShareContact(): ShareContact = ShareContact(recipientId)
}
/**
@ -61,19 +35,4 @@ sealed class ContactSearchKey {
* Key to an expand button for a given section
*/
data class Expand(val sectionKey: ContactSearchConfiguration.SectionKey) : ContactSearchKey()
@Parcelize
data class ParcelableRecipientSearchKey(val type: ParcelableType, val recipientId: RecipientId) : Parcelable {
fun asRecipientSearchKey(): RecipientSearchKey {
return when (type) {
ParcelableType.STORY -> RecipientSearchKey.Story(recipientId)
ParcelableType.KNOWN_RECIPIENT -> RecipientSearchKey.KnownRecipient(recipientId)
}
}
}
enum class ParcelableType {
STORY,
KNOWN_RECIPIENT
}
}

View File

@ -27,26 +27,23 @@ class ContactSearchMediator(
displaySmsTag: ContactSearchItems.DisplaySmsTag,
mapStateToConfiguration: (ContactSearchState) -> ContactSearchConfiguration,
private val contactSelectionPreFilter: (View?, Set<ContactSearchKey>) -> Set<ContactSearchKey> = { _, s -> s },
performSafetyNumberChecks: Boolean = true
performSafetyNumberChecks: Boolean = true,
adapterFactory: AdapterFactory = DefaultAdapterFactory
) {
private val viewModel: ContactSearchViewModel = ViewModelProvider(fragment, ContactSearchViewModel.Factory(selectionLimits, ContactSearchRepository(), performSafetyNumberChecks)).get(ContactSearchViewModel::class.java)
init {
val adapter = adapterFactory.create(
displayCheckBox,
displaySmsTag,
this::toggleSelection,
this::toggleStorySelection,
StoryContextMenuCallbacks()
) { viewModel.expandSection(it.sectionKey) }
val adapter = PagingMappingAdapter<ContactSearchKey>()
recyclerView.adapter = adapter
ContactSearchItems.register(
mappingAdapter = adapter,
displayCheckBox = displayCheckBox,
displaySmsTag = displaySmsTag,
recipientListener = this::toggleSelection,
storyListener = this::toggleStorySelection,
storyContextMenuCallbacks = StoryContextMenuCallbacks(),
expandListener = { viewModel.expandSection(it.sectionKey) }
)
val dataAndSelection: LiveData<Pair<List<ContactSearchData>, Set<ContactSearchKey>>> = LiveDataUtil.combineLatest(
viewModel.data,
viewModel.selectionState,
@ -90,7 +87,7 @@ class ContactSearchMediator(
return viewModel.errorEventsStream.observeOn(AndroidSchedulers.mainThread())
}
fun addToVisibleGroupStories(groupStories: Set<ContactSearchKey.RecipientSearchKey.Story>) {
fun addToVisibleGroupStories(groupStories: Set<ContactSearchKey.RecipientSearchKey>) {
viewModel.addToVisibleGroupStories(groupStories)
}
@ -143,4 +140,32 @@ class ContactSearchMediator(
.show()
}
}
/**
* Wraps the construction of a PagingMappingAdapter<ContactSearchKey> so that it can
* be swapped for another implementation, allow listeners to be wrapped, etc.
*/
fun interface AdapterFactory {
fun create(
displayCheckBox: Boolean,
displaySmsTag: ContactSearchItems.DisplaySmsTag,
recipientListener: (View, ContactSearchData.KnownRecipient, Boolean) -> Unit,
storyListener: (View, ContactSearchData.Story, Boolean) -> Unit,
storyContextMenuCallbacks: ContactSearchItems.StoryContextMenuCallbacks,
expandListener: (ContactSearchData.Expand) -> Unit
): PagingMappingAdapter<ContactSearchKey>
}
private object DefaultAdapterFactory : AdapterFactory {
override fun create(
displayCheckBox: Boolean,
displaySmsTag: ContactSearchItems.DisplaySmsTag,
recipientListener: (View, ContactSearchData.KnownRecipient, Boolean) -> Unit,
storyListener: (View, ContactSearchData.Story, Boolean) -> Unit,
storyContextMenuCallbacks: ContactSearchItems.StoryContextMenuCallbacks,
expandListener: (ContactSearchData.Expand) -> Unit
): PagingMappingAdapter<ContactSearchKey> {
return ContactSearchAdapter(displayCheckBox, displaySmsTag, recipientListener, storyListener, storyContextMenuCallbacks, expandListener)
}
}
}

View File

@ -21,8 +21,7 @@ class ContactSearchRepository {
val isSelectable = when (it) {
is ContactSearchKey.Expand -> false
is ContactSearchKey.Header -> false
is ContactSearchKey.RecipientSearchKey.KnownRecipient -> canSelectRecipient(it.recipientId)
is ContactSearchKey.RecipientSearchKey.Story -> canSelectRecipient(it.recipientId)
is ContactSearchKey.RecipientSearchKey -> canSelectRecipient(it.recipientId)
}
ContactSearchSelectionResult(it, isSelectable)
}.toSet()

View File

@ -98,7 +98,7 @@ class ContactSearchViewModel(
return selectionStore.state
}
fun addToVisibleGroupStories(groupStories: Set<ContactSearchKey.RecipientSearchKey.Story>) {
fun addToVisibleGroupStories(groupStories: Set<ContactSearchKey.RecipientSearchKey>) {
disposables += contactSearchRepository.markDisplayAsStory(groupStories.map { it.recipientId }).subscribe {
configurationStore.update { state ->
state.copy(

View File

@ -77,8 +77,8 @@ class SafetyNumberRepository(
private fun List<ContactSearchKey>.flattenToRecipientIds(): Set<RecipientId> {
return this
.map {
when (it) {
is ContactSearchKey.RecipientSearchKey.KnownRecipient -> {
when {
it is ContactSearchKey.RecipientSearchKey && !it.isStory -> {
val recipient = Recipient.resolved(it.recipientId)
if (recipient.isGroup) {
recipient.participantIds
@ -86,7 +86,7 @@ class SafetyNumberRepository(
listOf(it.recipientId)
}
}
is ContactSearchKey.RecipientSearchKey.Story -> Recipient.resolved(it.recipientId).participantIds
it is ContactSearchKey.RecipientSearchKey -> Recipient.resolved(it.recipientId).participantIds
else -> throw AssertionError("Invalid contact selection $it")
}
}

View File

@ -30,8 +30,11 @@ open class ConversationActivity : PassphraseRequiredActivity(), ConversationPare
}
override fun onCreate(savedInstanceState: Bundle?, ready: Boolean) {
shareDataTimestamp = savedInstanceState?.getLong(STATE_WATERMARK, -1L) ?: -1L
if (savedInstanceState != null) {
shareDataTimestamp = savedInstanceState.getLong(STATE_WATERMARK, -1L)
} else if (intent.flags and Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY != 0) {
shareDataTimestamp = System.currentTimeMillis()
}
setContentView(R.layout.conversation_parent_fragment_container)
if (savedInstanceState == null) {

View File

@ -1532,7 +1532,7 @@ public class ConversationParentFragment extends Fragment
SafetyNumberBottomSheet
.forIdentityRecordsAndDestination(
records,
new ContactSearchKey.RecipientSearchKey.KnownRecipient(recipient.getId())
new ContactSearchKey.RecipientSearchKey(recipient.getId(), false)
)
.show(getChildFragmentManager());
}
@ -2365,7 +2365,7 @@ public class ConversationParentFragment extends Fragment
}
private void showGroupCallingTooltip() {
if (Build.VERSION.SDK_INT == 19 || !SignalStore.tooltips().shouldShowGroupCallingTooltip() || callingTooltipShown) {
if (!SignalStore.tooltips().shouldShowGroupCallingTooltip() || callingTooltipShown) {
return;
}
@ -2636,7 +2636,7 @@ public class ConversationParentFragment extends Fragment
TextView message = inviteButton.get().findViewById(R.id.export_sms_message);
MaterialButton actionButton = inviteButton.get().findViewById(R.id.export_sms_button);
message.setText(requireContext().getString(R.string.ConversationActivity__sms_messaging_is_currently_disabled_invite_s_to_to_signal_to_keep_the_conversation_here,
message.setText(requireContext().getString(R.string.ConversationActivity__sms_messaging_is_no_longer_supported_in_signal_invite_s_to_to_signal_to_keep_the_conversation_here,
recipient.getDisplayName(requireContext())));
actionButton.setText(R.string.ConversationActivity__invite_to_signal);
actionButton.setOnClickListener(v -> handleInviteLink());
@ -3403,7 +3403,7 @@ public class ConversationParentFragment extends Fragment
}
@Override
public void sendAnywayAfterSafetyNumberChangedInBottomSheet(@NonNull List<? extends ContactSearchKey.RecipientSearchKey> destinations) {
public void sendAnywayAfterSafetyNumberChangedInBottomSheet(@NonNull List<ContactSearchKey.RecipientSearchKey> destinations) {
Log.d(TAG, "onSendAnywayAfterSafetyNumberChange");
initializeIdentityRecords().addListener(new AssertedSuccessListener<Boolean>() {
@Override

View File

@ -220,7 +220,8 @@ final class MenuState {
messageRecord.isChangeNumber() ||
messageRecord.isBoostRequest() ||
messageRecord.isPaymentsRequestToActivate() ||
messageRecord.isPaymentsActivated();
messageRecord.isPaymentsActivated() ||
messageRecord.isSmsExportType();
}
private final static class Builder {

View File

@ -40,9 +40,6 @@ class ChatColors(
val chatBubbleMask: Drawable
get() {
return when {
false -> {
ColorDrawable(Color.TRANSPARENT)
}
linearGradient != null -> {
RotatableGradientDrawable(
linearGradient.degrees,
@ -60,8 +57,7 @@ class ChatColors(
* Returns the ColorFilter to apply to a conversation bubble or other relevant piece of UI.
*/
@IgnoredOnParcel
val chatBubbleColorFilter: ColorFilter =
PorterDuffColorFilter(Color.TRANSPARENT, PorterDuff.Mode.SRC_IN)
val chatBubbleColorFilter: ColorFilter = PorterDuffColorFilter(Color.TRANSPARENT, PorterDuff.Mode.SRC_IN)
@ColorInt
fun asSingleColor(): Int {
@ -109,7 +105,6 @@ class ChatColors(
}
fun asCircle(): Drawable {
val toWrap: Drawable = chatBubbleMask
val path = Path()

View File

@ -73,8 +73,8 @@ open class MultiselectForwardActivity : FragmentWrapperActivity(), MultiselectFo
} else if (intent == null || !intent.hasExtra(RESULT_SELECTION)) {
throw IllegalStateException("Selection contract requires a selection.")
} else {
val selection: List<ContactSearchKey.ParcelableRecipientSearchKey> = intent.getParcelableArrayListExtra(RESULT_SELECTION)!!
selection.map { it.asRecipientSearchKey() }
val selection: List<ContactSearchKey.RecipientSearchKey> = intent.getParcelableArrayListExtra(RESULT_SELECTION)!!
selection
}
}
}

View File

@ -191,7 +191,7 @@ class MultiselectForwardFragment :
shareSelectionAdapter.submitList(contactSelection.mapIndexed { index, key -> ShareSelectionMappingModel(key.requireShareContact(), index == 0) })
addMessage.visible = !args.forceDisableAddMessage && contactSelection.any { key -> key !is ContactSearchKey.RecipientSearchKey.Story } && args.multiShareArgs.isNotEmpty()
addMessage.visible = !args.forceDisableAddMessage && contactSelection.any { key -> !key.requireRecipientSearchKey().isStory } && args.multiShareArgs.isNotEmpty()
if (contactSelection.isNotEmpty() && !bottomBar.isVisible) {
bottomBar.animation = AnimationUtils.loadAnimation(requireContext(), R.anim.slide_fade_from_bottom)
@ -237,13 +237,13 @@ class MultiselectForwardFragment :
setFragmentResultListener(CreateStoryWithViewersFragment.REQUEST_KEY) { _, bundle ->
val recipientId: RecipientId = bundle.getParcelable(CreateStoryWithViewersFragment.STORY_RECIPIENT)!!
contactSearchMediator.setKeysSelected(setOf(ContactSearchKey.RecipientSearchKey.Story(recipientId)))
contactSearchMediator.setKeysSelected(setOf(ContactSearchKey.RecipientSearchKey(recipientId, true)))
contactFilterView.clear()
}
setFragmentResultListener(ChooseGroupStoryBottomSheet.GROUP_STORY) { _, bundle ->
val groups: Set<RecipientId> = bundle.getParcelableArrayList<RecipientId>(ChooseGroupStoryBottomSheet.RESULT_SET)?.toSet() ?: emptySet()
val keys: Set<ContactSearchKey.RecipientSearchKey.Story> = groups.map { ContactSearchKey.RecipientSearchKey.Story(it) }.toSet()
val keys: Set<ContactSearchKey.RecipientSearchKey> = groups.map { ContactSearchKey.RecipientSearchKey(it, true) }.toSet()
contactSearchMediator.addToVisibleGroupStories(keys)
contactSearchMediator.setKeysSelected(keys)
contactFilterView.clear()
@ -350,7 +350,7 @@ class MultiselectForwardFragment :
dismissibleDialog?.dismiss()
val resultsBundle = Bundle().apply {
putParcelableArrayList(RESULT_SELECTION, ArrayList(selectedContacts.map { it.requireParcelable() }))
putParcelableArrayList(RESULT_SELECTION, ArrayList(selectedContacts.map { it.requireRecipientSearchKey() }))
}
callback.setResult(resultsBundle)
@ -489,7 +489,7 @@ class MultiselectForwardFragment :
}
override fun onMyStoryConfigured(recipientId: RecipientId) {
contactSearchMediator.setKeysSelected(setOf(ContactSearchKey.RecipientSearchKey.Story(recipientId)))
contactSearchMediator.setKeysSelected(setOf(ContactSearchKey.RecipientSearchKey(recipientId, true)))
contactSearchMediator.refresh()
}

View File

@ -62,14 +62,14 @@ class MultiselectForwardRepository {
SignalExecutors.BOUNDED.execute {
val filteredContacts: Set<ContactSearchKey> = shareContacts
.asSequence()
.filter { it is ContactSearchKey.RecipientSearchKey.Story || it is ContactSearchKey.RecipientSearchKey.KnownRecipient }
.filter { it is ContactSearchKey.RecipientSearchKey }
.toSet()
val mappedArgs: List<MultiShareArgs> = multiShareArgs.map { it.buildUpon(filteredContacts).build() }
val results = mappedArgs.sortedBy { it.timestamp }.map { MultiShareSender.sendSync(it) }
if (additionalMessage.isNotEmpty()) {
val additional = MultiShareArgs.Builder(filteredContacts.filterNot { it is ContactSearchKey.RecipientSearchKey.Story }.toSet())
val additional = MultiShareArgs.Builder(filteredContacts.filterNot { it is ContactSearchKey.RecipientSearchKey && it.isStory }.toSet())
.withDraftText(additionalMessage)
.build()

View File

@ -2,7 +2,6 @@ package org.thoughtcrime.securesms.conversation.ui.inlinequery
import android.content.Context
import android.graphics.Rect
import android.os.Build
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
@ -34,10 +33,8 @@ class InlineQueryResultsPopup(
private val adapter: MappingAdapter
init {
if (Build.VERSION.SDK_INT >= 21) {
contentView.outlineProvider = ViewOutlineProvider.BACKGROUND
contentView.clipToOutline = true
}
contentView.outlineProvider = ViewOutlineProvider.BACKGROUND
contentView.clipToOutline = true
inputMethodMode = INPUT_METHOD_NOT_NEEDED
@ -46,9 +43,7 @@ class InlineQueryResultsPopup(
callback = null
}
if (Build.VERSION.SDK_INT >= 21) {
elevation = 20f
}
elevation = 20f
adapter = InlineQueryAdapter { m -> callback?.onSelection(m) }
list.adapter = adapter

View File

@ -107,7 +107,7 @@ public class ConversationListArchiveFragment extends ConversationListFragment im
@Override
protected @DrawableRes int getArchiveIconRes() {
return R.drawable.ic_unarchive_24;
return R.drawable.symbol_archive_android_up_24;
}
@Override

View File

@ -1258,9 +1258,9 @@ public class ConversationListFragment extends MainFragment implements ActionMode
}));
if (conversation.getThreadRecord().isArchived()) {
items.add(new ActionItem(R.drawable.ic_unarchive_24, getResources().getQuantityString(R.plurals.ConversationListFragment_unarchive_plural, 1), () -> handleArchive(id, false)));
items.add(new ActionItem(R.drawable.symbol_archive_android_up_24, getResources().getQuantityString(R.plurals.ConversationListFragment_unarchive_plural, 1), () -> handleArchive(id, false)));
} else {
items.add(new ActionItem(R.drawable.ic_archive_24, getResources().getQuantityString(R.plurals.ConversationListFragment_archive_plural, 1), () -> handleArchive(id, false)));
items.add(new ActionItem(R.drawable.symbol_archive_android_24, getResources().getQuantityString(R.plurals.ConversationListFragment_archive_plural, 1), () -> handleArchive(id, false)));
}
items.add(new ActionItem(R.drawable.ic_delete_24, getResources().getQuantityString(R.plurals.ConversationListFragment_delete_plural, 1), () -> handleDelete(id)));
@ -1301,11 +1301,9 @@ public class ConversationListFragment extends MainFragment implements ActionMode
public void onDestroyActionMode(ActionMode mode) {
viewModel.endSelection();
if (Build.VERSION.SDK_INT >= 21) {
TypedArray color = getActivity().getTheme().obtainStyledAttributes(new int[] { android.R.attr.statusBarColor });
WindowUtil.setStatusBarColor(getActivity().getWindow(), color.getColor(0, Color.BLACK));
color.recycle();
}
TypedArray color = getActivity().getTheme().obtainStyledAttributes(new int[] { android.R.attr.statusBarColor });
WindowUtil.setStatusBarColor(getActivity().getWindow(), color.getColor(0, Color.BLACK));
color.recycle();
if (Build.VERSION.SDK_INT >= 23) {
TypedArray lightStatusBarAttr = getActivity().getTheme().obtainStyledAttributes(new int[] { android.R.attr.windowLightStatusBar });
@ -1363,9 +1361,9 @@ public class ConversationListFragment extends MainFragment implements ActionMode
}
if (isArchived()) {
items.add(new ActionItem(R.drawable.ic_unarchive_24, getResources().getQuantityString(R.plurals.ConversationListFragment_unarchive_plural, count), () -> handleArchive(selectionIds, true)));
items.add(new ActionItem(R.drawable.symbol_archive_android_up_24, getResources().getQuantityString(R.plurals.ConversationListFragment_unarchive_plural, count), () -> handleArchive(selectionIds, true)));
} else {
items.add(new ActionItem(R.drawable.ic_archive_24, getResources().getQuantityString(R.plurals.ConversationListFragment_archive_plural, count), () -> handleArchive(selectionIds, true)));
items.add(new ActionItem(R.drawable.symbol_archive_android_24, getResources().getQuantityString(R.plurals.ConversationListFragment_archive_plural, count), () -> handleArchive(selectionIds, true)));
}
items.add(new ActionItem(R.drawable.ic_delete_24, getResources().getQuantityString(R.plurals.ConversationListFragment_delete_plural, count), () -> handleDelete(selectionIds)));
@ -1394,7 +1392,7 @@ public class ConversationListFragment extends MainFragment implements ActionMode
}
protected @DrawableRes int getArchiveIconRes() {
return R.drawable.ic_archive_24;
return R.drawable.symbol_archive_android_24;
}
@WorkerThread

View File

@ -202,7 +202,7 @@ class ConversationListFilterPullView @JvmOverloads constructor(
animateHelpText = 0
helpTextStartFraction = ANIMATE_HELP_TEXT_START_FRACTION
binding.helpText.animate().alpha(0f).setListener(object : AnimationCompleteListener() {
override fun onAnimationEnd(animation: Animator?) {
override fun onAnimationEnd(animation: Animator) {
binding.helpText.visibility = INVISIBLE
}
})

View File

@ -335,7 +335,11 @@ public class MessageTable extends DatabaseTable implements MessageTypes, Recipie
private static final String RAW_ID_WHERE = TABLE_NAME + "._id = ?";
private static final String SNIPPET_QUERY = "SELECT " + MessageTable.ID + ", " + MessageTable.TYPE + ", " + MessageTable.DATE_RECEIVED + " FROM " + MessageTable.TABLE_NAME + " " +
"WHERE " + MessageTable.THREAD_ID + " = ? AND " + MessageTable.TYPE + " & " + MessageTypes.GROUP_V2_LEAVE_BITS + " != " + MessageTypes.GROUP_V2_LEAVE_BITS + " AND " + MessageTable.STORY_TYPE + " = 0 AND " + MessageTable.PARENT_STORY_ID + " <= 0 " +
"WHERE " + MessageTable.THREAD_ID + " = ? AND " +
MessageTable.TYPE + " & " + MessageTypes.GROUP_V2_LEAVE_BITS + " != " + MessageTypes.GROUP_V2_LEAVE_BITS + " AND " +
MessageTable.STORY_TYPE + " = 0 AND " +
MessageTable.PARENT_STORY_ID + " <= 0 AND " +
MessageTable.TYPE + " NOT IN (" + MessageTypes.PROFILE_CHANGE_TYPE + ", " + MessageTypes.GV1_MIGRATION_TYPE + ", " + MessageTypes.CHANGE_NUMBER_TYPE + ", " + MessageTypes.BOOST_REQUEST_TYPE + ", " + MessageTypes.SMS_EXPORT_TYPE + ") " +
"ORDER BY " + MessageTable.DATE_RECEIVED + " DESC " +
"LIMIT 1";
@ -941,7 +945,7 @@ public class MessageTable extends DatabaseTable implements MessageTypes, Recipie
}
if (!silent) {
final boolean keepThreadArchived = SignalStore.settings().shouldKeepMutedChatsArchived() && recipient.isMuted();
final boolean keepThreadArchived = SignalStore.settings().shouldKeepMutedChatsArchived() && (recipient.isMuted() || (groupRecipient != null && groupRecipient.isMuted()));
SignalDatabase.threads().update(threadId, !keepThreadArchived);
}

View File

@ -3277,7 +3277,7 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
}
fun markPreMessageRequestRecipientsAsProfileSharingEnabled(messageRequestEnableTime: Long) {
val whereArgs = SqlUtil.buildArgs(messageRequestEnableTime, messageRequestEnableTime)
val whereArgs = SqlUtil.buildArgs(messageRequestEnableTime)
val select =
"""
SELECT r.$ID FROM $TABLE_NAME AS r

View File

@ -191,6 +191,24 @@ class StorySendTable(context: Context, databaseHelper: SignalDatabase) : Databas
return messageIds
}
fun getStoryMessageFor(recipientId: RecipientId, timestamp: Long): MessageId? {
readableDatabase.query(
TABLE_NAME,
arrayOf(MESSAGE_ID),
"$RECIPIENT_ID = ? AND $SENT_TIMESTAMP = ?",
SqlUtil.buildArgs(recipientId, timestamp),
null,
null,
null,
"1"
).use { cursor ->
if (cursor.moveToFirst()) {
return MessageId(cursor.requireLong(MESSAGE_ID))
}
}
return null
}
override fun remapRecipient(oldId: RecipientId, newId: RecipientId) {
val query = "$RECIPIENT_ID = ?"
val args = SqlUtil.buildArgs(oldId)

View File

@ -1646,7 +1646,8 @@ class ThreadTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa
private fun ConversationFilter.toQuery(): String {
return when (this) {
ConversationFilter.OFF -> ""
ConversationFilter.UNREAD -> " AND $READ != ${ReadStatus.READ.serialize()}"
//language=sql
ConversationFilter.UNREAD -> " AND ($UNREAD_COUNT > 0 OR $READ == ${ReadStatus.FORCED_UNREAD.serialize()})"
ConversationFilter.MUTED -> error("This filter selection isn't supported yet.")
ConversationFilter.GROUPS -> error("This filter selection isn't supported yet.")
}

View File

@ -14,8 +14,7 @@ object TypefaceHelper {
weight.value,
false
)
true -> Typeface.create("${family.familyName}-$weightName", Typeface.NORMAL)
else -> Typeface.create(family.familyName, if (weight.value > Weight.MEDIUM.value) Typeface.BOLD else Typeface.NORMAL)
else -> Typeface.create("${family.familyName}-$weightName", Typeface.NORMAL)
}
}

View File

@ -128,7 +128,7 @@ public class AddGroupDetailsFragment extends LoggingFragment {
viewModel.getIsMms().observe(getViewLifecycleOwner(), isMms -> {
disappearingMessagesRow.setVisibility(isMms ? View.GONE : View.VISIBLE);
mmsWarning.setVisibility(isMms ? View.VISIBLE : View.GONE);
mmsWarningText.setText(R.string.AddGroupDetailsFragment__youve_selected_a_contact_that_doesnt_support);
mmsWarningText.setText(R.string.AddGroupDetailsFragment__youve_selected_a_contact_that_doesnt_support_signal_groups_mms_removal);
name.setHint(isMms ? R.string.AddGroupDetailsFragment__group_name_optional : R.string.AddGroupDetailsFragment__group_name_required);
toolbar.setTitle(isMms ? R.string.AddGroupDetailsFragment__create_group : R.string.AddGroupDetailsFragment__name_this_group);
});

View File

@ -155,7 +155,7 @@ class KeyboardPageSearchView @JvmOverloads constructor(
.setDuration(REVEAL_DURATION)
.alpha(0f)
.setListener(object : AnimationCompleteListener() {
override fun onAnimationEnd(animation: Animator?) {
override fun onAnimationEnd(animation: Animator) {
visibility = INVISIBLE
}
})

View File

@ -3,6 +3,7 @@ package org.thoughtcrime.securesms.keyvalue;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.signal.core.util.StringStringSerializer;
import org.thoughtcrime.securesms.lock.PinHashing;
import org.thoughtcrime.securesms.util.JsonUtils;
import org.whispersystems.signalservice.api.KbsPinData;
@ -13,6 +14,8 @@ import java.io.IOException;
import java.security.SecureRandom;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public final class KbsValues extends SignalStoreValues {
@ -24,6 +27,7 @@ public final class KbsValues extends SignalStoreValues {
private static final String LAST_CREATE_FAILED_TIMESTAMP = "kbs.last_create_failed_timestamp";
public static final String OPTED_OUT = "kbs.opted_out";
private static final String PIN_FORGOTTEN_OR_SKIPPED = "kbs.pin.forgotten.or.skipped";
private static final String KBS_AUTH_TOKENS = "kbs.kbs_auth_tokens";
KbsValues(KeyValueStore store) {
super(store);
@ -165,6 +169,30 @@ public final class KbsValues extends SignalStoreValues {
putBoolean(PIN_FORGOTTEN_OR_SKIPPED, value);
}
public synchronized void putAuthTokenList(List<String> tokens) {
putList(KBS_AUTH_TOKENS, tokens, StringStringSerializer.INSTANCE);
}
public synchronized List<String> getKbsAuthTokenList() {
return getList(KBS_AUTH_TOKENS, StringStringSerializer.INSTANCE);
}
/**
* Keeps the 10 most recent KBS auth tokens.
* @param token
* @return whether the token was added (new) or ignored (already existed)
*/
public synchronized boolean appendAuthTokenToList(String token) {
List<String> tokens = getKbsAuthTokenList();
if (tokens.contains(token)) {
return false;
} else {
final List<String> result = Stream.concat(Stream.of(token), tokens.stream()).limit(10).collect(Collectors.toList());
putAuthTokenList(result);
return true;
}
}
/** Should only be called by {@link org.thoughtcrime.securesms.pin.PinState}. */
public synchronized void optOut() {
getStore().beginWrite()

View File

@ -4,7 +4,6 @@ import android.animation.Animator
import android.animation.Animator.AnimatorListener
import android.annotation.SuppressLint
import android.content.Context
import android.os.Build
import android.util.AttributeSet
import android.view.MotionEvent
import android.view.animation.PathInterpolator
@ -103,14 +102,14 @@ class LottieAnimatedButton @JvmOverloads constructor(
MotionEvent.ACTION_UP -> {
if (isAnimating) {
addAnimatorListener(object : AnimatorListener {
override fun onAnimationEnd(animation: Animator?) {
override fun onAnimationEnd(animation: Animator) {
removeAllAnimatorListeners()
playAnimationReverse()
}
override fun onAnimationStart(animation: Animator?) {}
override fun onAnimationCancel(animation: Animator?) {}
override fun onAnimationRepeat(animation: Animator?) {}
override fun onAnimationStart(animation: Animator) {}
override fun onAnimationCancel(animation: Animator) {}
override fun onAnimationRepeat(animation: Animator) {}
})
} else {
playAnimationReverse()
@ -162,9 +161,7 @@ class AnimatedInOutImageButton @JvmOverloads constructor(
.scaleX(1f)
.scaleY(1f)
if (Build.VERSION.SDK_INT >= 21) {
animator.interpolator = PathInterpolator(0.4f, 0.0f, 0.2f, 1f)
}
animator.interpolator = PathInterpolator(0.4f, 0.0f, 0.2f, 1f)
animator.start()
}
@ -178,9 +175,7 @@ class AnimatedInOutImageButton @JvmOverloads constructor(
.scaleY(0.5f)
.withEndAction(endAction)
if (Build.VERSION.SDK_INT >= 21) {
animator.interpolator = PathInterpolator(0.4f, 0.0f, 0.2f, 1f)
}
animator.interpolator = PathInterpolator(0.4f, 0.0f, 0.2f, 1f)
animator.start()
}

View File

@ -1,7 +1,6 @@
package org.thoughtcrime.securesms.mediapreview
import android.content.Context
import android.os.Build
import android.os.Bundle
import androidx.appcompat.app.AppCompatDelegate
import androidx.core.content.ContextCompat
@ -26,11 +25,10 @@ class MediaPreviewV2Activity : PassphraseRequiredActivity(), VoiceNoteMediaContr
setContentView(R.layout.activity_mediapreview_v2)
voiceNoteMediaController = VoiceNoteMediaController(this)
if (Build.VERSION.SDK_INT >= 21) {
val systemBarColor = ContextCompat.getColor(this, R.color.signal_dark_colorSurface)
window.statusBarColor = systemBarColor
window.navigationBarColor = systemBarColor
}
val systemBarColor = ContextCompat.getColor(this, R.color.signal_dark_colorSurface)
window.statusBarColor = systemBarColor
window.navigationBarColor = systemBarColor
if (savedInstanceState == null) {
val bundle = Bundle()
val args = MediaIntentFactory.requireArguments(intent.extras!!)

View File

@ -8,7 +8,6 @@ import android.content.DialogInterface
import android.content.Intent
import android.graphics.PorterDuff
import android.graphics.PorterDuffColorFilter
import android.os.Build
import android.os.Bundle
import android.text.SpannableStringBuilder
import android.view.LayoutInflater
@ -153,7 +152,6 @@ class MediaPreviewV2Fragment : LoggingFragment(R.layout.fragment_media_preview_v
super.onPageSelected(position)
if (position != viewModel.currentPosition) {
debouncer.clear()
fullscreenHelper.showSystemUI()
}
viewModel.setCurrentPage(position)
}
@ -244,7 +242,6 @@ class MediaPreviewV2Fragment : LoggingFragment(R.layout.fragment_media_preview_v
}
bindAlbumRail(albumThumbnailMedia, currentItem)
fullscreenHelper.showSystemUI()
crossfadeViewIn(binding.mediaPreviewDetailsContainer)
}
@ -351,9 +348,7 @@ class MediaPreviewV2Fragment : LoggingFragment(R.layout.fragment_media_preview_v
scrollAlbumRailToCurrentAdapterPosition()
}
}
if (Build.VERSION.SDK_INT >= 21) {
viewPropertyAnimator.interpolator = PathInterpolator(0.17f, 0.17f, 0f, 1f)
}
viewPropertyAnimator.interpolator = PathInterpolator(0.17f, 0.17f, 0f, 1f)
viewPropertyAnimator.start()
true
} else {
@ -452,7 +447,6 @@ class MediaPreviewV2Fragment : LoggingFragment(R.layout.fragment_media_preview_v
if (pagerAdapter.getFragmentTag(viewModel.currentPosition) == tag) {
debouncer.clear()
fullscreenHelper.showSystemUI()
}
}

View File

@ -45,6 +45,7 @@ import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.LoggingFragment;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.animation.AnimationCompleteListener;
import org.thoughtcrime.securesms.mediasend.camerax.CameraXModelBlocklist;
import org.thoughtcrime.securesms.mediasend.v2.MediaAnimations;
import org.thoughtcrime.securesms.mediasend.v2.MediaCountIndicatorButton;
import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader.DecryptableUri;
@ -59,7 +60,7 @@ import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.rxjava3.disposables.Disposable;
/**
* Camera capture implemented with the legacy camera API's. Should only be used if sdk < 21.
* Camera capture implemented with the legacy camera API's. Should only be used if a device is on the {@link CameraXModelBlocklist}.
*/
public class Camera1Fragment extends LoggingFragment implements CameraFragment,
TextureView.SurfaceTextureListener,

View File

@ -73,7 +73,6 @@ import io.reactivex.rxjava3.disposables.Disposable;
* preferred whenever possible.
*/
@ExperimentalVideo
@RequiresApi(21)
public class CameraXFragment extends LoggingFragment implements CameraFragment {
private static final String TAG = Log.tag(CameraXFragment.class);

View File

@ -5,12 +5,10 @@ import android.view.Window;
import android.view.WindowManager;
import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
import androidx.camera.core.CameraSelector;
import androidx.camera.core.ImageCapture;
import androidx.camera.view.CameraController;
@RequiresApi(21)
final class CameraXSelfieFlashHelper {
private static final float MAX_SCREEN_BRIGHTNESS = 1f;

View File

@ -15,7 +15,6 @@ import org.thoughtcrime.securesms.R;
import java.util.Arrays;
import java.util.List;
@RequiresApi(21)
public final class CameraXFlashToggleView extends AppCompatImageView {
private static final String STATE_FLASH_INDEX = "flash.toggle.state.flash.index";

View File

@ -2,7 +2,6 @@ package org.thoughtcrime.securesms.mediasend.camerax
import android.content.Context
import android.os.Build
import androidx.annotation.RequiresApi
import androidx.camera.view.CameraController
import androidx.camera.view.video.ExperimentalVideo
import org.signal.core.util.asListContains
@ -13,7 +12,6 @@ import org.thoughtcrime.securesms.video.VideoUtil
/**
* Describes device capabilities
*/
@RequiresApi(21)
@ExperimentalVideo
sealed class CameraXModePolicy {

View File

@ -41,7 +41,6 @@ public class CameraXUtil {
private static final String TAG = Log.tag(CameraXUtil.class);
@RequiresApi(21)
private static final int[] CAMERA_HARDWARE_LEVEL_ORDERING = new int[]{CameraMetadata.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY,
CameraMetadata.INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED,
CameraMetadata.INFO_SUPPORTED_HARDWARE_LEVEL_FULL};
@ -60,7 +59,6 @@ public class CameraXUtil {
CameraMetadata.INFO_SUPPORTED_HARDWARE_LEVEL_3};
@SuppressWarnings("SuspiciousNameCombination")
@RequiresApi(21)
public static ImageResult toJpeg(@NonNull ImageProxy image, boolean flip) throws IOException {
ImageProxy.PlaneProxy[] planes = image.getPlanes();
ByteBuffer buffer = planes[0].getBuffer();
@ -106,10 +104,9 @@ public class CameraXUtil {
}
public static boolean isSupported() {
return Build.VERSION.SDK_INT >= 21 && !CameraXModelBlocklist.isBlocklisted();
return !CameraXModelBlocklist.isBlocklisted();
}
@RequiresApi(21)
public static int toCameraDirectionInt(CameraSelector cameraSelector) {
if (cameraSelector == CameraSelector.DEFAULT_FRONT_CAMERA) {
return Camera.CameraInfo.CAMERA_FACING_FRONT;
@ -118,7 +115,6 @@ public class CameraXUtil {
}
}
@RequiresApi(21)
public static CameraSelector toCameraSelector(@CameraSelector.LensFacing int cameraDirectionInt) {
if (cameraDirectionInt == Camera.CameraInfo.CAMERA_FACING_FRONT) {
return CameraSelector.DEFAULT_FRONT_CAMERA;
@ -127,7 +123,6 @@ public class CameraXUtil {
}
}
@RequiresApi(21)
public static @ImageCapture.CaptureMode int getOptimalCaptureMode() {
return FastCameraModels.contains(Build.MODEL) ? ImageCapture.CAPTURE_MODE_MAXIMIZE_QUALITY
: ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY;
@ -138,7 +133,6 @@ public class CameraXUtil {
return Math.max(maxDisplay, 1920);
}
@TargetApi(21)
public static @NonNull Size buildResolutionForRatio(int longDimension, @NonNull Rational ratio, boolean isPortrait) {
int shortDimension = longDimension * ratio.getDenominator() / ratio.getNumerator();
@ -187,7 +181,6 @@ public class CameraXUtil {
return transformedData;
}
@RequiresApi(21)
private static boolean shouldCropImage(@NonNull ImageProxy image) {
Size sourceSize = new Size(image.getWidth(), image.getHeight());
Size targetSize = new Size(image.getCropRect().width(), image.getCropRect().height());
@ -205,12 +198,10 @@ public class CameraXUtil {
return out.toByteArray();
}
@RequiresApi(21)
public static boolean isMixedModeSupported(@NonNull Context context) {
return getLowestSupportedHardwareLevel(context) != CameraMetadata.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY;
}
@RequiresApi(21)
public static int getLowestSupportedHardwareLevel(@NonNull Context context) {
@SuppressLint("RestrictedApi") CameraManager cameraManager = CameraManagerCompat.from(context).unwrap();
@ -241,13 +232,11 @@ public class CameraXUtil {
}
}
@RequiresApi(21)
private static int maxHardwareLevel() {
if (Build.VERSION.SDK_INT >= 24) return CameraMetadata.INFO_SUPPORTED_HARDWARE_LEVEL_3;
else return CameraMetadata.INFO_SUPPORTED_HARDWARE_LEVEL_FULL;
}
@RequiresApi(21)
private static int smallerHardwareLevel(int levelA, int levelB) {
int[] hardwareInfoOrdering = getHardwareInfoOrdering();
@ -258,7 +247,6 @@ public class CameraXUtil {
return CameraMetadata.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY;
}
@RequiresApi(21)
private static int[] getHardwareInfoOrdering() {
if (Build.VERSION.SDK_INT >= 28) return CAMERA_HARDWARE_LEVEL_ORDERING_28;
else if (Build.VERSION.SDK_INT >= 24) return CAMERA_HARDWARE_LEVEL_ORDERING_24;

View File

@ -29,7 +29,7 @@ sealed class MediaSelectionDestination {
}
class SingleRecipient(private val id: RecipientId) : MediaSelectionDestination() {
override fun getRecipientSearchKey(): ContactSearchKey.RecipientSearchKey = ContactSearchKey.RecipientSearchKey.KnownRecipient(id)
override fun getRecipientSearchKey(): ContactSearchKey.RecipientSearchKey = ContactSearchKey.RecipientSearchKey(id, false)
override fun toBundle(): Bundle {
return Bundle().apply {
@ -39,7 +39,7 @@ sealed class MediaSelectionDestination {
}
class SingleStory(private val id: RecipientId) : MediaSelectionDestination() {
override fun getRecipientSearchKey(): ContactSearchKey.RecipientSearchKey = ContactSearchKey.RecipientSearchKey.Story(id)
override fun getRecipientSearchKey(): ContactSearchKey.RecipientSearchKey = ContactSearchKey.RecipientSearchKey(id, true)
override fun toBundle(): Bundle {
return Bundle().apply {
@ -51,8 +51,8 @@ sealed class MediaSelectionDestination {
class MultipleRecipients(val recipientSearchKeys: List<ContactSearchKey.RecipientSearchKey>) : MediaSelectionDestination() {
companion object {
fun fromParcel(parcelables: List<ContactSearchKey.ParcelableRecipientSearchKey>): MultipleRecipients {
return MultipleRecipients(parcelables.map { it.asRecipientSearchKey() }.filterIsInstance(ContactSearchKey.RecipientSearchKey::class.java))
fun fromParcel(parcelables: List<ContactSearchKey.RecipientSearchKey>): MultipleRecipients {
return MultipleRecipients(parcelables)
}
}
@ -68,7 +68,7 @@ sealed class MediaSelectionDestination {
override fun toBundle(): Bundle {
return Bundle().apply {
putParcelableArrayList(RECIPIENT_LIST, ArrayList(recipientSearchKeys.map { it.requireParcelable() }))
putParcelableArrayList(RECIPIENT_LIST, ArrayList(recipientSearchKeys.map { it.requireRecipientSearchKey() }))
}
}
}

View File

@ -37,7 +37,14 @@ class MediaSelectionGalleryFragment : Fragment(R.layout.fragment_container), Med
)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
val isFirst = arguments?.getBoolean("suppressEmptyError") ?: arguments?.getBoolean("first") ?: false
val args = arguments
val isFirst = when {
args == null -> false
args.containsKey("suppressEmptyError") -> args.getBoolean("suppressEmptyError")
args.containsKey("first") -> args.getBoolean("first")
else -> false
}
sharedViewModel.setSuppressEmptyError(isFirst)
mediaGalleryFragment = ensureMediaGalleryFragment()

View File

@ -86,7 +86,7 @@ class ChooseGroupStoryBottomSheet : FixedRoundedCornerBottomSheetDialogFragment(
mediator.getSelectionState().observe(viewLifecycleOwner) { state ->
adapter.submitList(
state.filterIsInstance(ContactSearchKey.RecipientSearchKey.KnownRecipient::class.java)
state.filterIsInstance(ContactSearchKey.RecipientSearchKey::class.java)
.map { it.recipientId }
.mapIndexed { index, recipientId ->
ShareSelectionMappingModel(
@ -146,7 +146,7 @@ class ChooseGroupStoryBottomSheet : FixedRoundedCornerBottomSheetDialogFragment(
RESULT_SET,
ArrayList(
mediator.getSelectedContacts()
.filterIsInstance(ContactSearchKey.RecipientSearchKey.KnownRecipient::class.java)
.filterIsInstance(ContactSearchKey.RecipientSearchKey::class.java)
.map { it.recipientId }
)
)

View File

@ -37,7 +37,7 @@ class ChooseStoryTypeBottomSheet : DSLSettingsBottomSheetFragment(
stringId = R.string.ChooseStoryTypeBottomSheet__visible_only_to
),
icon = DSLSettingsIcon.from(
iconId = R.drawable.ic_stories_24,
iconId = R.drawable.symbol_stories_24,
iconTintId = R.color.signal_colorOnSurface,
backgroundId = R.drawable.circle_tintable,
backgroundTint = R.color.signal_colorSurface5,

View File

@ -251,7 +251,7 @@ class TextStoryPostCreationFragment : Fragment(R.layout.stories_text_post_creati
return if (linkPreviewState.linkPreview.isPresent) {
linkPreviewState.linkPreview.get()
} else if (!linkPreviewState.activeUrlForError.isNullOrEmpty()) {
LinkPreview(linkPreviewState.activeUrlForError!!, "", "", 0L, Optional.empty())
LinkPreview(linkPreviewState.activeUrlForError!!, linkPreviewState.activeUrlForError!!, "", 0L, Optional.empty())
} else {
null
}

View File

@ -64,7 +64,7 @@ class TextStoryPostSendRepository {
for (contact in contactSearchKey) {
val recipient = Recipient.resolved(contact.requireShareContact().recipientId.get())
val isStory = contact is ContactSearchKey.RecipientSearchKey.Story || recipient.isDistributionList
val isStory = contact.requireRecipientSearchKey().isStory || recipient.isDistributionList
if (isStory && !recipient.isMyStory) {
SignalStore.storyValues().setLatestStorySend(StorySend.newSend(recipient))

View File

@ -35,6 +35,7 @@ import org.thoughtcrime.securesms.crypto.SecurityEvent;
import org.thoughtcrime.securesms.database.AttachmentTable;
import org.thoughtcrime.securesms.database.CallTable;
import org.thoughtcrime.securesms.database.GroupTable;
import org.thoughtcrime.securesms.database.StorySendTable;
import org.thoughtcrime.securesms.database.model.GroupRecord;
import org.thoughtcrime.securesms.database.GroupReceiptTable;
import org.thoughtcrime.securesms.database.GroupReceiptTable.GroupReceiptInfo;
@ -1750,12 +1751,20 @@ public final class MessageContentProcessor {
try {
RecipientId storyAuthorRecipient = RecipientId.from(storyContext.getAuthorServiceId());
RecipientId selfId = Recipient.self().getId();
ParentStoryId parentStoryId;
QuoteModel quoteModel = null;
long expiresInMillis = 0L;
MessageId storyMessageId = null;
try {
MessageId storyMessageId = database.getStoryId(storyAuthorRecipient, storyContext.getSentTimestamp());
if (selfId.equals(storyAuthorRecipient)) {
storyMessageId = SignalDatabase.storySends().getStoryMessageFor(senderRecipient.getId(), storyContext.getSentTimestamp());
}
if (storyMessageId == null) {
storyMessageId = database.getStoryId(storyAuthorRecipient, storyContext.getSentTimestamp());
}
MmsMessageRecord story = (MmsMessageRecord) database.getMessageRecord(storyMessageId.getId());
Recipient threadRecipient = Objects.requireNonNull(SignalDatabase.threads().getRecipientForThreadId(story.getThreadId()));
boolean groupStory = threadRecipient.isActiveGroup();
@ -1942,10 +1951,17 @@ public final class MessageContentProcessor {
}
private long handleSynchronizeSentExpirationUpdate(@NonNull SentTranscriptMessage message)
throws MmsException, BadGroupIdException
throws MmsException
{
log(message.getTimestamp(), "Synchronize sent expiration update.");
Optional<GroupId> groupId = getSyncMessageDestination(message).getGroupId();
if (groupId.isPresent() && groupId.get().isV2()) {
warn(String.valueOf(message.getTimestamp()), "Expiration update received for GV2. Ignoring.");
return -1;
}
MessageTable database = SignalDatabase.messages();
Recipient recipient = getSyncMessageDestination(message);
@ -1974,9 +1990,9 @@ public final class MessageContentProcessor {
try {
Optional<SignalServiceDataMessage.Reaction> reaction = message.getDataMessage().get().getReaction();
ParentStoryId parentStoryId;
SignalServiceDataMessage.StoryContext storyContext = message.getDataMessage().get().getStoryContext().get();
MessageTable database = SignalDatabase.messages();
Recipient recipient = getSyncMessageDestination(message);
SignalServiceDataMessage.StoryContext storyContext = message.getDataMessage().get().getStoryContext().get();
MessageTable database = SignalDatabase.messages();
Recipient recipient = getSyncMessageDestination(message);
QuoteModel quoteModel = null;
long expiresInMillis = 0L;
RecipientId storyAuthorRecipient = RecipientId.from(storyContext.getAuthorServiceId());
@ -2206,8 +2222,8 @@ public final class MessageContentProcessor {
{
log(envelopeTimestamp, "Synchronize sent media message for " + message.getTimestamp());
MessageTable database = SignalDatabase.messages();
Recipient recipients = getSyncMessageDestination(message);
MessageTable database = SignalDatabase.messages();
Recipient recipients = getSyncMessageDestination(message);
Optional<QuoteModel> quote = getValidatedQuote(message.getDataMessage().get().getQuote());
Optional<Attachment> sticker = getStickerAttachment(message.getDataMessage().get().getSticker());
Optional<List<Contact>> sharedContacts = getContacts(message.getDataMessage().get().getSharedContacts());
@ -3019,7 +3035,7 @@ public final class MessageContentProcessor {
LinkPreview linkPreview = new LinkPreview(url.get(), title.orElse(""), description.orElse(""), preview.getDate(), thumbnail);
linkPreviews.add(linkPreview);
} else {
warn(String.format("Discarding an invalid link preview. hasTitle: %b presentInBody: %b validDomain: %b", hasTitle, presentInBody, validDomain));
warn(String.format("Discarding an invalid link preview. hasTitle: %b presentInBody: %b isStoryEmbed: %b validDomain: %b", hasTitle, presentInBody, isStoryEmbed, validDomain));
}
}

View File

@ -1,5 +1,7 @@
package org.thoughtcrime.securesms.pin;
import android.app.backup.BackupManager;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@ -8,6 +10,7 @@ import org.signal.core.util.logging.Log;
import org.signal.libsignal.protocol.InvalidKeyException;
import org.thoughtcrime.securesms.KbsEnclave;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.lock.PinHashing;
import org.whispersystems.signalservice.api.KbsPinData;
import org.whispersystems.signalservice.api.KeyBackupService;
@ -69,6 +72,7 @@ public class KbsRepository {
try {
authorization = authorization == null ? kbs.getAuthorization() : authorization;
backupAuthToken(authorization);
token = kbs.getToken(authorization);
} catch (NonSuccessfulResponseCodeException e) {
if (e.getCode() == 404) {
@ -95,6 +99,13 @@ public class KbsRepository {
return Objects.requireNonNull(firstKnownTokenData);
}
private static void backupAuthToken(String token) {
final boolean tokenIsNew = SignalStore.kbsValues().appendAuthTokenToList(token);
if (tokenIsNew) {
new BackupManager(ApplicationDependencies.getApplication()).dataChanged();
}
}
/**
* Invoked during registration to restore the master key based on the server response during
* verification.

View File

@ -29,6 +29,7 @@ import org.whispersystems.signalservice.internal.contacts.crypto.Unauthenticated
import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import java.util.Locale;
import java.util.Optional;
import java.util.concurrent.TimeUnit;

View File

@ -309,7 +309,7 @@ public class EditProfileFragment extends LoggingFragment {
private void handleUpload() {
viewModel.getUploadResult().observe(getViewLifecycleOwner(), uploadResult -> {
if (uploadResult == EditProfileRepository.UploadResult.SUCCESS) {
if (Build.VERSION.SDK_INT >= 21 && !viewModel.isGroup()) {
if (!viewModel.isGroup()) {
handleFinishedLollipop();
}
else {
@ -330,7 +330,6 @@ public class EditProfileFragment extends LoggingFragment {
controller.onProfileNameUploadCompleted();
}
@RequiresApi(api = 21)
private void handleFinishedLollipop() {
int[] finishButtonLocation = new int[2];
int[] revealLocation = new int[2];

View File

@ -143,7 +143,7 @@ public class EditAboutFragment extends Fragment implements ManageProfileActivity
this.emojiView.setImageDrawable(drawable);
this.selectedEmoji = emoji;
} else {
this.emojiView.setImageResource(R.drawable.ic_add_emoji);
this.emojiView.setImageResource(R.drawable.symbol_emoji_plus_24);
this.selectedEmoji = "";
}
}

View File

@ -230,14 +230,14 @@ public class ManageProfileFragment extends LoggingFragment {
private void presentAboutEmoji(@NonNull String aboutEmoji) {
if (aboutEmoji == null || aboutEmoji.isEmpty()) {
binding.manageProfileAboutIcon.setImageDrawable(ResourcesCompat.getDrawable(getResources(), R.drawable.ic_compose_24, null));
binding.manageProfileAboutIcon.setImageDrawable(ResourcesCompat.getDrawable(getResources(), R.drawable.symbol_edit_24, null));
} else {
Drawable emoji = EmojiUtil.convertToDrawable(requireContext(), aboutEmoji);
if (emoji != null) {
binding.manageProfileAboutIcon.setImageDrawable(emoji);
} else {
binding.manageProfileAboutIcon.setImageDrawable(ResourcesCompat.getDrawable(getResources(), R.drawable.ic_compose_24, null));
binding.manageProfileAboutIcon.setImageDrawable(ResourcesCompat.getDrawable(getResources(), R.drawable.symbol_edit_24, null));
}
}
}

View File

@ -1367,7 +1367,7 @@ public class Recipient {
}
public @NonNull FallbackContactPhoto getPhotoForDistributionList() {
return new ResourceContactPhoto(R.drawable.ic_stories_24, R.drawable.ic_stories_24, R.drawable.ic_stories_24);
return new ResourceContactPhoto(R.drawable.symbol_stories_24, R.drawable.symbol_stories_24, R.drawable.symbol_stories_24);
}
}

View File

@ -5,6 +5,7 @@ import android.app.Application;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.WorkerThread;
import androidx.core.app.NotificationManagerCompat;
import org.signal.core.util.logging.Log;
import org.signal.libsignal.protocol.state.PreKeyRecord;
@ -25,6 +26,7 @@ import org.thoughtcrime.securesms.jobmanager.JobManager;
import org.thoughtcrime.securesms.jobs.DirectoryRefreshJob;
import org.thoughtcrime.securesms.jobs.RotateCertificateJob;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.notifications.NotificationIds;
import org.thoughtcrime.securesms.pin.PinState;
import org.thoughtcrime.securesms.push.AccountManagerFactory;
import org.thoughtcrime.securesms.recipients.Recipient;
@ -168,7 +170,7 @@ public final class RegistrationRepository {
SignalStore.account().setServicePassword(registrationData.getPassword());
SignalStore.account().setRegistered(true);
TextSecurePreferences.setPromptedPushRegistration(context, true);
TextSecurePreferences.setUnauthorizedReceived(context, false);
NotificationManagerCompat.from(context).cancel(NotificationIds.UNREGISTERED_NOTIFICATION_ID);
PinState.onRegistration(context, kbsData, pin, hasPin);
}

View File

@ -99,7 +99,7 @@ object SafetyNumberBottomSheet {
fun forIdentityRecordsAndDestinations(identityRecords: List<IdentityRecord>, destinations: List<ContactSearchKey>): Factory {
val args = SafetyNumberBottomSheetArgs(
identityRecords.map { it.recipientId },
destinations.filterIsInstance<ContactSearchKey.RecipientSearchKey>().map { it.requireParcelable() }
destinations.filterIsInstance<ContactSearchKey.RecipientSearchKey>().map { it.requireRecipientSearchKey() }
)
return SheetFactory(args)
@ -115,7 +115,7 @@ object SafetyNumberBottomSheet {
fun forIdentityRecordsAndDestination(identityRecords: List<IdentityRecord>, destination: ContactSearchKey): Factory {
val args = SafetyNumberBottomSheetArgs(
identityRecords.map { it.recipientId },
listOf(destination).filterIsInstance<ContactSearchKey.RecipientSearchKey>().map { it.requireParcelable() }
listOf(destination).filterIsInstance<ContactSearchKey.RecipientSearchKey>().map { it.requireRecipientSearchKey() }
)
return SheetFactory(args)
@ -131,14 +131,14 @@ object SafetyNumberBottomSheet {
return args!!
}
private fun getDestinationFromRecord(messageRecord: MessageRecord): List<ContactSearchKey.ParcelableRecipientSearchKey> {
private fun getDestinationFromRecord(messageRecord: MessageRecord): List<ContactSearchKey.RecipientSearchKey> {
val key = if ((messageRecord as? MmsMessageRecord)?.storyType?.isStory == true) {
ContactSearchKey.RecipientSearchKey.Story(messageRecord.recipient.id)
ContactSearchKey.RecipientSearchKey(messageRecord.recipient.id, true)
} else {
ContactSearchKey.RecipientSearchKey.KnownRecipient(messageRecord.recipient.id)
ContactSearchKey.RecipientSearchKey(messageRecord.recipient.id, false)
}
return listOf(key.requireParcelable())
return listOf(key)
}
/**

View File

@ -12,6 +12,6 @@ import org.thoughtcrime.securesms.recipients.RecipientId
@Parcelize
data class SafetyNumberBottomSheetArgs(
val untrustedRecipients: List<RecipientId>,
val destinations: List<ContactSearchKey.ParcelableRecipientSearchKey>,
val destinations: List<ContactSearchKey.RecipientSearchKey>,
val messageId: MessageId? = null
) : Parcelable

View File

@ -25,7 +25,7 @@ class SafetyNumberBottomSheetViewModel(
private const val MAX_RECIPIENTS_TO_DISPLAY = 5
}
private val destinationStore = RxStore(args.destinations.map { it.asRecipientSearchKey() })
private val destinationStore = RxStore(args.destinations)
val destinationSnapshot: List<ContactSearchKey.RecipientSearchKey>
get() = destinationStore.state

View File

@ -183,11 +183,11 @@ class RotationDialView @JvmOverloads constructor(
}
private inner class GestureListener : GestureDetector.SimpleOnGestureListener() {
override fun onDown(e: MotionEvent?): Boolean {
override fun onDown(e: MotionEvent): Boolean {
return true
}
override fun onScroll(e1: MotionEvent?, e2: MotionEvent?, distanceX: Float, distanceY: Float): Boolean {
override fun onScroll(e1: MotionEvent, e2: MotionEvent, distanceX: Float, distanceY: Float): Boolean {
val degreeIncrement: Float = distanceX / dimensions.spaceBetweenAngleIndicators
val prevDialDegrees = getDialDegrees(degrees)
val newDialDegrees = getDialDegrees(degrees + degreeIncrement)

View File

@ -24,6 +24,7 @@ import org.thoughtcrime.securesms.util.Util;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
@ -61,11 +62,9 @@ public final class MultiShareArgs implements Parcelable {
}
protected MultiShareArgs(Parcel in) {
List<ContactSearchKey.ParcelableRecipientSearchKey> parcelableRecipientSearchKeys = in.createTypedArrayList(ContactSearchKey.ParcelableRecipientSearchKey.CREATOR);
List<ContactSearchKey.RecipientSearchKey> parcelableRecipientSearchKeys = in.createTypedArrayList(ContactSearchKey.RecipientSearchKey.CREATOR);
contactSearchKeys = parcelableRecipientSearchKeys.stream()
.map(ContactSearchKey.ParcelableRecipientSearchKey::asRecipientSearchKey)
.collect(Collectors.toSet());
contactSearchKeys = new HashSet<>(parcelableRecipientSearchKeys);
media = in.createTypedArrayList(Media.CREATOR);
draftText = in.readString();
stickerLocator = in.readParcelable(StickerLocator.class.getClassLoader());
@ -197,7 +196,8 @@ public final class MultiShareArgs implements Parcelable {
}
public boolean allRecipientsAreStories() {
return !contactSearchKeys.isEmpty() && contactSearchKeys.stream().allMatch(key -> key instanceof ContactSearchKey.RecipientSearchKey.Story);
return !contactSearchKeys.isEmpty() && contactSearchKeys.stream()
.allMatch(key -> key.requireRecipientSearchKey().isStory());
}
public static final Creator<MultiShareArgs> CREATOR = new Creator<MultiShareArgs>() {
@ -219,7 +219,7 @@ public final class MultiShareArgs implements Parcelable {
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeTypedList(Stream.of(contactSearchKeys).map(ContactSearchKey::requireParcelable).toList());
dest.writeTypedList(Stream.of(contactSearchKeys).map(ContactSearchKey::requireRecipientSearchKey).toList());
dest.writeTypedList(media);
dest.writeString(draftText);
dest.writeParcelable(stickerLocator, flags);
@ -267,7 +267,7 @@ public final class MultiShareArgs implements Parcelable {
(!media.isEmpty() ||
!TextUtils.isEmpty(draftText) ||
MediaUtil.isImageOrVideoType(dataType) ||
(!contactSearchKeys.isEmpty() && contactSearchKeys.stream().anyMatch(key -> key instanceof ContactSearchKey.RecipientSearchKey.Story)));
(!contactSearchKeys.isEmpty() && contactSearchKeys.stream().anyMatch(key -> key.requireRecipientSearchKey().isStory())));
}
public static final class Builder {

View File

@ -103,7 +103,7 @@ class ShareActivity : PassphraseRequiredActivity(), MultiselectForwardFragment.C
openConversation(
ShareEvent.OpenConversation(
shareState.loadState.resolvedShareData,
ContactSearchKey.RecipientSearchKey.KnownRecipient(directShareTarget)
ContactSearchKey.RecipientSearchKey(directShareTarget, false)
)
)
} else {
@ -134,8 +134,7 @@ class ShareActivity : PassphraseRequiredActivity(), MultiselectForwardFragment.C
throw AssertionError("Expected a recipient selection!")
}
val parcelizedKeys: List<ContactSearchKey.ParcelableRecipientSearchKey> = bundle.getParcelableArrayList(MultiselectForwardFragment.RESULT_SELECTION)!!
val contactSearchKeys = parcelizedKeys.map { it.asRecipientSearchKey() }
val contactSearchKeys: List<ContactSearchKey.RecipientSearchKey> = bundle.getParcelableArrayList(MultiselectForwardFragment.RESULT_SELECTION)!!
viewModel.onContactSelectionConfirmed(contactSearchKeys)
}

View File

@ -10,7 +10,6 @@ import android.transition.TransitionValues
import android.util.AttributeSet
import android.view.View
import android.view.ViewGroup
import androidx.annotation.RequiresApi
import androidx.core.view.ViewCompat
class ScaleTransition : Transition {
@ -36,7 +35,6 @@ class ScaleTransition : Transition {
constructor() : super()
@RequiresApi(21)
constructor(context: Context, attrs: AttributeSet) : super(context, attrs)
override fun getTransitionProperties(): Array<String> {
@ -108,7 +106,7 @@ class ScaleTransition : Transition {
ofFloat(View.SCALE_Y, 1f)
).apply {
addListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator?) {
override fun onAnimationEnd(animation: Animator) {
resetValues(start.view)
resetValues(end.view)
}

View File

@ -125,7 +125,7 @@ class StoryFirstTimeNavigationView @JvmOverloads constructor(
isPlayingAnimations = true
tapToAdvance.addAnimatorListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator?) {
override fun onAnimationEnd(animation: Animator) {
if (isPlayingAnimations) {
swipeUp.playAnimation()
}
@ -133,7 +133,7 @@ class StoryFirstTimeNavigationView @JvmOverloads constructor(
})
swipeUp.addAnimatorListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator?) {
override fun onAnimationEnd(animation: Animator) {
if (isPlayingAnimations) {
swipeRight.playAnimation()
}
@ -141,7 +141,7 @@ class StoryFirstTimeNavigationView @JvmOverloads constructor(
})
swipeRight.addAnimatorListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator?) {
override fun onAnimationEnd(animation: Animator) {
postDelayed({
if (isPlayingAnimations) {
startLottieAnimations()

View File

@ -227,7 +227,7 @@ object StoryContextMenu {
}
)
add(
ActionItem(R.drawable.ic_download_24_tinted, context.getString(R.string.save)) {
ActionItem(R.drawable.ic_save_24_tinted, context.getString(R.string.save)) {
callbacks.onSave()
}
)

View File

@ -142,11 +142,11 @@ class StoriesLandingFragment : DSLSettingsFragment(layoutId = R.layout.stories_l
setEnterSharedElementCallback(object : SharedElementCallback() {
override fun onSharedElementStart(sharedElementNames: MutableList<String>?, sharedElements: MutableList<View>?, sharedElementSnapshots: MutableList<View>?) {
if (sharedElementNames?.contains("camera_fab") == true) {
cameraFab.setImageResource(R.drawable.ic_compose_outline_24)
cameraFab.setImageResource(R.drawable.symbol_edit_24)
lifecycleDisposable += Single.timer(200, TimeUnit.MILLISECONDS)
.observeOn(AndroidSchedulers.mainThread())
.subscribeBy {
cameraFab.setImageResource(R.drawable.ic_camera_outline_24)
cameraFab.setImageResource(R.drawable.symbol_camera_24)
sharedElementTarget.alpha = 0f
}
}
@ -157,7 +157,7 @@ class StoriesLandingFragment : DSLSettingsFragment(layoutId = R.layout.stories_l
Permissions.with(this)
.request(Manifest.permission.CAMERA)
.ifNecessary()
.withRationaleDialog(getString(R.string.ConversationActivity_to_capture_photos_and_video_allow_signal_access_to_the_camera), R.drawable.ic_camera_24)
.withRationaleDialog(getString(R.string.ConversationActivity_to_capture_photos_and_video_allow_signal_access_to_the_camera), R.drawable.symbol_camera_24)
.withPermanentDenialDialog(getString(R.string.ConversationActivity_signal_needs_the_camera_permission_to_take_photos_or_video))
.onAllGranted {
startActivityIfAble(MediaSelectionActivity.camera(requireContext(), isStory = true))

View File

@ -128,7 +128,7 @@ class AddToGroupStoryDelegate(
private fun sendNonPreUploadedMedia(result: MediaSendActivityResult) {
Log.d(TAG, "Sending non-preupload media.")
val multiShareArgs = MultiShareArgs.Builder(setOf(ContactSearchKey.RecipientSearchKey.Story(result.recipientId)))
val multiShareArgs = MultiShareArgs.Builder(setOf(ContactSearchKey.RecipientSearchKey(result.recipientId, true)))
.withMedia(result.nonUploadedMedia.toList())
.withDraftText(result.body)
.withMentions(result.mentions.toList())

View File

@ -1,6 +1,5 @@
package org.thoughtcrime.securesms.stories.viewer
import android.os.Build
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
@ -31,7 +30,7 @@ class StoryViewerViewModel(
storyViewerArgs.storyThumbUri != null -> StoryViewerState.CrossfadeSource.ImageUri(storyViewerArgs.storyThumbUri, storyViewerArgs.storyThumbBlur)
else -> StoryViewerState.CrossfadeSource.None
},
skipCrossfade = storyViewerArgs.isFromNotification || storyViewerArgs.isFromQuote || Build.VERSION.SDK_INT < 21
skipCrossfade = storyViewerArgs.isFromNotification || storyViewerArgs.isFromQuote
)
)

View File

@ -3,7 +3,6 @@ package org.thoughtcrime.securesms.stories.viewer.first
import android.app.Dialog
import android.graphics.Color
import android.graphics.drawable.ColorDrawable
import android.os.Build
import android.os.Bundle
import android.view.View
import android.view.WindowManager
@ -36,14 +35,12 @@ class StoryFirstTimeNavigationFragment : DialogFragment(R.layout.story_viewer_fi
val dialog = super.onCreateDialog(savedInstanceState)
dialog.window!!.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
if (Build.VERSION.SDK_INT >= 21) {
dialog.window!!.addFlags(
WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION or
WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS or
WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS or
WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS
)
}
dialog.window!!.addFlags(
WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION or
WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS or
WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS or
WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS
)
return dialog
}

View File

@ -1184,7 +1184,7 @@ class StoryViewerPageFragment :
scaleFactor = 1f
isPerformingEndAnimation = true
card.animate().scaleX(1f).scaleY(1f).setListener(object : AnimationCompleteListener() {
override fun onAnimationEnd(animation: Animator?) {
override fun onAnimationEnd(animation: Animator) {
isPerformingEndAnimation = false
viewModel.setIsUserScaling(false)
sharedViewModel.setIsChildScrolling(false)
@ -1255,7 +1255,7 @@ class StoryViewerPageFragment :
return true
}
override fun onFling(e1: MotionEvent?, e2: MotionEvent?, velocityX: Float, velocityY: Float): Boolean {
override fun onFling(e1: MotionEvent, e2: MotionEvent, velocityX: Float, velocityY: Float): Boolean {
val isSideSwipe = abs(velocityX) > abs(velocityY)
if (!isSideSwipe) {
return false

View File

@ -387,7 +387,7 @@ class StoryGroupReplyFragment :
resendReaction = emoji
SafetyNumberBottomSheet
.forIdentityRecordsAndDestination(error.untrustedRecords, ContactSearchKey.RecipientSearchKey.Story(groupRecipientId))
.forIdentityRecordsAndDestination(error.untrustedRecords, ContactSearchKey.RecipientSearchKey(groupRecipientId, true))
.show(childFragmentManager)
} else {
Log.w(TAG, "Failed to send reply", error)
@ -536,7 +536,7 @@ class StoryGroupReplyFragment :
resendMentions = mentions
SafetyNumberBottomSheet
.forIdentityRecordsAndDestination(throwable.untrustedRecords, ContactSearchKey.RecipientSearchKey.Story(groupRecipientId))
.forIdentityRecordsAndDestination(throwable.untrustedRecords, ContactSearchKey.RecipientSearchKey(groupRecipientId, true))
.show(childFragmentManager)
} else {
Log.w(TAG, "Failed to send reply", throwable)

View File

@ -36,7 +36,7 @@ object StoryGroupReplySender {
}
return messageAndRecipient.flatMapCompletable { (message, recipient) ->
UntrustedRecords.checkForBadIdentityRecords(setOf(ContactSearchKey.RecipientSearchKey.KnownRecipient(recipient.id)), System.currentTimeMillis() - IdentityRecordList.DEFAULT_UNTRUSTED_WINDOW)
UntrustedRecords.checkForBadIdentityRecords(setOf(ContactSearchKey.RecipientSearchKey(recipient.id, false)), System.currentTimeMillis() - IdentityRecordList.DEFAULT_UNTRUSTED_WINDOW)
.andThen(
Completable.create {
MessageSender.send(

View File

@ -89,13 +89,11 @@ public final class FeatureFlags {
private static final String PHONE_NUMBER_PRIVACY = "android.pnp";
private static final String USE_FCM_FOREGROUND_SERVICE = "android.useFcmForegroundService.3";
private static final String STORIES_AUTO_DOWNLOAD_MAXIMUM = "android.stories.autoDownloadMaximum";
private static final String GIFT_BADGE_SEND_SUPPORT = "android.giftBadges.sending.3";
private static final String TELECOM_MANUFACTURER_ALLOWLIST = "android.calling.telecomAllowList";
private static final String TELECOM_MODEL_BLOCKLIST = "android.calling.telecomModelBlockList";
private static final String CAMERAX_MODEL_BLOCKLIST = "android.cameraXModelBlockList";
private static final String CAMERAX_MIXED_MODEL_BLOCKLIST = "android.cameraXMixedModelBlockList";
private static final String RECIPIENT_MERGE_V2 = "android.recipientMergeV2";
private static final String SMS_EXPORTER = "android.sms.exporter.2";
private static final String HIDE_CONTACTS = "android.hide.contacts";
public static final String CREDIT_CARD_PAYMENTS = "android.credit.card.payments.3";
private static final String PAYMENTS_REQUEST_ACTIVATE_FLOW = "android.payments.requestActivateFlow";
@ -105,7 +103,7 @@ public final class FeatureFlags {
private static final String CDS_HARD_LIMIT = "android.cds.hardLimit";
private static final String CHAT_FILTERS = "android.chat.filters.3";
private static final String PAYPAL_ONE_TIME_DONATIONS = "android.oneTimePayPalDonations.2";
private static final String PAYPAL_RECURRING_DONATIONS = "android.recurringPayPalDonations";
private static final String PAYPAL_RECURRING_DONATIONS = "android.recurringPayPalDonations.2";
/**
* We will only store remote values for flags in this set. If you want a flag to be controllable
@ -145,13 +143,11 @@ public final class FeatureFlags {
PAYMENTS_COUNTRY_BLOCKLIST,
USE_FCM_FOREGROUND_SERVICE,
STORIES_AUTO_DOWNLOAD_MAXIMUM,
GIFT_BADGE_SEND_SUPPORT,
TELECOM_MANUFACTURER_ALLOWLIST,
TELECOM_MODEL_BLOCKLIST,
CAMERAX_MODEL_BLOCKLIST,
CAMERAX_MIXED_MODEL_BLOCKLIST,
RECIPIENT_MERGE_V2,
SMS_EXPORTER,
HIDE_CONTACTS,
CREDIT_CARD_PAYMENTS,
PAYMENTS_REQUEST_ACTIVATE_FLOW,

View File

@ -558,8 +558,6 @@ public class TextSecurePreferences {
if (value) {
clearLocalCredentials(context);
} else {
NotificationManagerCompat.from(context).cancel(NotificationIds.UNREGISTERED_NOTIFICATION_ID);
}
}

View File

@ -7,7 +7,6 @@ import android.util.DisplayMetrics;
import android.util.Size;
import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
import org.thoughtcrime.securesms.mms.MediaConstraints;
import org.thoughtcrime.securesms.util.MediaUtil;
@ -28,7 +27,6 @@ public final class VideoUtil {
private static final int TOTAL_BYTES_PER_SECOND = (VIDEO_BIT_RATE / 8) + (AUDIO_BIT_RATE / 8);
@RequiresApi(21)
public static final String VIDEO_MIME_TYPE = MediaFormat.MIMETYPE_VIDEO_AVC;
public static final String AUDIO_MIME_TYPE = "audio/mp4a-latm";
@ -36,7 +34,6 @@ public final class VideoUtil {
private VideoUtil() { }
@RequiresApi(21)
public static Size getVideoRecordingSize() {
return isPortrait(screenSize())
? new Size(VIDEO_SHORT_WIDTH, VIDEO_LONG_WIDTH)
@ -54,13 +51,11 @@ public final class VideoUtil {
return VIDEO_MAX_UPLOAD_LENGTH_S;
}
@RequiresApi(21)
private static Size screenSize() {
DisplayMetrics metrics = Resources.getSystem().getDisplayMetrics();
return new Size(metrics.widthPixels, metrics.heightPixels);
}
@RequiresApi(21)
private static boolean isPortrait(Size size) {
return size.getWidth() < size.getHeight();
}

View File

@ -235,7 +235,6 @@ public abstract class AudioManagerCompat {
}
}
@RequiresApi(21)
private static class Api21AudioManagerCompat extends Api19AudioManagerCompat {
private static AudioAttributes AUDIO_ATTRIBUTES = new AudioAttributes.Builder()

View File

@ -158,7 +158,7 @@ class FullSignalAudioManager(context: Context, eventListener: EventListener?) :
updateAudioDeviceState()
wiredHeadsetReceiver = WiredHeadsetReceiver()
context.registerReceiver(wiredHeadsetReceiver, IntentFilter(if (Build.VERSION.SDK_INT >= 21) AudioManager.ACTION_HEADSET_PLUG else Intent.ACTION_HEADSET_PLUG))
context.registerReceiver(wiredHeadsetReceiver, IntentFilter(AudioManager.ACTION_HEADSET_PLUG))
state = State.PREINITIALIZED

View File

@ -0,0 +1,16 @@
/**
* Copyright (C) 2023 Open Whisper Systems
*
* Licensed according to the LICENSE file in this repository.
*/
syntax = "proto3";
package signal;
option java_package = "org.thoughtcrime.securesms.absbackup";
option java_outer_classname = "ExternalBackupProtos";
message KbsAuthToken {
repeated string token = 1;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 228 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 170 B

View File

@ -1,9 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@color/core_ultramarine_light"
android:pathData="M17,15a2.98,2.98 0,0 0,-2.184 0.955L8.9,12.728a2.726,2.726 0,0 0,0 -1.456l5.915,-3.227A3.1,3.1 0,1 0,14.1 6.728L8.184,9.955a3,3 0,1 0,0 4.09L14.1,17.272A2.995,2.995 0,1 0,17 15Z"/>
</vector>

View File

@ -0,0 +1,24 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="56dp"
android:height="57dp"
android:viewportWidth="56"
android:viewportHeight="57">
<path
android:pathData="M17,3C11.477,3 7,7.477 7,13v20c0,5.185 3.947,9.449 9,9.95v8.222c0,1.781 2.154,2.674 3.414,1.414L29,43h8c5.523,0 10,-4.477 10,-10V13c0,-5.523 -4.477,-10 -10,-10H17Z"
android:fillColor="#1B1C1F"
android:fillType="evenOdd"/>
<path
android:pathData="M4,13C4,5.82 9.82,0 17,0h20c7.18,0 13,5.82 13,13v20c0,7.18 -5.82,13 -13,13h-6.757l-8.707,8.707c-3.15,3.15 -8.536,0.92 -8.536,-3.535v-5.8C7.777,43.685 4,38.785 4,33L4,13ZM16,42.95c-5.053,-0.501 -9,-4.765 -9,-9.95L7,13C7,7.477 11.477,3 17,3h20c5.523,0 10,4.477 10,10v20c0,5.523 -4.477,10 -10,10h-8l-9.586,9.586c-1.26,1.26 -3.414,0.367 -3.414,-1.414L16,42.95Z"
android:fillColor="#5C5E65"
android:fillType="evenOdd"/>
<path
android:pathData="M44,42m-12,0a12,12 0,1 1,24 0a12,12 0,1 1,-24 0"
android:fillColor="#4CAF50"/>
<path
android:pathData="M49.15,36.866a0.75,0.75 0,0 1,0.234 1.035l-6,9.5a0.75,0.75 0,0 1,-1.226 0.06l-3.5,-4.5a0.75,0.75 0,1 1,1.184 -0.922l2.844,3.657 5.43,-8.596a0.75,0.75 0,0 1,1.035 -0.234Z"
android:fillColor="#D2EBD3"/>
<path
android:pathData="M27,11.75A5.25,5.25 0,0 0,21.75 17v3.469a3.5,3.5 0,0 0,-1.369 1.442C20,22.66 20,23.64 20,25.6v1.8c0,1.96 0,2.94 0.381,3.689a3.5,3.5 0,0 0,1.53 1.53C22.66,33 23.64,33 25.6,33h2.8c1.96,0 2.94,0 3.689,-0.382a3.5,3.5 0,0 0,1.53 -1.529C34,30.34 34,29.36 34,27.4v-1.8c0,-1.96 0,-2.94 -0.382,-3.689a3.5,3.5 0,0 0,-1.368 -1.442L32.25,17c0,-2.9 -2.35,-5.25 -5.25,-5.25ZM29.75,20.007L29.75,17a2.75,2.75 0,1 0,-5.5 0v3.007C24.642,20 25.087,20 25.6,20h2.8c0.513,0 0.958,0 1.35,0.007ZM28,26.687a1.75,1.75 0,1 0,-2 0L26,28.5a1,1 0,1 0,2 0v-1.814Z"
android:fillColor="#6191F3"
android:fillType="evenOdd"/>
</vector>

View File

@ -0,0 +1,23 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="56dp"
android:height="56dp"
android:viewportWidth="56"
android:viewportHeight="56">
<path
android:pathData="M8,7a4,4 0,0 1,4 -4h16a4,4 0,0 1,4 4v34a4,4 0,0 1,-4 4H12a4,4 0,0 1,-4 -4V7Z"
android:fillColor="#1B1C1F"/>
<path
android:pathData="M12,0h16a7,7 0,0 1,7 7v34a7,7 0,0 1,-7 7L12,48a7,7 0,0 1,-7 -7L5,7a7,7 0,0 1,7 -7ZM12,3a4,4 0,0 0,-4 4v34a4,4 0,0 0,4 4h16a4,4 0,0 0,4 -4L32,7a4,4 0,0 0,-4 -4L12,3Z"
android:fillColor="#5C5E65"
android:fillType="evenOdd"/>
<path
android:pathData="M24,15a4,4 0,0 1,4 -4h16a4,4 0,0 1,4 4v34a4,4 0,0 1,-4 4H28a4,4 0,0 1,-4 -4V15Z"
android:fillColor="#1B1C1F"/>
<path
android:pathData="M28,8h16a7,7 0,0 1,7 7v34a7,7 0,0 1,-7 7L28,56a7,7 0,0 1,-7 -7L21,15a7,7 0,0 1,7 -7ZM28,11a4,4 0,0 0,-4 4v34a4,4 0,0 0,4 4h16a4,4 0,0 0,4 -4L48,15a4,4 0,0 0,-4 -4L28,11Z"
android:fillColor="#5C5E65"
android:fillType="evenOdd"/>
<path
android:pathData="m31.086,31 l-3.293,-3.293a1,1 0,0 1,1.414 -1.414l5,5a1,1 0,0 1,0 1.414l-5,5a1,1 0,0 1,-1.414 -1.414L31.086,33H21.5a8,8 0,0 1,-8 -8v-3a1,1 0,1 1,2 0v3a6,6 0,0 0,6 6h9.586Z"
android:fillColor="#6191F3"/>
</vector>

View File

@ -0,0 +1,24 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="56dp"
android:height="57dp"
android:viewportWidth="56"
android:viewportHeight="57">
<path
android:pathData="M17,3C11.477,3 7,7.477 7,13v20c0,5.185 3.947,9.449 9,9.95v8.222c0,1.781 2.154,2.674 3.414,1.414L29,43h8c5.523,0 10,-4.477 10,-10V13c0,-5.523 -4.477,-10 -10,-10H17Z"
android:fillColor="#1B1C1F"
android:fillType="evenOdd"/>
<path
android:pathData="M4,13C4,5.82 9.82,0 17,0h20c7.18,0 13,5.82 13,13v20c0,7.18 -5.82,13 -13,13h-6.757l-8.707,8.707c-3.15,3.15 -8.536,0.92 -8.536,-3.535v-5.8C7.777,43.685 4,38.785 4,33L4,13ZM16,42.95c-5.053,-0.501 -9,-4.765 -9,-9.95L7,13C7,7.477 11.477,3 17,3h20c5.523,0 10,4.477 10,10v20c0,5.523 -4.477,10 -10,10h-8l-9.586,9.586c-1.26,1.26 -3.414,0.367 -3.414,-1.414L16,42.95Z"
android:fillColor="#5C5E65"
android:fillType="evenOdd"/>
<path
android:pathData="M44,42m-12,0a12,12 0,1 1,24 0a12,12 0,1 1,-24 0"
android:fillColor="#930006"/>
<path
android:pathData="M40.53,37.47a0.75,0.75 0,1 0,-1.06 1.06L42.94,42l-3.47,3.47a0.75,0.75 0,1 0,1.06 1.06L44,43.06l3.47,3.47a0.75,0.75 0,1 0,1.06 -1.06L45.06,42l3.47,-3.47a0.75,0.75 0,1 0,-1.06 -1.06L44,40.94l-3.47,-3.47Z"
android:fillColor="#FFDAD4"/>
<path
android:pathData="M32.5,10.75A5.25,5.25 0,0 0,27.25 16v4.007C26.858,20 26.413,20 25.9,20h-2.8c-1.96,0 -2.94,0 -3.689,0.381a3.5,3.5 0,0 0,-1.53 1.53c-0.381,0.749 -0.381,1.729 -0.381,3.689v1.8c0,1.96 0,2.94 0.381,3.689a3.5,3.5 0,0 0,1.53 1.53C20.16,33 21.14,33 23.1,33h2.8c1.96,0 2.94,0 3.689,-0.382a3.5,3.5 0,0 0,1.53 -1.529c0.381,-0.749 0.381,-1.729 0.381,-3.689v-1.8c0,-1.96 0,-2.94 -0.381,-3.689a3.5,3.5 0,0 0,-1.369 -1.442L29.75,16a2.75,2.75 0,1 1,5.5 0v2.5a1.25,1.25 0,1 0,2.5 0L37.75,16c0,-2.9 -2.35,-5.25 -5.25,-5.25ZM25.5,26.686a1.75,1.75 0,1 0,-2 0L23.5,28.5a1,1 0,1 0,2 0v-1.814Z"
android:fillColor="#808389"
android:fillType="evenOdd"/>
</vector>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 264 B

Some files were not shown because too many files have changed in this diff Show More